]> git.ipfire.org Git - oddments/collecty.git/blob - src/collecty/plugins/sensors.py
684a03fffaaca6b23e499bd60ed1afe30e8bc492
[oddments/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 import os
24 import re
25
26 from .. import _collecty
27 from . import base
28
29 class GraphTemplateSensorsTemperature(base.GraphTemplate):
30 name = "sensors-temperature"
31
32 @property
33 def rrd_graph(self):
34 _ = self.locale.translate
35
36 return [
37 # Convert everything to Celsius
38 "CDEF:value_c=value,273.15,-",
39 "CDEF:critical_c=critical,273.15,-",
40 "CDEF:high_c=high,273.15,-",
41 "CDEF:low_c=low,273.15,-",
42
43 # Change colour when the value gets above high
44 "CDEF:value_c_high=value_c,high_c,GT,value_c,UNKN,IF",
45 "CDEF:value_c_normal=value_c,high_c,GT,UNKN,value_c,IF",
46
47 # Get data points for the threshold lines
48 "VDEF:critical_c_line=critical_c,MINIMUM",
49 "VDEF:low_c_line=low_c,MAXIMUM",
50
51 # Draw the temperature value
52 "LINE3:value_c_high#ff0000",
53 "LINE2:value_c_normal#00ff00:%-15s" % _("Temperature"),
54
55 # Draw the legend
56 "GPRINT:value_c_cur:%10.2lf °C\l",
57 "GPRINT:value_c_avg: %-15s %%6.2lf °C" % _("Average"),
58 "GPRINT:value_c_max: %-15s %%6.2lf °C" % _("Maximum"),
59 "GPRINT:value_c_min: %-15s %%6.2lf °C" % _("Minimum"),
60
61 # Empty line
62 "COMMENT: \\n",
63
64 # Draw boundary lines
65 "COMMENT:%s\:" % _("Temperature Thresholds"),
66 "HRULE:critical_c_line#000000:%-15s" % _("Critical"),
67 "GPRINT:critical_c_line:%6.2lf °C\\r",
68 "HRULE:low_c_line#0000ff:%-15s" % _("Low"),
69 "GPRINT:low_c_line:%6.2lf °C\\r",
70 ]
71
72 @property
73 def graph_title(self):
74 _ = self.locale.translate
75 return _("Temperature (%s)") % self.object.sensor.name
76
77 @property
78 def graph_vertical_label(self):
79 _ = self.locale.translate
80 return _("° Celsius")
81
82
83 class GraphTemplateSensorsProcessorTemperature(base.GraphTemplate):
84 name = "processor-temperature"
85
86 core_colours = [
87 "#ff000033",
88 "#0000ff33",
89 "#00ff0033",
90 "#0000ff33",
91 ]
92
93 def get_temperature_sensors(self):
94 # Use the coretemp module if available
95 sensors = self.plugin.get_detected_sensor_objects("coretemp-*")
96
97 # Fall back to the ACPI sensor
98 if not sensors:
99 sensors = self.plugin.get_detected_sensor_objects("acpitz-virtual-*")
100
101 return sensors
102
103 def get_objects(self, *args, **kwargs):
104 sensors = self.get_temperature_sensors()
105
106 return list(sensors)
107
108 @property
109 def rrd_graph(self):
110 _ = self.locale.translate
111 rrd_graph = []
112
113 counter = 0
114 ids = []
115
116 for core in self.objects:
117 id = "core%s" % counter
118 ids.append(id)
119 counter += 1
120
121 rrd_graph += core.make_rrd_defs(id) + [
122 # Convert everything to celsius
123 "CDEF:%s_value_c=%s_value,273.15,-" % (id, id),
124 "CDEF:%s_critical_c=%s_critical,273.15,-" % (id, id),
125 "CDEF:%s_high_c=%s_high,273.15,-" % (id, id),
126 ]
127
128 # Compute the temperature of the processor
129 # by taking the average of all cores
130 all_core_values = ("%s_value_c" % id for id in ids)
131 rrd_graph += [
132 "CDEF:all_value_c=%s,%s,AVG" % (",".join(all_core_values), len(ids)),
133 ]
134
135 # Get the high threshold of the first core
136 # (assuming that all cores have the same threshold)
137 for id in ids:
138 rrd_graph.append("CDEF:all_high_c=%s_high_c" % id)
139 break
140
141 rrd_graph += [
142 # Change colour when the value gets above high
143 "CDEF:all_value_c_high=all_value_c,all_high_c,GT,all_value_c,UNKN,IF",
144 "CDEF:all_value_c_normal=all_value_c,all_high_c,GT,UNKN,all_value_c,IF",
145
146 "LINE2:all_value_c_high#FF0000",
147 "LINE2:all_value_c_normal#000000:%-15s" % _("Temperature"),
148
149 "GPRINT:all_value_c_avg: %-15s %%6.2lf °C" % _("Average"),
150 "GPRINT:all_value_c_max: %-15s %%6.2lf °C" % _("Maximum"),
151 "GPRINT:all_value_c_min: %-15s %%6.2lf °C" % _("Minimum"),
152 ]
153
154 for id, core, colour in zip(ids, self.objects, self.core_colours):
155 rrd_graph += [
156 # TODO these lines were supposed to be dashed, but that
157 # didn't really work here
158 "LINE1:%s_value_c%s:%-10s" % (id, colour, core.sensor.label),
159 ]
160
161 # Draw the critical line
162 for id in ids:
163 rrd_graph += [
164 "HRULE:%s_critical_c_min#000000:%-15s" % (id, _("Critical")),
165 "GPRINT:%s_critical_c_min:%%6.2lf °C\\r" % id,
166 ]
167 break
168
169 return rrd_graph
170
171 @property
172 def graph_title(self):
173 _ = self.locale.translate
174 return _("Processor")
175
176 @property
177 def graph_vertical_label(self):
178 _ = self.locale.translate
179 return _("Temperature")
180
181
182 class SensorBaseObject(base.Object):
183 def init(self, sensor):
184 self.sensor = sensor
185
186 def __repr__(self):
187 return "<%s %s (%s)>" % (self.__class__.__name__, self.sensor.name, self.sensor.label)
188
189 @property
190 def id(self):
191 return "-".join((self.sensor.name, self.sensor.label))
192
193 @property
194 def type(self):
195 return self.sensor.type
196
197
198 class SensorTemperatureObject(SensorBaseObject):
199 rrd_schema = [
200 "DS:value:GAUGE:0:U",
201 "DS:critical:GAUGE:0:U",
202 "DS:low:GAUGE:0:U",
203 "DS:high:GAUGE:0:U",
204 ]
205
206 def collect(self):
207 assert self.type == "temperature"
208
209 return (
210 self.sensor.value,
211 self.critical,
212 self.low,
213 self.high,
214 )
215
216 @property
217 def critical(self):
218 try:
219 return self.sensor.critical
220 except AttributeError:
221 return "NaN"
222
223 @property
224 def low(self):
225 try:
226 return self.sensor.minimum
227 except AttributeError:
228 return "NaN"
229
230 @property
231 def high(self):
232 try:
233 return self.sensor.high
234 except AttributeError:
235 return "NaN"
236
237
238 class SensorVoltageObject(SensorBaseObject):
239 rrd_schema = [
240 "DS:value:GAUGE:0:U",
241 "DS:minimum:GAUGE:0:U",
242 "DS:maximum:GAUGE:0:U",
243 ]
244
245 def collect(self):
246 assert self.type == "voltage"
247
248 return (
249 self.sensor.value,
250 self.minimum,
251 self.maximum,
252 )
253
254 @property
255 def minimum(self):
256 try:
257 return self.sensor.minimum
258 except AttributeError:
259 return "NaN"
260
261 @property
262 def maximum(self):
263 try:
264 return self.sensor.maximum
265 except AttributeError:
266 return "NaN"
267
268
269 class SensorFanObject(SensorBaseObject):
270 rrd_schema = [
271 "DS:value:GAUGE:0:U",
272 "DS:minimum:GAUGE:0:U",
273 "DS:maximum:GAUGE:0:U",
274 ]
275
276 def collect(self):
277 assert self.type == "fan"
278
279 return (
280 self.sensor.value,
281 self.minimum,
282 self.maximum,
283 )
284
285 @property
286 def minimum(self):
287 try:
288 return self.sensor.minimum
289 except AttributeError:
290 return "NaN"
291
292 @property
293 def maximum(self):
294 try:
295 return self.sensor.maximum
296 except AttributeError:
297 return "NaN"
298
299
300 class SensorsPlugin(base.Plugin):
301 name = "sensors"
302 description = "Sensors Plugin"
303
304 templates = [
305 GraphTemplateSensorsProcessorTemperature,
306 GraphTemplateSensorsTemperature,
307 ]
308
309 def init(self):
310 # Initialise the sensors library.
311 _collecty.sensors_init()
312
313 def __del__(self):
314 _collecty.sensors_cleanup()
315
316 @property
317 def objects(self):
318 return self.get_detected_sensor_objects()
319
320 def get_detected_sensor_objects(self, what=None):
321 for sensor in _collecty.get_detected_sensors(what):
322 if sensor.type == "temperature":
323 yield SensorTemperatureObject(self, sensor)
324
325 elif sensor.type == "voltage":
326 yield SensorVoltageObject(self, sensor)
327
328 elif sensor.type == "fan":
329 yield SensorFanObject(self, sensor)