]> git.ipfire.org Git - collecty.git/blob - src/collecty/plugins/sensors.py
f81f7dea53e6b07e8aafe3ee701b9e99207e66f4
[collecty.git] / src / collecty / plugins / sensors.py
1 #!/usr/bin/python3
2 # encoding: utf-8
3 ###############################################################################
4 # #
5 # collecty - A system statistics collection daemon for IPFire #
6 # Copyright (C) 2015 IPFire development team #
7 # #
8 # This program is free software: you can redistribute it and/or modify #
9 # it under the terms of the GNU General Public License as published by #
10 # the Free Software Foundation, either version 3 of the License, or #
11 # (at your option) any later version. #
12 # #
13 # This program is distributed in the hope that it will be useful, #
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
16 # GNU General Public License for more details. #
17 # #
18 # You should have received a copy of the GNU General Public License #
19 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
20 # #
21 ###############################################################################
22
23 from collecty import _collecty
24 import os
25 import re
26
27 from . import base
28
29 from ..i18n import _
30
31 class GraphTemplateSensorsTemperature(base.GraphTemplate):
32 name = "sensors-temperature"
33
34 @property
35 def rrd_graph(self):
36 _ = self.locale.translate
37
38 return [
39 "DEF:value_kelvin=%(file)s:value:AVERAGE",
40 "DEF:critical_kelvin=%(file)s:critical:AVERAGE",
41 "DEF:high_kelvin=%(file)s:high:AVERAGE",
42 "DEF:low_kelvin=%(file)s:low:AVERAGE",
43
44 # Convert everything to celsius
45 "CDEF:value=value_kelvin,273.15,-",
46 "CDEF:critical=critical_kelvin,273.15,-",
47 "CDEF:high=high_kelvin,273.15,-",
48 "CDEF:low=low_kelvin,273.15,-",
49
50 # Change colour when the value gets above high
51 "CDEF:value_high=value,high,GT,value,UNKN,IF",
52 "CDEF:value_normal=value,high,GT,UNKN,value,IF",
53
54 "VDEF:value_cur=value,LAST",
55 "VDEF:value_avg=value,AVERAGE",
56 "VDEF:value_max=value,MAXIMUM",
57 "VDEF:value_min=value,MINIMUM",
58
59 # Get data points for the threshold lines
60 "VDEF:critical_line=critical,MINIMUM",
61 "VDEF:low_line=low,MAXIMUM",
62
63 # Draw the temperature value
64 "LINE3:value_high#ff0000",
65 "LINE2:value_normal#00ff00:%-15s" % _("Temperature"),
66
67 # Draw the legend
68 "GPRINT:value_cur:%%10.2lf °C\l",
69 "GPRINT:value_avg: %-15s %%6.2lf °C\l" % _("Average"),
70 "GPRINT:value_max: %-15s %%6.2lf °C\l" % _("Maximum"),
71 "GPRINT:value_min: %-15s %%6.2lf °C\l" % _("Minimum"),
72
73 # Empty line
74 "COMMENT: \\n",
75
76 # Draw boundary lines
77 "COMMENT:%s\:" % _("Temperature Thresholds"),
78 "HRULE:critical_line#000000:%-15s" % _("Critical"),
79 "GPRINT:critical_line:%%6.2lf °C\\r",
80 "HRULE:low_line#0000ff:%-15s" % _("Low"),
81 "GPRINT:low_line:%%6.2lf °C\\r",
82 ]
83
84 @property
85 def graph_title(self):
86 _ = self.locale.translate
87 return _("Temperature (%s)") % self.object.sensor.name
88
89 @property
90 def graph_vertical_label(self):
91 _ = self.locale.translate
92 return _("° Celsius")
93
94
95 class GraphTemplateSensorsProcessorTemperature(base.GraphTemplate):
96 name = "processor-temperature"
97
98 core_colours = {
99 "core0" : "#ff000033",
100 "core1" : "#0000ff33",
101 "core2" : "#00ff0033",
102 "core3" : "#0000ff33",
103 }
104
105 def get_temperature_sensors(self):
106 return self.plugin.get_detected_sensor_objects("coretemp-*")
107
108 def get_object_table(self):
109 objects_table = {}
110
111 counter = 0
112 for object in self.get_temperature_sensors():
113 objects_table["core%s" % counter] = object
114 counter += 1
115
116 return objects_table
117
118 @property
119 def rrd_graph(self):
120 _ = self.locale.translate
121 rrd_graph = []
122
123 cores = sorted(self.object_table.keys())
124
125 for core in cores:
126 i = {
127 "core" : core,
128 }
129
130 core_graph = [
131 "DEF:value_kelvin_%(core)s=%%(%(core)s)s:value:AVERAGE",
132 "DEF:critical_kelvin_%(core)s=%%(%(core)s)s:critical:AVERAGE",
133 "DEF:high_kelvin_%(core)s=%%(%(core)s)s:high:AVERAGE",
134
135 # Convert everything to celsius
136 "CDEF:value_%(core)s=value_kelvin_%(core)s,273.15,-",
137 "CDEF:critical_%(core)s=critical_kelvin_%(core)s,273.15,-",
138 "CDEF:high_%(core)s=high_kelvin_%(core)s,273.15,-",
139 ]
140
141 rrd_graph += [line % i for line in core_graph]
142
143 all_core_values = ("value_%s" % c for c in cores)
144 all_core_highs = ("high_%s" % c for c in cores)
145
146 rrd_graph += [
147 # Compute the temperature of the processor
148 # by taking the average of all cores
149 "CDEF:value=%s,%s,AVG" % (",".join(all_core_values), len(cores)),
150 "CDEF:high=%s,MIN" % ",".join(all_core_highs),
151
152 # Change colour when the value gets above high
153 "CDEF:value_high=value,high,GT,value,UNKN,IF",
154 "CDEF:value_normal=value,high,GT,UNKN,value,IF",
155
156 "VDEF:value_avg=value,AVERAGE",
157 "VDEF:value_max=value,MAXIMUM",
158 "VDEF:value_min=value,MINIMUM",
159
160 "LINE3:value_high#FF0000",
161 "LINE3:value_normal#000000:%-15s\l" % _("Temperature"),
162
163 "GPRINT:value_avg: %-15s %%6.2lf °C\l" % _("Average"),
164 "GPRINT:value_max: %-15s %%6.2lf °C\l" % _("Maximum"),
165 "GPRINT:value_min: %-15s %%6.2lf °C\l" % _("Minimum"),
166 ]
167
168 for core in cores:
169 object = self.object_table.get(core)
170
171 i = {
172 "colour" : self.core_colours.get(core, "#000000"),
173 "core" : core,
174 "label" : object.sensor.label,
175 }
176
177 core_graph = [
178 # TODO these lines were supposed to be dashed, but that
179 # didn't really work here
180 "LINE2:value_%(core)s%(colour)s:%(label)-10s",
181 ]
182
183 rrd_graph += [line % i for line in core_graph]
184
185 # Draw the critical line
186 rrd_graph += [
187 "VDEF:critical_line=critical_core0,MINIMUM",
188 "HRULE:critical_line#000000:%-15s" % _("Critical"),
189 "GPRINT:critical_line:%%6.2lf °C\\r",
190 ]
191
192 return rrd_graph
193
194 @property
195 def graph_title(self):
196 _ = self.locale.translate
197 return _("Processor")
198
199 @property
200 def graph_vertical_label(self):
201 _ = self.locale.translate
202 return _("Temperature")
203
204
205 class SensorBaseObject(base.Object):
206 def init(self, sensor):
207 self.sensor = sensor
208
209 def __repr__(self):
210 return "<%s %s (%s)>" % (self.__class__.__name__, self.sensor.name, self.sensor.label)
211
212 @property
213 def id(self):
214 return "-".join((self.sensor.name, self.sensor.label))
215
216 @property
217 def type(self):
218 return self.sensor.type
219
220
221 class SensorTemperatureObject(SensorBaseObject):
222 rrd_schema = [
223 "DS:value:GAUGE:0:U",
224 "DS:critical:GAUGE:0:U",
225 "DS:low:GAUGE:0:U",
226 "DS:high:GAUGE:0:U",
227 ]
228
229 def collect(self):
230 assert self.type == "temperature"
231
232 return (
233 self.sensor.value,
234 self.critical,
235 self.low,
236 self.high,
237 )
238
239 @property
240 def critical(self):
241 try:
242 return self.sensor.critical
243 except AttributeError:
244 return "NaN"
245
246 @property
247 def low(self):
248 try:
249 return self.sensor.minimum
250 except AttributeError:
251 return "NaN"
252
253 @property
254 def high(self):
255 try:
256 return self.sensor.high
257 except AttributeError:
258 return "NaN"
259
260
261 class SensorVoltageObject(SensorBaseObject):
262 rrd_schema = [
263 "DS:value:GAUGE:0:U",
264 "DS:minimum:GAUGE:0:U",
265 "DS:maximum:GAUGE:0:U",
266 ]
267
268 def collect(self):
269 assert self.type == "voltage"
270
271 return (
272 self.sensor.value,
273 self.minimum,
274 self.maximum,
275 )
276
277 @property
278 def minimum(self):
279 try:
280 return self.sensor.minimum
281 except AttributeError:
282 return "NaN"
283
284 @property
285 def maximum(self):
286 try:
287 return self.sensor.maximum
288 except AttributeError:
289 return "NaN"
290
291
292 class SensorFanObject(SensorBaseObject):
293 rrd_schema = [
294 "DS:value:GAUGE:0:U",
295 "DS:minimum:GAUGE:0:U",
296 "DS:maximum:GAUGE:0:U",
297 ]
298
299 def collect(self):
300 assert self.type == "fan"
301
302 return (
303 self.sensor.value,
304 self.minimum,
305 self.maximum,
306 )
307
308 @property
309 def minimum(self):
310 try:
311 return self.sensor.minimum
312 except AttributeError:
313 return "NaN"
314
315 @property
316 def maximum(self):
317 try:
318 return self.sensor.maximum
319 except AttributeError:
320 return "NaN"
321
322
323 class SensorsPlugin(base.Plugin):
324 name = "sensors"
325 description = "Sensors Plugin"
326
327 templates = [
328 GraphTemplateSensorsProcessorTemperature,
329 GraphTemplateSensorsTemperature,
330 ]
331
332 def init(self):
333 # Initialise the sensors library.
334 _collecty.sensors_init()
335
336 def __del__(self):
337 _collecty.sensors_cleanup()
338
339 @property
340 def objects(self):
341 return self.get_detected_sensor_objects()
342
343 def get_detected_sensor_objects(self, what=None):
344 for sensor in _collecty.get_detected_sensors(what):
345 if sensor.type == "temperature":
346 yield SensorTemperatureObject(self, sensor)
347
348 elif sensor.type == "voltage":
349 yield SensorVoltageObject(self, sensor)
350
351 elif sensor.type == "fan":
352 yield SensorFanObject(self, sensor)