Test Failed
Push — develop ( 504450...f0e8ef )
by Nicolas
03:16
created

glances/plugins/glances_sensors.py (1 issue)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 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
import psutil
23
import warnings
24
25
from glances.logger import logger
26
from glances.compat import iteritems
27
from glances.plugins.sensors.glances_batpercent import Plugin as BatPercentPlugin
0 ignored issues
show
This line is too long as per the coding-style (81/80).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
28
from glances.plugins.sensors.glances_hddtemp import Plugin as HddTempPlugin
29
from glances.plugins.glances_plugin import GlancesPlugin
30
31
SENSOR_TEMP_UNIT = 'C'
32
SENSOR_FAN_UNIT = 'R'
33
34
35
def to_fahrenheit(celsius):
36
    """Convert Celsius to Fahrenheit."""
37
    return celsius * 1.8 + 32
38
39
40
class Plugin(GlancesPlugin):
41
    """Glances sensors plugin.
42
43
    The stats list includes both sensors and hard disks stats, if any.
44
    The sensors are already grouped by chip type and then sorted by name.
45
    The hard disks are already sorted by name.
46
    """
47
48
    def __init__(self, args=None, config=None):
49
        """Init the plugin."""
50
        super(Plugin, self).__init__(args=args,
51
                                     config=config,
52
                                     stats_init_value=[])
53
54
        # Init the sensor class
55
        self.glancesgrabsensors = GlancesGrabSensors()
56
57
        # Instance for the HDDTemp Plugin in order to display the hard disks
58
        # temperatures
59
        self.hddtemp_plugin = HddTempPlugin(args=args, config=config)
60
61
        # Instance for the BatPercent in order to display the batteries
62
        # capacities
63
        self.batpercent_plugin = BatPercentPlugin(args=args, config=config)
64
65
        # We want to display the stat in the curse interface
66
        self.display_curse = True
67
68
    def get_key(self):
69
        """Return the key of the list."""
70
        return 'label'
71
72
    @GlancesPlugin._check_decorator
73
    @GlancesPlugin._log_result_decorator
74
    def update(self):
75
        """Update sensors stats using the input method."""
76
        # Init new stats
77
        stats = self.get_init_value()
78
79
        if self.input_method == 'local':
80
            # Update stats using the dedicated lib
81
            stats = []
82
            # Get the temperature
83
            try:
84
                temperature = self.__set_type(self.glancesgrabsensors.get('temperature_core'),
85
                                              'temperature_core')
86
            except Exception as e:
87
                logger.error("Cannot grab sensors temperatures (%s)" % e)
88
            else:
89
                # Append temperature
90
                stats.extend(temperature)
91
            # Get the FAN speed
92
            try:
93
                fan_speed = self.__set_type(self.glancesgrabsensors.get('fan_speed'),
94
                                            'fan_speed')
95
            except Exception as e:
96
                logger.error("Cannot grab FAN speed (%s)" % e)
97
            else:
98
                # Append FAN speed
99
                stats.extend(fan_speed)
100
            # Update HDDtemp stats
101
            try:
102
                hddtemp = self.__set_type(self.hddtemp_plugin.update(),
103
                                          'temperature_hdd')
104
            except Exception as e:
105
                logger.error("Cannot grab HDD temperature (%s)" % e)
106
            else:
107
                # Append HDD temperature
108
                stats.extend(hddtemp)
109
            # Update batteries stats
110
            try:
111
                batpercent = self.__set_type(self.batpercent_plugin.update(),
112
                                             'battery')
113
            except Exception as e:
114
                logger.error("Cannot grab battery percent (%s)" % e)
115
            else:
116
                # Append Batteries %
117
                stats.extend(batpercent)
118
119
        elif self.input_method == 'snmp':
120
            # Update stats using SNMP
121
            # No standard:
122
            # http://www.net-snmp.org/wiki/index.php/Net-SNMP_and_lm-sensors_on_Ubuntu_10.04
123
124
            pass
125
126
        # Set the alias for each stat
127
        for stat in stats:
128
            alias = self.has_alias(stat["label"].lower())
129
            if alias:
130
                stat["label"] = alias
131
132
        # Update the stats
133
        self.stats = stats
134
135
        return self.stats
136
137
    def __set_type(self, stats, sensor_type):
138
        """Set the plugin type.
139
140
        4 types of stats is possible in the sensors plugin:
141
        - Core temperature: 'temperature_core'
142
        - Fan speed: 'fan_speed'
143
        - HDD temperature: 'temperature_hdd'
144
        - Battery capacity: 'battery'
145
        """
146
        for i in stats:
147
            # Set the sensors type
148
            i.update({'type': sensor_type})
149
            # also add the key name
150
            i.update({'key': self.get_key()})
151
152
        return stats
153
154
    def update_views(self):
155
        """Update stats views."""
156
        # Call the father's method
157
        super(Plugin, self).update_views()
158
159
        # Add specifics informations
160
        # Alert
161
        for i in self.stats:
162
            if not i['value']:
163
                continue
164
            if i['type'] == 'battery':
165
                self.views[i[self.get_key()]]['value']['decoration'] = self.get_alert(100 - i['value'], header=i['type'])
166
            else:
167
                self.views[i[self.get_key()]]['value']['decoration'] = self.get_alert(i['value'], header=i['type'])
168
169
    def msg_curse(self, args=None, max_width=None):
170
        """Return the dict to display in the curse interface."""
171
        # Init the return message
172
        ret = []
173
174
        # Only process if stats exist and display plugin enable...
