2 ###############################################################################
4 # collecty - A system statistics collection daemon for IPFire #
5 # Copyright (C) 2012 IPFire development team #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 ###############################################################################
25 from .. import _collecty
28 from ..colours
import *
30 class GraphTemplateDiskBadSectors(base
.GraphTemplate
):
31 name
= "disk-bad-sectors"
35 _
= self
.locale
.translate
38 "AREA:bad_sectors%s:%s" % (COLOUR_CRITICAL
,_("Bad Sectors")),
39 "GPRINT:bad_sectors_cur:%12s\:" % _("Current") + " %9.2lf",
40 "GPRINT:bad_sectors_max:%12s\:" % _("Maximum") + " %9.2lf",
44 def graph_title(self
):
45 _
= self
.locale
.translate
46 return _("Bad Sectors of %s") % self
.object.device_string
49 def graph_vertical_label(self
):
50 _
= self
.locale
.translate
51 return _("Pending/Relocated Sectors")
54 class GraphTemplateDiskBytes(base
.GraphTemplate
):
59 _
= self
.locale
.translate
62 "CDEF:read_bytes=read_sectors,512,*",
63 "CDEF:write_bytes=write_sectors,512,*",
65 "LINE1:read_bytes%s:%-15s" % (COLOUR_READ
, _("Read")),
66 "GPRINT:read_bytes_cur:%12s\:" % _("Current") + " %9.2lf",
67 "GPRINT:read_bytes_max:%12s\:" % _("Maximum") + " %9.2lf",
68 "GPRINT:read_bytes_min:%12s\:" % _("Minimum") + " %9.2lf",
69 "GPRINT:read_bytes_avg:%12s\:" % _("Average") + " %9.2lf",
71 "LINE1:write_bytes%s:%-15s" % (COLOUR_WRITE
, _("Written")),
72 "GPRINT:write_bytes_cur:%12s\:" % _("Current") + " %9.2lf",
73 "GPRINT:write_bytes_max:%12s\:" % _("Maximum") + " %9.2lf",
74 "GPRINT:write_bytes_min:%12s\:" % _("Minimum") + " %9.2lf",
75 "GPRINT:write_bytes_avg:%12s\:" % _("Average") + " %9.2lf",
83 def graph_title(self
):
84 _
= self
.locale
.translate
85 return _("Disk Utilisation of %s") % self
.object.device_string
88 def graph_vertical_label(self
):
89 _
= self
.locale
.translate
90 return _("Byte per Second")
93 class GraphTemplateDiskIoOps(base
.GraphTemplate
):
98 _
= self
.locale
.translate
101 "LINE1:read_ios%s:%-15s" % (COLOUR_READ
, _("Read")),
102 "GPRINT:read_ios_cur:%12s\:" % _("Current") + " %6.2lf",
103 "GPRINT:read_ios_max:%12s\:" % _("Maximum") + " %6.2lf",
104 "GPRINT:read_ios_min:%12s\:" % _("Minimum") + " %6.2lf",
105 "GPRINT:read_ios_avg:%12s\:" % _("Average") + " %6.2lf",
107 "LINE1:write_ios%s:%-15s" % (COLOUR_WRITE
, _("Written")),
108 "GPRINT:write_ios_cur:%12s\:" % _("Current") + " %6.2lf",
109 "GPRINT:write_ios_max:%12s\:" % _("Maximum") + " %6.2lf",
110 "GPRINT:write_ios_min:%12s\:" % _("Minimum") + " %6.2lf",
111 "GPRINT:write_ios_avg:%12s\:" % _("Average") + " %6.2lf",
119 def graph_title(self
):
120 _
= self
.locale
.translate
121 return _("Disk IO Operations of %s") % self
.object.device_string
124 def graph_vertical_label(self
):
125 _
= self
.locale
.translate
126 return _("Operations per Second")
129 class GraphTemplateDiskTemperature(base
.GraphTemplate
):
130 name
= "disk-temperature"
134 _
= self
.locale
.translate
137 "CDEF:celsius=temperature,273.15,-",
138 "VDEF:temp_cur=celsius,LAST",
139 "VDEF:temp_min=celsius,MINIMUM",
140 "VDEF:temp_max=celsius,MAXIMUM",
141 "VDEF:temp_avg=celsius,AVERAGE",
143 "LINE2:celsius%s:%s" % (PRIMARY
, _("Temperature")),
144 "GPRINT:temp_cur:%12s\:" % _("Current") + " %3.2lf",
145 "GPRINT:temp_max:%12s\:" % _("Maximum") + " %3.2lf",
146 "GPRINT:temp_min:%12s\:" % _("Minimum") + " %3.2lf",
147 "GPRINT:temp_avg:%12s\:" % _("Average") + " %3.2lf",
153 def graph_title(self
):
154 _
= self
.locale
.translate
155 return _("Disk Temperature of %s") % self
.object.device_string
158 def graph_vertical_label(self
):
159 _
= self
.locale
.translate
160 return _("° Celsius")
163 def rrd_graph_args(self
):
165 # Make the y-axis have a decimal
166 "--left-axis-format", "%3.1lf",
170 class DiskObject(base
.Object
):
172 "DS:awake:GAUGE:0:1",
173 "DS:read_ios:DERIVE:0:U",
174 "DS:read_sectors:DERIVE:0:U",
175 "DS:write_ios:DERIVE:0:U",
176 "DS:write_sectors:DERIVE:0:U",
177 "DS:bad_sectors:GAUGE:0:U",
178 "DS:temperature:GAUGE:U:U",
182 return "<%s %s (%s)>" % (self
.__class
__.__name
__, self
.sys_path
, self
.id)
184 def init(self
, device
):
185 self
.dev_path
= os
.path
.join("/dev", device
)
186 self
.sys_path
= os
.path
.join("/sys/block", device
)
188 self
.device
= _collecty
.BlockDevice(self
.dev_path
)
192 return "-".join((self
.device
.model
, self
.device
.serial
))
195 def device_string(self
):
196 return "%s (%s)" % (self
.device
.model
, self
.dev_path
)
199 stats
= self
.parse_stats()
203 stats
.get("read_ios"),
204 stats
.get("read_sectors"),
205 stats
.get("write_ios"),
206 stats
.get("write_sectors"),
207 self
.get_bad_sectors(),
208 self
.get_temperature(),
211 def parse_stats(self
):
213 https://www.kernel.org/doc/Documentation/block/stat.txt
215 Name units description
216 ---- ----- -----------
217 read I/Os requests number of read I/Os processed
218 read merges requests number of read I/Os merged with in-queue I/O
219 read sectors sectors number of sectors read
220 read ticks milliseconds total wait time for read requests
221 write I/Os requests number of write I/Os processed
222 write merges requests number of write I/Os merged with in-queue I/O
223 write sectors sectors number of sectors written
224 write ticks milliseconds total wait time for write requests
225 in_flight requests number of I/Os currently in flight
226 io_ticks milliseconds total time this block device has been active
227 time_in_queue milliseconds total wait time for all requests
229 stats_file
= os
.path
.join(self
.sys_path
, "stat")
231 with
open(stats_file
) as f
:
232 stats
= f
.read().split()
235 "read_ios" : stats
[0],
236 "read_merges" : stats
[1],
237 "read_sectors" : stats
[2],
238 "read_ticks" : stats
[3],
239 "write_ios" : stats
[4],
240 "write_merges" : stats
[5],
241 "write_sectors" : stats
[6],
242 "write_ticks" : stats
[7],
243 "in_flight" : stats
[8],
244 "io_ticks" : stats
[9],
245 "time_in_queue" : stats
[10],
248 def is_smart_supported(self
):
250 We can only query SMART data if SMART is supported by the disk
251 and when the disk is awake.
253 return self
.device
.is_smart_supported() and self
.device
.is_awake()
256 # If SMART is supported we can get the data from the disk
257 if self
.device
.is_smart_supported():
258 if self
.device
.is_awake():
263 # Otherwise we just assume that the disk is awake
266 def get_temperature(self
):
267 if not self
.is_smart_supported():
271 return self
.device
.get_temperature()
275 def get_bad_sectors(self
):
276 if not self
.is_smart_supported():
279 return self
.device
.get_bad_sectors()
282 class DiskPlugin(base
.Plugin
):
284 description
= "Disk Plugin"
287 GraphTemplateDiskBadSectors
,
288 GraphTemplateDiskBytes
,
289 GraphTemplateDiskIoOps
,
290 GraphTemplateDiskTemperature
,
293 block_device_patterns
= [
300 for dev
in self
.find_block_devices():
302 yield DiskObject(self
, dev
)
306 def find_block_devices(self
):
307 for device
in os
.listdir("/sys/block"):
308 # Skip invalid device names
309 if not self
._valid
_block
_device
_name
(device
):
314 def _valid_block_device_name(self
, name
):
315 # Check if the given name matches any of the valid patterns.
316 for pattern
in self
.block_device_patterns
:
317 if re
.match(pattern
, name
):