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 *
29 from ..constants
import *
31 class GraphTemplateDiskBadSectors(base
.GraphTemplate
):
32 name
= "disk-bad-sectors"
36 _
= self
.locale
.translate
39 "COMMENT:%s" % EMPTY_LABEL
,
40 "COMMENT:%s" % (COLUMN
% _("Current")),
41 "COMMENT:%s\\j" % (COLUMN
% _("Maximum")),
43 "AREA:bad_sectors%s:%s" % (
44 transparency(COLOUR_CRITICAL
, AREA_OPACITY
),
45 LABEL
% _("Bad Sectors"),
47 "GPRINT:bad_sectors_cur:%s" % INTEGER
,
48 "GPRINT:bad_sectors_max:%s\\j" % INTEGER
,
51 "LINE:bad_sectors%s" % COLOUR_CRITICAL
,
55 def graph_title(self
):
56 _
= self
.locale
.translate
58 return _("Bad Sectors of %s") % self
.object.device_string
61 def graph_vertical_label(self
):
62 _
= self
.locale
.translate
64 return _("Pending/Relocated Sectors")
67 class GraphTemplateDiskBytes(base
.GraphTemplate
):
72 _
= self
.locale
.translate
75 "CDEF:read_bytes=read_sectors,512,*",
76 "CDEF:write_bytes=write_sectors,512,*",
78 "LINE1:read_bytes%s:%-15s" % (COLOUR_READ
, _("Read")),
79 "GPRINT:read_bytes_cur:%12s\:" % _("Current") + " %9.2lf",
80 "GPRINT:read_bytes_max:%12s\:" % _("Maximum") + " %9.2lf",
81 "GPRINT:read_bytes_min:%12s\:" % _("Minimum") + " %9.2lf",
82 "GPRINT:read_bytes_avg:%12s\:" % _("Average") + " %9.2lf",
84 "LINE1:write_bytes%s:%-15s" % (COLOUR_WRITE
, _("Written")),
85 "GPRINT:write_bytes_cur:%12s\:" % _("Current") + " %9.2lf",
86 "GPRINT:write_bytes_max:%12s\:" % _("Maximum") + " %9.2lf",
87 "GPRINT:write_bytes_min:%12s\:" % _("Minimum") + " %9.2lf",
88 "GPRINT:write_bytes_avg:%12s\:" % _("Average") + " %9.2lf",
96 def graph_title(self
):
97 _
= self
.locale
.translate
98 return _("Disk Utilisation of %s") % self
.object.device_string
101 def graph_vertical_label(self
):
102 _
= self
.locale
.translate
103 return _("Byte per Second")
106 class GraphTemplateDiskIoOps(base
.GraphTemplate
):
111 _
= self
.locale
.translate
114 "LINE1:read_ios%s:%-15s" % (COLOUR_READ
, _("Read")),
115 "GPRINT:read_ios_cur:%12s\:" % _("Current") + " %6.2lf",
116 "GPRINT:read_ios_max:%12s\:" % _("Maximum") + " %6.2lf",
117 "GPRINT:read_ios_min:%12s\:" % _("Minimum") + " %6.2lf",
118 "GPRINT:read_ios_avg:%12s\:" % _("Average") + " %6.2lf",
120 "LINE1:write_ios%s:%-15s" % (COLOUR_WRITE
, _("Written")),
121 "GPRINT:write_ios_cur:%12s\:" % _("Current") + " %6.2lf",
122 "GPRINT:write_ios_max:%12s\:" % _("Maximum") + " %6.2lf",
123 "GPRINT:write_ios_min:%12s\:" % _("Minimum") + " %6.2lf",
124 "GPRINT:write_ios_avg:%12s\:" % _("Average") + " %6.2lf",
132 def graph_title(self
):
133 _
= self
.locale
.translate
134 return _("Disk IO Operations of %s") % self
.object.device_string
137 def graph_vertical_label(self
):
138 _
= self
.locale
.translate
139 return _("Operations per Second")
142 class GraphTemplateDiskTemperature(base
.GraphTemplate
):
143 name
= "disk-temperature"
147 _
= self
.locale
.translate
150 "CDEF:celsius=temperature,273.15,-",
151 "VDEF:temp_cur=celsius,LAST",
152 "VDEF:temp_min=celsius,MINIMUM",
153 "VDEF:temp_max=celsius,MAXIMUM",
154 "VDEF:temp_avg=celsius,AVERAGE",
156 "LINE2:celsius%s:%s" % (PRIMARY
, _("Temperature")),
157 "GPRINT:temp_cur:%12s\:" % _("Current") + " %3.2lf",
158 "GPRINT:temp_max:%12s\:" % _("Maximum") + " %3.2lf",
159 "GPRINT:temp_min:%12s\:" % _("Minimum") + " %3.2lf",
160 "GPRINT:temp_avg:%12s\:" % _("Average") + " %3.2lf",
166 def graph_title(self
):
167 _
= self
.locale
.translate
168 return _("Disk Temperature of %s") % self
.object.device_string
171 def graph_vertical_label(self
):
172 _
= self
.locale
.translate
173 return _("° Celsius")
176 def rrd_graph_args(self
):
178 # Make the y-axis have a decimal
179 "--left-axis-format", "%3.1lf",
183 class DiskObject(base
.Object
):
185 "DS:awake:GAUGE:0:1",
186 "DS:read_ios:DERIVE:0:U",
187 "DS:read_sectors:DERIVE:0:U",
188 "DS:write_ios:DERIVE:0:U",
189 "DS:write_sectors:DERIVE:0:U",
190 "DS:bad_sectors:GAUGE:0:U",
191 "DS:temperature:GAUGE:U:U",
195 return "<%s %s (%s)>" % (self
.__class
__.__name
__, self
.sys_path
, self
.id)
197 def init(self
, device
):
198 self
.dev_path
= os
.path
.join("/dev", device
)
199 self
.sys_path
= os
.path
.join("/sys/block", device
)
201 self
.device
= _collecty
.BlockDevice(self
.dev_path
)
205 return "-".join((self
.device
.model
, self
.device
.serial
))
208 def device_string(self
):
209 return "%s (%s)" % (self
.device
.model
, self
.dev_path
)
212 stats
= self
.parse_stats()
216 stats
.get("read_ios"),
217 stats
.get("read_sectors"),
218 stats
.get("write_ios"),
219 stats
.get("write_sectors"),
220 self
.get_bad_sectors(),
221 self
.get_temperature(),
224 def parse_stats(self
):
226 https://www.kernel.org/doc/Documentation/block/stat.txt
228 Name units description
229 ---- ----- -----------
230 read I/Os requests number of read I/Os processed
231 read merges requests number of read I/Os merged with in-queue I/O
232 read sectors sectors number of sectors read
233 read ticks milliseconds total wait time for read requests
234 write I/Os requests number of write I/Os processed
235 write merges requests number of write I/Os merged with in-queue I/O
236 write sectors sectors number of sectors written
237 write ticks milliseconds total wait time for write requests
238 in_flight requests number of I/Os currently in flight
239 io_ticks milliseconds total time this block device has been active
240 time_in_queue milliseconds total wait time for all requests
242 stats_file
= os
.path
.join(self
.sys_path
, "stat")
244 with
open(stats_file
) as f
:
245 stats
= f
.read().split()
248 "read_ios" : stats
[0],
249 "read_merges" : stats
[1],
250 "read_sectors" : stats
[2],
251 "read_ticks" : stats
[3],
252 "write_ios" : stats
[4],
253 "write_merges" : stats
[5],
254 "write_sectors" : stats
[6],
255 "write_ticks" : stats
[7],
256 "in_flight" : stats
[8],
257 "io_ticks" : stats
[9],
258 "time_in_queue" : stats
[10],
261 def is_smart_supported(self
):
263 We can only query SMART data if SMART is supported by the disk
264 and when the disk is awake.
266 return self
.device
.is_smart_supported() and self
.device
.is_awake()
269 # If SMART is supported we can get the data from the disk
270 if self
.device
.is_smart_supported():
271 if self
.device
.is_awake():
276 # Otherwise we just assume that the disk is awake
279 def get_temperature(self
):
280 if not self
.is_smart_supported():
284 return self
.device
.get_temperature()
288 def get_bad_sectors(self
):
289 if not self
.is_smart_supported():
292 return self
.device
.get_bad_sectors()
295 class DiskPlugin(base
.Plugin
):
297 description
= "Disk Plugin"
300 GraphTemplateDiskBadSectors
,
301 GraphTemplateDiskBytes
,
302 GraphTemplateDiskIoOps
,
303 GraphTemplateDiskTemperature
,
306 block_device_patterns
= [
313 for dev
in self
.find_block_devices():
315 yield DiskObject(self
, dev
)
319 def find_block_devices(self
):
320 for device
in os
.listdir("/sys/block"):
321 # Skip invalid device names
322 if not self
._valid
_block
_device
_name
(device
):
327 def _valid_block_device_name(self
, name
):
328 # Check if the given name matches any of the valid patterns.
329 for pattern
in self
.block_device_patterns
:
330 if re
.match(pattern
, name
):