175
        if not self.stats or self.is_disable():
176
            return ret
177
178
        # Max size for the interface name
179
        name_max_width = max_width - 12
180
181
        # Header
182
        msg = '{:{width}}'.format('SENSORS', width=name_max_width)
183
        ret.append(self.curse_add_line(msg, "TITLE"))
184
185
        # Stats
186
        for i in self.stats:
187
            # Do not display anything if no battery are detected
188
            if i['type'] == 'battery' and i['value'] == []:
189
                continue
190
            # New line
191
            ret.append(self.curse_new_line())
192
            msg = '{:{width}}'.format(i["label"][:name_max_width],
193
                                      width=name_max_width)
194
            ret.append(self.curse_add_line(msg))
195
            if i['value'] in (b'ERR', b'SLP', b'UNK', b'NOS'):
196
                msg = '{:>13}'.format(i['value'])
197
                ret.append(self.curse_add_line(
198
                    msg, self.get_views(item=i[self.get_key()],
199
                                        key='value',
200
                                        option='decoration')))
201
            else:
202
                if (args.fahrenheit and i['type'] != 'battery' and
203
                        i['type'] != 'fan_speed'):
204
                    value = to_fahrenheit(i['value'])
205
                    unit = 'F'
206
                else:
207
                    value = i['value']
208
                    unit = i['unit']
209
                try:
210
                    msg = '{:>13.0f}{}'.format(value, unit)
211
                    ret.append(self.curse_add_line(
212
                        msg, self.get_views(item=i[self.get_key()],
213
                                            key='value',
214
                                            option='decoration')))
215
                except (TypeError, ValueError):
216
                    pass
217
218
        return ret
219
220
221
class GlancesGrabSensors(object):
222
    """Get sensors stats."""
223
224
    def __init__(self):
225
        """Init sensors stats."""
226
        # Temperatures
227
        self.init_temp = False
228
        self.stemps = {}
229
        try:
230
            # psutil>=5.1.0, Linux-only
231
            self.stemps = psutil.sensors_temperatures()
232
        except AttributeError:
233
            logger.debug("Cannot grab temperatures. Platform not supported.")
234
        else:
235
            self.init_temp = True
236
            # Solve an issue #1203 concerning a RunTimeError warning message displayed
237
            # in the curses interface.
238
            warnings.filterwarnings("ignore")
239
240
        # Fans
241
        self.init_fan = False
242
        self.sfans = {}
243
        try:
244
            # psutil>=5.2.0, Linux-only
245
            self.sfans = psutil.sensors_fans()
246
        except AttributeError:
247
            logger.debug("Cannot grab fans speed. Platform not supported.")
248
        else:
249
            self.init_fan = True
250
251
        # !!! Disable Fan: High CPU consumption with psutil 5.2.0 or higher
252
        # Delete the two followings lines when corrected (https://github.com/giampaolo/psutil/issues/1199)
253
        # Correct and tested with PsUtil 5.6.1 (Ubuntu 18.04)
254
        # self.init_fan = False
255
        # logger.debug("Fan speed sensors disable (see https://github.com/giampaolo/psutil/issues/1199)")
256
257
        # Init the stats
258
        self.reset()
259
260
    def reset(self):
261
        """Reset/init the stats."""
262
        self.sensors_list = []
263
264
    def __update__(self):
265
        """Update the stats."""
266
        # Reset the list
267
        self.reset()
268
269
        if not self.init_temp:
270
            return self.sensors_list
271
272
        # Temperatures sensors
273
        self.sensors_list.extend(self.build_sensors_list(SENSOR_TEMP_UNIT))
274
275
        # Fans sensors
276
        self.sensors_list.extend(self.build_sensors_list(SENSOR_FAN_UNIT))
277
278
        return self.sensors_list
279
280
    def build_sensors_list(self, type):
281
        """Build the sensors list depending of the type.
282
283
        type: SENSOR_TEMP_UNIT or SENSOR_FAN_UNIT
284
285
        output: a list
286
        """
287
        ret = []
288
        if type == SENSOR_TEMP_UNIT and self.init_temp:
289
            input_list = self.stemps
290
            self.stemps = psutil.sensors_temperatures()
291
        elif type == SENSOR_FAN_UNIT and self.init_fan:
292
            input_list = self.sfans
293
            self.sfans = psutil.sensors_fans()
294
        else:
295
            return ret
296
        for chipname, chip in iteritems(input_list):
297
            i = 1
298
            for feature in chip:
299
                sensors_current = {}
300
                # Sensor name
301
                if feature.label == '':
302
                    sensors_current['label'] = chipname + ' ' + str(i)
303
                else:
304
                    sensors_current['label'] = feature.label
305
                # Fan speed and unit
306
                sensors_current['value'] = int(feature.current)
307
                sensors_current['unit'] = type
308
                # Add sensor to the list
309
                ret.append(sensors_current)
310
                i += 1
311
        return ret
312
313
    def get(self, sensor_type='temperature_core'):
314
        """Get sensors list."""
315
        self.__update__()
316
        if sensor_type == 'temperature_core':
317
            ret = [s for s in self.sensors_list if s['unit'] == SENSOR_TEMP_UNIT]
318
        elif sensor_type == 'fan_speed':
319
            ret = [s for s in self.sensors_list if s['unit'] == SENSOR_FAN_UNIT]
320
        else:
321
            # Unknown type
322
            logger.debug("Unknown sensor type %s" % sensor_type)
323
            ret = []
324
        return ret
325