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 *
32 class GraphTemplateDiskBadSectors(base
.GraphTemplate
):
33 name
= "disk-bad-sectors"
38 "COMMENT:%s" % EMPTY_LABEL
,
39 "COMMENT:%s" % (COLUMN
% _("Current")),
40 "COMMENT:%s\\j" % (COLUMN
% _("Maximum")),
42 "AREA:bad_sectors%s:%s" % (
43 transparency(COLOUR_CRITICAL
, AREA_OPACITY
),
44 LABEL
% _("Bad Sectors"),
46 "GPRINT:bad_sectors_cur:%s" % INTEGER
,
47 "GPRINT:bad_sectors_max:%s\\j" % INTEGER
,
50 "LINE:bad_sectors%s" % COLOUR_CRITICAL
,
54 def graph_title(self
):
55 return _("Bad Sectors of %s") % self
.object.device_string
58 def graph_vertical_label(self
):
59 return _("Pending/Relocated Sectors")
62 class GraphTemplateDiskBytes(base
.GraphTemplate
):
68 "CDEF:read_bytes=read_sectors,512,*",
69 "CDEF:write_bytes=write_sectors,512,*",
71 "LINE1:read_bytes%s:%-15s" % (COLOUR_READ
, _("Read")),
72 "GPRINT:read_bytes_cur:%12s\:" % _("Current") + " %9.2lf",
73 "GPRINT:read_bytes_max:%12s\:" % _("Maximum") + " %9.2lf",
74 "GPRINT:read_bytes_min:%12s\:" % _("Minimum") + " %9.2lf",
75 "GPRINT:read_bytes_avg:%12s\:" % _("Average") + " %9.2lf",
77 "LINE1:write_bytes%s:%-15s" % (COLOUR_WRITE
, _("Written")),
78 "GPRINT:write_bytes_cur:%12s\:" % _("Current") + " %9.2lf",
79 "GPRINT:write_bytes_max:%12s\:" % _("Maximum") + " %9.2lf",
80 "GPRINT:write_bytes_min:%12s\:" % _("Minimum") + " %9.2lf",
81 "GPRINT:write_bytes_avg:%12s\:" % _("Average") + " %9.2lf",
89 def graph_title(self
):
90 return _("Disk Utilisation of %s") % self
.object.device_string
93 def graph_vertical_label(self
):
94 return _("Byte per Second")
97 class GraphTemplateDiskIoOps(base
.GraphTemplate
):
103 "LINE1:read_ios%s:%-15s" % (COLOUR_READ
, _("Read")),
104 "GPRINT:read_ios_cur:%12s\:" % _("Current") + " %6.2lf",
105 "GPRINT:read_ios_max:%12s\:" % _("Maximum") + " %6.2lf",
106 "GPRINT:read_ios_min:%12s\:" % _("Minimum") + " %6.2lf",
107 "GPRINT:read_ios_avg:%12s\:" % _("Average") + " %6.2lf",
109 "LINE1:write_ios%s:%-15s" % (COLOUR_WRITE
, _("Written")),
110 "GPRINT:write_ios_cur:%12s\:" % _("Current") + " %6.2lf",
111 "GPRINT:write_ios_max:%12s\:" % _("Maximum") + " %6.2lf",
112 "GPRINT:write_ios_min:%12s\:" % _("Minimum") + " %6.2lf",
113 "GPRINT:write_ios_avg:%12s\:" % _("Average") + " %6.2lf",
121 def graph_title(self
):
122 return _("Disk IO Operations of %s") % self
.object.device_string
125 def graph_vertical_label(self
):
126 return _("Operations per Second")
129 class GraphTemplateDiskTemperature(base
.GraphTemplate
):
130 name
= "disk-temperature"
135 "CDEF:celsius=temperature,273.15,-",
136 "VDEF:temp_cur=celsius,LAST",
137 "VDEF:temp_min=celsius,MINIMUM",
138 "VDEF:temp_max=celsius,MAXIMUM",
139 "VDEF:temp_avg=celsius,AVERAGE",
141 "LINE2:celsius%s:%s" % (PRIMARY
, _("Temperature")),
142 "GPRINT:temp_cur:%12s\:" % _("Current") + " %3.2lf",
143 "GPRINT:temp_max:%12s\:" % _("Maximum") + " %3.2lf",
144 "GPRINT:temp_min:%12s\:" % _("Minimum") + " %3.2lf",
145 "GPRINT:temp_avg:%12s\:" % _("Average") + " %3.2lf",
151 def graph_title(self
):
152 return _("Disk Temperature of %s") % self
.object.device_string
155 def graph_vertical_label(self
):
156 return _("° Celsius")
159 def rrd_graph_args(self
):
161 # Make the y-axis have a decimal
162 "--left-axis-format", "%3.1lf",
166 class DiskObject(base
.Object
):
168 "DS:awake:GAUGE:0:1",
169 "DS:read_ios:DERIVE:0:U",
170 "DS:read_sectors:DERIVE:0:U",
171 "DS:write_ios:DERIVE:0:U",
172 "DS:write_sectors:DERIVE:0:U",
173 "DS:bad_sectors:GAUGE:0:U",
174 "DS:temperature:GAUGE:U:U",
178 return "<%s %s (%s)>" % (self
.__class
__.__name
__, self
.sys_path
, self
.id)
180 def init(self
, device
):
181 self
.dev_path
= os
.path
.join("/dev", device
)
182 self
.sys_path
= os
.path
.join("/sys/block", device
)
184 self
.device
= _collecty
.BlockDevice(self
.dev_path
)
188 return "-".join((self
.device
.model
, self
.device
.serial
))
191 def device_string(self
):
192 return "%s (%s)" % (self
.device
.model
, self
.dev_path
)
195 stats
= self
.parse_stats()
199 stats
.get("read_ios"),
200 stats
.get("read_sectors"),
201 stats
.get("write_ios"),
202 stats
.get("write_sectors"),
203 self
.get_bad_sectors(),
204 self
.get_temperature(),
207 def parse_stats(self
):
209 https://www.kernel.org/doc/Documentation/block/stat.txt
211 Name units description
212 ---- ----- -----------
213 read I/Os requests number of read I/Os processed
214 read merges requests number of read I/Os merged with in-queue I/O
215 read sectors sectors number of sectors read
216 read ticks milliseconds total wait time for read requests
217 write I/Os requests number of write I/Os processed
218 write merges requests number of write I/Os merged with in-queue I/O
219 write sectors sectors number of sectors written
220 write ticks milliseconds total wait time for write requests
221 in_flight requests number of I/Os currently in flight
222 io_ticks milliseconds total time this block device has been active
223 time_in_queue milliseconds total wait time for all requests
225 stats_file
= os
.path
.join(self
.sys_path
, "stat")
227 with
open(stats_file
) as f
:
228 stats
= f
.read().split()
231 "read_ios" : stats
[0],
232 "read_merges" : stats
[1],
233 "read_sectors" : stats
[2],
234 "read_ticks" : stats
[3],
235 "write_ios" : stats
[4],
236 "write_merges" : stats
[5],
237 "write_sectors" : stats
[6],
238 "write_ticks" : stats
[7],
239 "in_flight" : stats
[8],
240 "io_ticks" : stats
[9],
241 "time_in_queue" : stats
[10],
244 def is_smart_supported(self
):
246 We can only query SMART data if SMART is supported by the disk
247 and when the disk is awake.
249 return self
.device
.is_smart_supported() and self
.device
.is_awake()
252 # If SMART is supported we can get the data from the disk
253 if self
.device
.is_smart_supported():
254 if self
.device
.is_awake():
259 # Otherwise we just assume that the disk is awake
262 def get_temperature(self
):
263 if not self
.is_smart_supported():
267 return self
.device
.get_temperature()
271 def get_bad_sectors(self
):
272 if not self
.is_smart_supported():
275 return self
.device
.get_bad_sectors()
278 class DiskPlugin(base
.Plugin
):
280 description
= "Disk Plugin"
283 GraphTemplateDiskBadSectors
,
284 GraphTemplateDiskBytes
,
285 GraphTemplateDiskIoOps
,
286 GraphTemplateDiskTemperature
,
289 block_device_patterns
= [
296 for dev
in self
.find_block_devices():
298 yield DiskObject(self
, dev
)
302 def find_block_devices(self
):
303 for device
in os
.listdir("/sys/block"):
304 # Skip invalid device names
305 if not self
._valid
_block
_device
_name
(device
):
310 def _valid_block_device_name(self
, name
):
311 # Check if the given name matches any of the valid patterns.
312 for pattern
in self
.block_device_patterns
:
313 if re
.match(pattern
, name
):