1 | # -*- coding: utf-8 -*- |
||
2 | # |
||
3 | # This file is part of Glances. |
||
4 | # |
||
5 | # Copyright (C) 2015 Nicolargo <[email protected]> |
||
6 | # |
||
7 | # Glances is free software; you can redistribute it and/or modify |
||
8 | # it under the terms of the GNU Lesser 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. |
||
11 | # |
||
12 | # Glances 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 Lesser General Public License for more details. |
||
16 | # |
||
17 | # You should have received a copy of the GNU Lesser General Public License |
||
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
19 | |||
20 | """Sensors plugin.""" |
||
21 | |||
22 | # Sensors library (optional; Linux-only) |
||
23 | # Py3Sensors: https://bitbucket.org/gleb_zhulik/py3sensors |
||
24 | try: |
||
25 | import sensors |
||
26 | except ImportError: |
||
27 | pass |
||
28 | |||
29 | from glances.logger import logger |
||
30 | from glances.plugins.glances_batpercent import Plugin as BatPercentPlugin |
||
31 | from glances.plugins.glances_hddtemp import Plugin as HddTempPlugin |
||
32 | from glances.plugins.glances_plugin import GlancesPlugin |
||
33 | |||
34 | SENSOR_TEMP_UNIT = 'C' |
||
35 | SENSOR_FAN_UNIT = 'rpm' |
||
36 | |||
37 | |||
38 | def to_fahrenheit(celsius): |
||
39 | """Convert Celsius to Fahrenheit.""" |
||
40 | return celsius * 1.8 + 32 |
||
41 | |||
42 | |||
43 | class Plugin(GlancesPlugin): |
||
44 | |||
45 | """Glances sensors plugin. |
||
46 | |||
47 | The stats list includes both sensors and hard disks stats, if any. |
||
48 | The sensors are already grouped by chip type and then sorted by name. |
||
49 | The hard disks are already sorted by name. |
||
50 | """ |
||
51 | |||
52 | def __init__(self, args=None): |
||
53 | """Init the plugin.""" |
||
54 | super(Plugin, self).__init__(args=args) |
||
55 | |||
56 | # Init the sensor class |
||
57 | self.glancesgrabsensors = GlancesGrabSensors() |
||
58 | |||
59 | # Instance for the HDDTemp Plugin in order to display the hard disks |
||
60 | # temperatures |
||
61 | self.hddtemp_plugin = HddTempPlugin(args=args) |
||
62 | |||
63 | # Instance for the BatPercent in order to display the batteries |
||
64 | # capacities |
||
65 | self.batpercent_plugin = BatPercentPlugin(args=args) |
||
66 | |||
67 | # We want to display the stat in the curse interface |
||
68 | self.display_curse = True |
||
69 | |||
70 | # Init the stats |
||
71 | self.reset() |
||
72 | |||
73 | def get_key(self): |
||
74 | """Return the key of the list.""" |
||
75 | return 'label' |
||
76 | |||
77 | def reset(self): |
||
78 | """Reset/init the stats.""" |
||
79 | self.stats = [] |
||
80 | |||
81 | @GlancesPlugin._log_result_decorator |
||
82 | def update(self): |
||
83 | """Update sensors stats using the input method.""" |
||
84 | # Reset the stats |
||
85 | self.reset() |
||
86 | |||
87 | if self.input_method == 'local': |
||
88 | # Update stats using the dedicated lib |
||
89 | self.stats = [] |
||
90 | # Get the temperature |
||
91 | try: |
||
92 | temperature = self.__set_type(self.glancesgrabsensors.get('temperature_core'), |
||
93 | 'temperature_core') |
||
94 | except Exception as e: |
||
95 | logger.error("Cannot grab sensors temperatures (%s)" % e) |
||
96 | else: |
||
97 | # Append temperature |
||
98 | self.stats.extend(temperature) |
||
99 | # Get the FAN speed |
||
100 | try: |
||
101 | fan_speed = self.__set_type(self.glancesgrabsensors.get('fan_speed'), |
||
102 | 'fan_speed') |
||
103 | except Exception as e: |
||
104 | logger.error("Cannot grab FAN speed (%s)" % e) |
||
105 | else: |
||
106 | # Append FAN speed |
||
107 | self.stats.extend(fan_speed) |
||
108 | # Update HDDtemp stats |
||
109 | try: |
||
110 | hddtemp = self.__set_type(self.hddtemp_plugin.update(), |
||
111 | 'temperature_hdd') |
||
112 | except Exception as e: |
||
113 | logger.error("Cannot grab HDD temperature (%s)" % e) |
||
114 | else: |
||
115 | # Append HDD temperature |
||
116 | self.stats.extend(hddtemp) |
||
117 | # Update batteries stats |
||
118 | try: |
||
119 | batpercent = self.__set_type(self.batpercent_plugin.update(), |
||
120 | 'battery') |
||
121 | except Exception as e: |
||
122 | logger.error("Cannot grab battery percent (%s)" % e) |
||
123 | else: |
||
124 | # Append Batteries % |
||
125 | self.stats.extend(batpercent) |
||
126 | |||
127 | elif self.input_method == 'snmp': |
||
128 | # Update stats using SNMP |
||
129 | # No standard: |
||
130 | # http://www.net-snmp.org/wiki/index.php/Net-SNMP_and_lm-sensors_on_Ubuntu_10.04 |
||
131 | |||
132 | pass |
||
133 | |||
134 | # Update the view |
||
135 | self.update_views() |
||
136 | |||
137 | return self.stats |
||
138 | |||
139 | def __set_type(self, stats, sensor_type): |
||
140 | """Set the plugin type. |
||
141 | |||
142 | 4 types of stats is possible in the sensors plugin: |
||
143 | - Core temperature: 'temperature_core' |
||
144 | - Fan speed: 'fan_speed' |
||
145 | - HDD temperature: 'temperature_hdd' |
||
146 | - Battery capacity: 'battery' |
||
147 | """ |
||
148 | for i in stats: |
||
149 | # Set the sensors type |
||
150 | i.update({'type': sensor_type}) |
||
151 | # also add the key name |
||
152 | i.update({'key': self.get_key()}) |
||
153 | |||
154 | return stats |
||
155 | |||
156 | View Code Duplication | def update_views(self): |
|
0 ignored issues
–
show
Duplication
introduced
by
Loading history...
|
|||
157 | """Update stats views.""" |
||
158 | # Call the father's method |
||
159 | super(Plugin, self).update_views() |
||
160 | |||
161 | # Add specifics informations |
||
162 | # Alert |
||
163 | for i in self.stats: |
||
164 | if not i['value']: |
||
165 | continue |
||
166 | if i['type'] == 'battery': |
||
167 | self.views[i[self.get_key()]]['value']['decoration'] = self.get_alert(100 - i['value'], header=i['type']) |
||
168 | else: |
||
169 | self.views[i[self.get_key()]]['value']['decoration'] = self.get_alert(i['value'], header=i['type']) |
||
170 | |||
171 | def msg_curse(self, args=None): |
||
172 | """Return the dict to display in the curse interface.""" |
||
173 | # Init the return message |
||
174 | ret = [] |
||
175 | |||
176 | # Only process if stats exist and display plugin enable... |
||
177 | if not self.stats or args.disable_sensors: |
||
178 | return ret |
||
179 | |||
180 | # Build the string message |
||
181 | # Header |
||
182 | msg = '{0:18}'.format('SENSORS') |
||
183 | ret.append(self.curse_add_line(msg, "TITLE")) |
||
184 | |||
185 | for i in self.stats: |
||
186 | if i['value'] != b'ERR': |
||
187 | # New line |
||
188 | ret.append(self.curse_new_line()) |
||
189 | # Alias for the lable name ? |
||
190 | label = self.has_alias(i['label'].lower()) |
||
191 | if label is None: |
||
192 | label = i['label'] |
||
193 | if i['type'] != 'fan_speed': |
||
194 | msg = '{0:15}'.format(label[:15]) |
||
195 | else: |
||
196 | msg = '{0:13}'.format(label[:13]) |
||
197 | ret.append(self.curse_add_line(msg)) |
||
198 | if (args.fahrenheit and i['type'] != 'battery' and |
||
199 | i['type'] != 'fan_speed'): |
||
200 | value = to_fahrenheit(i['value']) |
||
201 | unit = 'F' |
||
202 | else: |
||
203 | value = i['value'] |
||
204 | unit = i['unit'] |
||
205 | try: |
||
206 | msg = '{0:>7.0f}{1}'.format(value, unit) |
||
207 | ret.append(self.curse_add_line( |
||
208 | msg, self.get_views(item=i[self.get_key()], |
||
209 | key='value', |
||
210 | option='decoration'))) |
||
211 | except (TypeError, ValueError): |
||
212 | pass |
||
213 | |||
214 | return ret |
||
215 | |||
216 | |||
217 | class GlancesGrabSensors(object): |
||
218 | |||
219 | """Get sensors stats using the py3sensors library.""" |
||
220 | |||
221 | def __init__(self): |
||
222 | """Init sensors stats.""" |
||
223 | try: |
||
224 | sensors.init() |
||
225 | except Exception: |
||
226 | self.initok = False |
||
227 | else: |
||
228 | self.initok = True |
||
229 | |||
230 | # Init the stats |
||
231 | self.reset() |
||
232 | |||
233 | def reset(self): |
||
234 | """Reset/init the stats.""" |
||
235 | self.sensors_list = [] |
||
236 | |||
237 | def __update__(self): |
||
238 | """Update the stats.""" |
||
239 | # Reset the list |
||
240 | self.reset() |
||
241 | |||
242 | if self.initok: |
||
243 | for chip in sensors.iter_detected_chips(): |
||
244 | for feature in chip: |
||
245 | sensors_current = {} |
||
246 | if feature.name.startswith(b'temp'): |
||
247 | # Temperature sensor |
||
248 | sensors_current['unit'] = SENSOR_TEMP_UNIT |
||
249 | elif feature.name.startswith(b'fan'): |
||
250 | # Fan speed sensor |
||
251 | sensors_current['unit'] = SENSOR_FAN_UNIT |
||
252 | if sensors_current: |
||
253 | try: |
||
254 | sensors_current['label'] = feature.label |
||
255 | sensors_current['value'] = int(feature.get_value()) |
||
256 | except SensorsError as e: |
||
257 | logger.debug("Cannot grab sensor stat(%s)" % e) |
||
258 | else: |
||
259 | self.sensors_list.append(sensors_current) |
||
260 | |||
261 | return self.sensors_list |
||
262 | |||
263 | def get(self, sensor_type='temperature_core'): |
||
264 | """Get sensors list.""" |
||
265 | self.__update__() |
||
266 | if sensor_type == 'temperature_core': |
||
267 | ret = [s for s in self.sensors_list if s['unit'] == SENSOR_TEMP_UNIT] |
||
268 | elif sensor_type == 'fan_speed': |
||
269 | ret = [s for s in self.sensors_list if s['unit'] == SENSOR_FAN_UNIT] |
||
270 | else: |
||
271 | # Unknown type |
||
272 | logger.debug("Unknown sensor type %s" % sensor_type) |
||
273 | ret = [] |
||
274 | return ret |
||
275 | |||
276 | def quit(self): |
||
277 | """End of connection.""" |
||
278 | if self.initok: |
||
279 | sensors.cleanup() |
||
280 |