Migrate to Python 3
[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         rrd_graph = [
35                 "DEF:value_kelvin=%(file)s:value:AVERAGE",
36                 "DEF:critical_kelvin=%(file)s:critical:AVERAGE",
37                 "DEF:high_kelvin=%(file)s:high:AVERAGE",
38                 "DEF:low_kelvin=%(file)s:low:AVERAGE",
39
40                 # Convert everything to celsius
41                 "CDEF:value=value_kelvin,273.15,-",
42                 "CDEF:critical=critical_kelvin,273.15,-",
43                 "CDEF:high=high_kelvin,273.15,-",
44                 "CDEF:low=low_kelvin,273.15,-",
45
46                 # Change colour when the value gets above high
47                 "CDEF:value_high=value,high,GT,value,UNKN,IF",
48                 "CDEF:value_normal=value,high,GT,UNKN,value,IF",
49
50                 "VDEF:value_cur=value,LAST",
51                 "VDEF:value_avg=value,AVERAGE",
52                 "VDEF:value_max=value,MAXIMUM",
53                 "VDEF:value_min=value,MINIMUM",
54
55                 # Get data points for the threshold lines
56                 "VDEF:critical_line=critical,MINIMUM",
57                 "VDEF:low_line=low,MAXIMUM",
58
59                 # Draw the temperature value
60                 "LINE3:value_high#ff0000",
61                 "LINE2:value_normal#00ff00:%-15s" % _("Temperature"),
62
63                 # Draw the legend
64                 "GPRINT:value_cur:%%10.2lf °C\l",
65                 "GPRINT:value_avg:  %-15s %%6.2lf °C\l" % _("Average"),
66                 "GPRINT:value_max:  %-15s %%6.2lf °C\l" % _("Maximum"),
67                 "GPRINT:value_min:  %-15s %%6.2lf °C\l" % _("Minimum"),
68
69                 # Empty line
70                 "COMMENT: \\n",
71
72                 # Draw boundary lines
73                 "COMMENT:%s\:" % _("Temperature Thresholds"),
74                 "HRULE:critical_line#000000:%-15s" % _("Critical"),
75                 "GPRINT:critical_line:%%6.2lf °C\\r",
76                 "HRULE:low_line#0000ff:%-15s" % _("Low"),
77                 "GPRINT:low_line:%%6.2lf °C\\r",
78         ]
79
80         @property
81         def graph_title(self):
82                 return _("Temperature (%s)") % self.object.sensor.name
83
84         @property
85         def graph_vertical_label(self):
86                 return _("° Celsius")
87
88
89 class GraphTemplateSensorsProcessorTemperature(base.GraphTemplate):
90         name = "processor-temperature"
91
92         core_colours = {
93                 "core0" : "#ff000033",
94                 "core1" : "#0000ff33",
95                 "core2" : "#00ff0033",
96                 "core3" : "#0000ff33",
97         }
98
99         def get_temperature_sensors(self):
100                 return self.plugin.get_detected_sensor_objects("coretemp-*")
101
102         def get_object_table(self):
103                 objects_table = {}
104
105                 counter = 0
106                 for object in self.get_temperature_sensors():
107                         objects_table["core%s" % counter] = object
108                         counter += 1
109
110                 return objects_table
111
112         @property
113         def rrd_graph(self):
114                 rrd_graph = []
115
116                 cores = sorted(self.object_table.keys())
117
118                 for core in cores:
119                         i = {
120                                 "core" : core,
121                         }
122
123                         core_graph = [
124                                 "DEF:value_kelvin_%(core)s=%%(%(core)s)s:value:AVERAGE",
125                                 "DEF:critical_kelvin_%(core)s=%%(%(core)s)s:critical:AVERAGE",
126                                 "DEF:high_kelvin_%(core)s=%%(%(core)s)s:high:AVERAGE",
127
128                                 # Convert everything to celsius
129                                 "CDEF:value_%(core)s=value_kelvin_%(core)s,273.15,-",
130                                 "CDEF:critical_%(core)s=critical_kelvin_%(core)s,273.15,-",
131                                 "CDEF:high_%(core)s=high_kelvin_%(core)s,273.15,-",
132                         ]
133
134                         rrd_graph += [line % i for line in core_graph]
135
136                 all_core_values = ("value_%s" % c for c in cores)
137                 all_core_highs  = ("high_%s"  % c for c in cores)
138
139                 rrd_graph += [
140                         # Compute the temperature of the processor
141                         # by taking the average of all cores
142                         "CDEF:value=%s,%s,AVG" % (",".join(all_core_values), len(cores)),
143                         "CDEF:high=%s,MIN" % ",".join(all_core_highs),
144
145                         # Change colour when the value gets above high
146                         "CDEF:value_high=value,high,GT,value,UNKN,IF",
147                         "CDEF:value_normal=value,high,GT,UNKN,value,IF",
148
149                         "VDEF:value_avg=value,AVERAGE",
150                         "VDEF:value_max=value,MAXIMUM",
151                         "VDEF:value_min=value,MINIMUM",
152
153                         "LINE3:value_high#FF0000",
154                         "LINE3:value_normal#000000:%-15s\l" % _("Temperature"),
155
156                         "GPRINT:value_avg:    %-15s %%6.2lf °C\l" % _("Average"),
157                         "GPRINT:value_max:    %-15s %%6.2lf °C\l" % _("Maximum"),
158                         "GPRINT:value_min:    %-15s %%6.2lf °C\l" % _("Minimum"),
159                 ]
160
161                 for core in cores:
162                         object = self.object_table.get(core)
163
164                         i = {
165                                 "colour" : self.core_colours.get(core, "#000000"),
166                                 "core"   : core,
167                                 "label"  : object.sensor.label,
168                         }
169
170                         core_graph = [
171                                 # TODO these lines were supposed to be dashed, but that
172                                 # didn't really work here
173                                 "LINE2:value_%(core)s%(colour)s:%(label)-10s",
174                         ]
175
176                         rrd_graph += [line % i for line in core_graph]
177
178                 # Draw the critical line
179                 rrd_graph += [
180                         "VDEF:critical_line=critical_core0,MINIMUM",
181                         "HRULE:critical_line#000000:%-15s" % _("Critical"),
182                         "GPRINT:critical_line:%%6.2lf °C\\r",
183                 ]
184
185                 return rrd_graph
186
187         @property
188         def graph_title(self):
189                 return _("Processor")
190
191         @property
192         def graph_vertical_label(self):
193                 return _("Temperature")
194
195
196 class SensorBaseObject(base.Object):
197         def init(self, sensor):
198                 self.sensor = sensor
199
200         def __repr__(self):
201                 return "<%s %s (%s)>" % (self.__class__.__name__, self.sensor.name, self.sensor.label)
202
203         @property
204         def id(self):
205                 return "-".join((self.sensor.name, self.sensor.label))
206
207         @property
208         def type(self):
209                 return self.sensor.type
210
211
212 class SensorTemperatureObject(SensorBaseObject):
213         rrd_schema = [
214                 "DS:value:GAUGE:0:U",
215                 "DS:critical:GAUGE:0:U",
216                 "DS:low:GAUGE:0:U",
217                 "DS:high:GAUGE:0:U",
218         ]
219
220         def collect(self):
221                 assert self.type == "temperature"
222
223                 return (
224                         self.sensor.value,
225                         self.critical,
226                         self.low,
227                         self.high,
228                 )
229
230         @property
231         def critical(self):
232                 try:
233                         return self.sensor.critical
234                 except AttributeError:
235                         return "NaN"
236
237         @property
238         def low(self):
239                 try:
240                         return self.sensor.minimum
241                 except AttributeError:
242                         return "NaN"
243
244         @property
245         def high(self):
246                 try:
247                         return self.sensor.high
248                 except AttributeError:
249                         return "NaN"
250
251
252 class SensorVoltageObject(SensorBaseObject):
253         rrd_schema = [
254                 "DS:value:GAUGE:0:U",
255                 "DS:minimum:GAUGE:0:U",
256                 "DS:maximum:GAUGE:0:U",
257         ]
258
259         def collect(self):
260                 assert self.type == "voltage"
261
262                 return (
263                         self.sensor.value,
264                         self.minimum,
265                         self.maximum,
266                 )
267
268         @property
269         def minimum(self):
270                 try:
271                         return self.sensor.minimum
272                 except AttributeError:
273                         return "NaN"
274
275         @property
276         def maximum(self):
277                 try:
278                         return self.sensor.maximum
279                 except AttributeError:
280                         return "NaN"
281
282
283 class SensorFanObject(SensorBaseObject):
284         rrd_schema = [
285                 "DS:value:GAUGE:0:U",
286                 "DS:minimum:GAUGE:0:U",
287                 "DS:maximum:GAUGE:0:U",
288         ]
289
290         def collect(self):
291                 assert self.type == "fan"
292
293                 return (
294                         self.sensor.value,
295                         self.minimum,
296                         self.maximum,
297                 )
298
299         @property
300         def mimimum(self):
301                 try:
302                         return self.sensor.minimum
303                 except AttributeError:
304                         return "NaN"
305
306         @property
307         def maximum(self):
308                 try:
309                         return self.sensor.maximum
310                 except AttributeError:
311                         return "NaN"
312
313
314 class SensorsPlugin(base.Plugin):
315         name = "sensors"
316         description = "Sensors Plugin"
317
318         templates = [
319                 GraphTemplateSensorsProcessorTemperature,
320                 GraphTemplateSensorsTemperature,
321         ]
322
323         def init(self):
324                 # Initialise the sensors library.
325                 _collecty.sensors_init()
326
327         def __del__(self):
328                 _collecty.sensors_cleanup()
329
330         @property
331         def objects(self):
332                 return self.get_detected_sensor_objects()
333
334         def get_detected_sensor_objects(self, what=None):
335                 for sensor in _collecty.get_detected_sensors(what):
336                         if sensor.type == "temperature":
337                                 yield SensorTemperatureObject(self, sensor)
338
339                         elif sensor.type == "voltage":
340                                 yield SensorVoltageObject(self, sensor)
341
342                         elif sensor.type == "fan":
343                                 yield SensorFanObject(self, sensor)