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 ###############################################################################
22 from collecty
import _collecty
30 class GraphTemplateDiskBadSectors(base
.GraphTemplate
):
31 name
= "disk-bad-sectors"
35 _
= self
.locale
.translate
38 "DEF:bad_sectors=%(file)s:bad_sectors:AVERAGE",
40 "AREA:bad_sectors#ff0000:%s" % _("Bad Sectors"),
42 "VDEF:bad_sectors_cur=bad_sectors,LAST",
43 "VDEF:bad_sectors_max=bad_sectors,MAXIMUM",
44 "GPRINT:bad_sectors_cur:%12s\:" % _("Current") + " %9.2lf",
45 "GPRINT:bad_sectors_max:%12s\:" % _("Maximum") + " %9.2lf\\n",
49 def graph_title(self
):
50 _
= self
.locale
.translate
51 return _("Bad Sectors of %s") % self
.object.device_string
54 def graph_vertical_label(self
):
55 _
= self
.locale
.translate
56 return _("Pending/Relocated Sectors")
59 class GraphTemplateDiskBytes(base
.GraphTemplate
):
64 _
= self
.locale
.translate
67 "DEF:read_sectors=%(file)s:read_sectors:AVERAGE",
68 "DEF:write_sectors=%(file)s:write_sectors:AVERAGE",
70 "CDEF:read_bytes=read_sectors,512,*",
71 "CDEF:write_bytes=write_sectors,512,*",
73 "LINE1:read_bytes#ff0000:%-15s" % _("Read"),
74 "VDEF:read_cur=read_bytes,LAST",
75 "VDEF:read_min=read_bytes,MINIMUM",
76 "VDEF:read_max=read_bytes,MAXIMUM",
77 "VDEF:read_avg=read_bytes,AVERAGE",
78 "GPRINT:read_cur:%12s\:" % _("Current") + " %9.2lf",
79 "GPRINT:read_max:%12s\:" % _("Maximum") + " %9.2lf",
80 "GPRINT:read_min:%12s\:" % _("Minimum") + " %9.2lf",
81 "GPRINT:read_avg:%12s\:" % _("Average") + " %9.2lf\\n",
83 "LINE1:write_bytes#00ff00:%-15s" % _("Written"),
84 "VDEF:write_cur=write_bytes,LAST",
85 "VDEF:write_min=write_bytes,MINIMUM",
86 "VDEF:write_max=write_bytes,MAXIMUM",
87 "VDEF:write_avg=write_bytes,AVERAGE",
88 "GPRINT:write_cur:%12s\:" % _("Current") + " %9.2lf",
89 "GPRINT:write_max:%12s\:" % _("Maximum") + " %9.2lf",
90 "GPRINT:write_min:%12s\:" % _("Minimum") + " %9.2lf",
91 "GPRINT:write_avg:%12s\:" % _("Average") + " %9.2lf\\n",
99 def graph_title(self
):
100 _
= self
.locale
.translate
101 return _("Disk Utilisation of %s") % self
.object.device_string
104 def graph_vertical_label(self
):
105 _
= self
.locale
.translate
106 return _("Byte per Second")
109 class GraphTemplateDiskIoOps(base
.GraphTemplate
):
114 _
= self
.locale
.translate
117 "DEF:read_ios=%(file)s:read_ios:AVERAGE",
118 "DEF:write_ios=%(file)s:write_ios:AVERAGE",
120 "LINE1:read_ios#ff0000:%-15s" % _("Read"),
121 "VDEF:read_cur=read_ios,LAST",
122 "VDEF:read_min=read_ios,MINIMUM",
123 "VDEF:read_max=read_ios,MAXIMUM",
124 "VDEF:read_avg=read_ios,AVERAGE",
125 "GPRINT:read_cur:%12s\:" % _("Current") + " %6.2lf",
126 "GPRINT:read_max:%12s\:" % _("Maximum") + " %6.2lf",
127 "GPRINT:read_min:%12s\:" % _("Minimum") + " %6.2lf",
128 "GPRINT:read_avg:%12s\:" % _("Average") + " %6.2lf\\n",
130 "LINE1:write_ios#00ff00:%-15s" % _("Written"),
131 "VDEF:write_cur=write_ios,LAST",
132 "VDEF:write_min=write_ios,MINIMUM",
133 "VDEF:write_max=write_ios,MAXIMUM",
134 "VDEF:write_avg=write_ios,AVERAGE",
135 "GPRINT:write_cur:%12s\:" % _("Current") + " %6.2lf",
136 "GPRINT:write_max:%12s\:" % _("Maximum") + " %6.2lf",
137 "GPRINT:write_min:%12s\:" % _("Minimum") + " %6.2lf",
138 "GPRINT:write_avg:%12s\:" % _("Average") + " %6.2lf\\n",
146 def graph_title(self
):
147 _
= self
.locale
.translate
148 return _("Disk IO Operations of %s") % self
.object.device_string
151 def graph_vertical_label(self
):
152 _
= self
.locale
.translate
153 return _("Operations per Second")
156 class GraphTemplateDiskTemperature(base
.GraphTemplate
):
157 name
= "disk-temperature"
161 _
= self
.locale
.translate
164 "DEF:kelvin=%(file)s:temperature:AVERAGE",
165 "CDEF:celsius=kelvin,273.15,-",
167 "LINE2:celsius#ff0000:%s" % _("Temperature"),
168 "VDEF:temp_cur=celsius,LAST",
169 "VDEF:temp_min=celsius,MINIMUM",
170 "VDEF:temp_max=celsius,MAXIMUM",
171 "VDEF:temp_avg=celsius,AVERAGE",
172 "GPRINT:temp_cur:%12s\:" % _("Current") + " %3.2lf",
173 "GPRINT:temp_max:%12s\:" % _("Maximum") + " %3.2lf",
174 "GPRINT:temp_min:%12s\:" % _("Minimum") + " %3.2lf",
175 "GPRINT:temp_avg:%12s\:" % _("Average") + " %3.2lf\\n",
181 def graph_title(self
):
182 _
= self
.locale
.translate
183 return _("Disk Temperature of %s") % self
.object.device_string
186 def graph_vertical_label(self
):
187 _
= self
.locale
.translate
188 return _("° Celsius")
191 def rrd_graph_args(self
):
193 # Make the y-axis have a decimal
194 "--left-axis-format", "%3.1lf",
198 class DiskObject(base
.Object
):
200 "DS:awake:GAUGE:0:1",
201 "DS:read_ios:DERIVE:0:U",
202 "DS:read_sectors:DERIVE:0:U",
203 "DS:write_ios:DERIVE:0:U",
204 "DS:write_sectors:DERIVE:0:U",
205 "DS:bad_sectors:GAUGE:0:U",
206 "DS:temperature:GAUGE:U:U",
210 return "<%s %s (%s)>" % (self
.__class
__.__name
__, self
.sys_path
, self
.id)
212 def init(self
, device
):
213 self
.dev_path
= os
.path
.join("/dev", device
)
214 self
.sys_path
= os
.path
.join("/sys/block", device
)
216 self
.device
= _collecty
.BlockDevice(self
.dev_path
)
220 return "-".join((self
.device
.model
, self
.device
.serial
))
223 def device_string(self
):
224 return "%s (%s)" % (self
.device
.model
, self
.dev_path
)
227 stats
= self
.parse_stats()
231 stats
.get("read_ios"),
232 stats
.get("read_sectors"),
233 stats
.get("write_ios"),
234 stats
.get("write_sectors"),
235 self
.get_bad_sectors(),
236 self
.get_temperature(),
239 def parse_stats(self
):
241 https://www.kernel.org/doc/Documentation/block/stat.txt
243 Name units description
244 ---- ----- -----------
245 read I/Os requests number of read I/Os processed
246 read merges requests number of read I/Os merged with in-queue I/O
247 read sectors sectors number of sectors read
248 read ticks milliseconds total wait time for read requests
249 write I/Os requests number of write I/Os processed
250 write merges requests number of write I/Os merged with in-queue I/O
251 write sectors sectors number of sectors written
252 write ticks milliseconds total wait time for write requests
253 in_flight requests number of I/Os currently in flight
254 io_ticks milliseconds total time this block device has been active
255 time_in_queue milliseconds total wait time for all requests
257 stats_file
= os
.path
.join(self
.sys_path
, "stat")
259 with
open(stats_file
) as f
:
260 stats
= f
.read().split()
263 "read_ios" : stats
[0],
264 "read_merges" : stats
[1],
265 "read_sectors" : stats
[2],
266 "read_ticks" : stats
[3],
267 "write_ios" : stats
[4],
268 "write_merges" : stats
[5],
269 "write_sectors" : stats
[6],
270 "write_ticks" : stats
[7],
271 "in_flight" : stats
[8],
272 "io_ticks" : stats
[9],
273 "time_in_queue" : stats
[10],
276 def is_smart_supported(self
):
278 We can only query SMART data if SMART is supported by the disk
279 and when the disk is awake.
281 return self
.device
.is_smart_supported() and self
.device
.is_awake()
284 # If SMART is supported we can get the data from the disk
285 if self
.device
.is_smart_supported():
286 if self
.device
.is_awake():
291 # Otherwise we just assume that the disk is awake
294 def get_temperature(self
):
295 if not self
.is_smart_supported():
298 return self
.device
.get_temperature()
300 def get_bad_sectors(self
):
301 if not self
.is_smart_supported():
304 return self
.device
.get_bad_sectors()
307 class DiskPlugin(base
.Plugin
):
309 description
= "Disk Plugin"
312 GraphTemplateDiskBadSectors
,
313 GraphTemplateDiskBytes
,
314 GraphTemplateDiskIoOps
,
315 GraphTemplateDiskTemperature
,
318 block_device_patterns
= [
319 re
.compile(r
"(x?v|s)d[a-z]+"),
320 re
.compile(r
"mmcblk[0-9]+"),
325 for dev
in self
.find_block_devices():
327 yield DiskObject(self
, dev
)
331 def find_block_devices(self
):
332 for device
in os
.listdir("/sys/block"):
333 # Skip invalid device names
334 if not self
._valid
_block
_device
_name
(device
):
339 def _valid_block_device_name(self
, name
):
340 # Check if the given name matches any of the valid patterns.
341 for pattern
in self
.block_device_patterns
:
342 if pattern
.match(name
):