Completed
Push — master ( a1acfe...a11662 )
by Nicolas
01:53 queued 19s
created

GlancesGrabSensors.build_sensors_list()   D

Complexity

Conditions 8

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
c 1
b 0
f 0
dl 0
loc 31
rs 4
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2017 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
24
from glances.logger import logger
25
from glances.compat import iteritems
26
from glances.plugins.glances_batpercent import Plugin as BatPercentPlugin
27
from glances.plugins.glances_hddtemp import Plugin as HddTempPlugin
28
from glances.plugins.glances_plugin import GlancesPlugin
29
30
SENSOR_TEMP_UNIT = 'C'
31
SENSOR_FAN_UNIT = 'rpm'
32
33
34
def to_fahrenheit(celsius):
35
    """Convert Celsius to Fahrenheit."""
36
    return celsius * 1.8 + 32
37
38
39
class Plugin(GlancesPlugin):
40
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):
49
        """Init the plugin."""
50
        super(Plugin, self).__init__(args=args)
51
52
        # Init the sensor class
53
        self.glancesgrabsensors = GlancesGrabSensors()
54
55
        # Instance for the HDDTemp Plugin in order to display the hard disks
56
        # temperatures
57
        self.hddtemp_plugin = HddTempPlugin(args=args)
58
59
        # Instance for the BatPercent in order to display the batteries
60
        # capacities
61
        self.batpercent_plugin = BatPercentPlugin(args=args)
62
63
        # We want to display the stat in the curse interface
64
        self.display_curse = True
65
66
        # Init the stats
67
        self.reset()
68
69
    def get_key(self):
70
        """Return the key of the list."""
71
        return 'label'
72
73
    def reset(self):
74
        """Reset/init the stats."""
75
        self.stats = []
76
77
    @GlancesPlugin._check_decorator
78
    @GlancesPlugin._log_result_decorator
79
    def update(self):
80
        """Update sensors stats using the input method."""
81
        # Reset the stats
82
        self.reset()
83
84
        if self.input_method == 'local':
85
            # Update stats using the dedicated lib
86
            self.stats = []
87
            # Get the temperature
88
            try:
89
                temperature = self.__set_type(self.glancesgrabsensors.get('temperature_core'),
90
                                              'temperature_core')
91
            except Exception as e:
92
                logger.error("Cannot grab sensors temperatures (%s)" % e)
93
            else:
94
                # Append temperature
95
                self.stats.extend(temperature)
96
            # Get the FAN speed
97
            try:
98
                fan_speed = self.__set_type(self.glancesgrabsensors.get('fan_speed'),
99
                                            'fan_speed')
100
            except Exception as e:
101
                logger.error("Cannot grab FAN speed (%s)" % e)
102
            else:
103
                # Append FAN speed
104
                self.stats.extend(fan_speed)
105
            # Update HDDtemp stats
106
            try:
107
                hddtemp = self.__set_type(self.hddtemp_plugin.update(),
108
                                          'temperature_hdd')
109
            except Exception as e:
110
                logger.error("Cannot grab HDD temperature (%s)" % e)
111
            else:
112
                # Append HDD temperature
113
                self.stats.extend(hddtemp)
114
            # Update batteries stats
115
            try:
116
                batpercent = self.__set_type(self.batpercent_plugin.update(),
117
                                             'battery')
118
            except Exception as e:
119
                logger.error("Cannot grab battery percent (%s)" % e)
120
            else:
121
                # Append Batteries %
122
                self.stats.extend(batpercent)
123
124
        elif self.input_method == 'snmp':
125
            # Update stats using SNMP
126
            # No standard:
127
            # http://www.net-snmp.org/wiki/index.php/Net-SNMP_and_lm-sensors_on_Ubuntu_10.04
128
129
            pass
130
131
        return self.stats
132
133
    def __set_type(self, stats, sensor_type):
134
        """Set the plugin type.
135
136
        4 types of stats is possible in the sensors plugin:
137
        - Core temperature: 'temperature_core'
138
        - Fan speed: 'fan_speed'
139
        - HDD temperature: 'temperature_hdd'
140
        - Battery capacity: 'battery'
141
        """
142
        for i in stats:
143
            # Set the sensors type
144
            i.update({'type': sensor_type})
145
            # also add the key name
146
            i.update({'key': self.get_key()})
147
148
        return stats
149
150
    def update_views(self):
151
        """Update stats views."""
152
        # Call the father's method
153
        super(Plugin, self).update_views()
154
155
        # Add specifics informations
156 View Code Duplication
        # Alert
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
157
        for i in self.stats:
158
            if not i['value']:
159
                continue
160
            if i['type'] == 'battery':
161
                self.views[i[self.get_key()]]['value']['decoration'] = self.get_alert(100 - i['value'], header=i['type'])
162
            else:
163
                self.views[i[self.get_key()]]['value']['decoration'] = self.get_alert(i['value'], header=i['type'])
164
165
    def msg_curse(self, args=None):
166
        """Return the dict to display in the curse interface."""
167
        # Init the return message
168
        ret = []
169
170
        # Only process if stats exist and display plugin enable...
171
        if not self.stats or args.disable_sensors:
172
            return ret
173
174
        # Build the string message
175
        # Header
176
        msg = '{:18}'.format('SENSORS')
177
        ret.append(self.curse_add_line(msg, "TITLE"))
178
179
        for i in self.stats:
180
            # Do not display anything if no battery are detected
181
            if i['type'] == 'battery' and i['value'] == []:
182
                continue
183
            # New line
184
            ret.append(self.curse_new_line())
185
            # Alias for the lable name ?
186
            label = self.has_alias(i['label'].lower())
187
            if label is None:
188
                label = i['label']
189
            if i['type'] != 'fan_speed':
190
                msg = '{:15}'.format(label[:15])
191
            else:
192
                msg = '{:13}'.format(label[:13])
193
            ret.append(self.curse_add_line(msg))
194
            if i['value'] in (b'ERR', b'SLP', b'UNK', b'NOS'):
195
                msg = '{:>8}'.format(i['value'])
196
                ret.append(self.curse_add_line(
197
                    msg, self.get_views(item=i[self.get_key()],
198
                                        key='value',
199
                                        option='decoration')))
200
            else:
201
                if (args.fahrenheit and i['type'] != 'battery' and
202
                        i['type'] != 'fan_speed'):
203
                    value = to_fahrenheit(i['value'])
204
                    unit = 'F'
205
                else:
206
                    value = i['value']
207
                    unit = i['unit']
208
                try:
209
                    msg = '{:>7.0f}{}'.format(value, unit)
210
                    ret.append(self.curse_add_line(
211
                        msg, self.get_views(item=i[self.get_key()],
212
                                            key='value',
213
                                            option='decoration')))
214
                except (TypeError, ValueError):
215
                    pass
216
217
        return ret
218
219
220
class GlancesGrabSensors(object):
221
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 is required
231
            self.stemps = psutil.sensors_temperatures()
232
        except AttributeError:
233
            logger.warning("PsUtil 5.1.0 or higher is needed to grab temperatures sensors")
234
        except OSError as e:
235
            # FreeBSD: If oid 'hw.acpi.battery' not present, Glances wont start #1055
236
            logger.error("Can not grab temperatures sensors ({})".format(e))
237
        else:
238
            self.init_temp = True
239
240
        # Fans
241
        self.init_fan = False
242
        self.sfans = {}
243
        try:
244
            # psutil>=5.2.0 is required
245
            self.sfans = psutil.sensors_fans()
246
        except AttributeError:
247
            logger.warning("PsUtil 5.2.0 or higher is needed to grab fans sensors")
248
        except OSError as e:
249
            logger.error("Can not grab fans sensors ({})".format(e))
250
        else:
251
            self.init_fan = True
252
253
        # !!! Disable Fan: High CPU consumption is PSUtil 5.2.0
254
        # Delete the following line when corrected
255
        self.init_fan = False
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
        ret = []
287
        if type == SENSOR_TEMP_UNIT and self.init_temp:
288
            input_list = self.stemps
289
            self.stemps = psutil.sensors_temperatures()
290
        elif type == SENSOR_FAN_UNIT and self.init_fan:
291
            input_list = self.sfans
292
            self.sfans = psutil.sensors_fans()
293
        else:
294
            return ret
295
        for chipname, chip in iteritems(input_list):
296
            i = 1
297
            for feature in chip:
298
                sensors_current = {}
299
                # Sensor name
300
                if feature.label == '':
301
                    sensors_current['label'] = chipname + ' ' + str(i)
302
                else:
303
                    sensors_current['label'] = feature.label
304
                # Fan speed and unit
305
                sensors_current['value'] = int(feature.current)
306
                sensors_current['unit'] = type
307
                # Add sensor to the list
308
                ret.append(sensors_current)
309
                i += 1
310
        return ret
311
312
    def get(self, sensor_type='temperature_core'):
313
        """Get sensors list."""
314
        self.__update__()
315
        if sensor_type == 'temperature_core':
316
            ret = [s for s in self.sensors_list if s['unit'] == SENSOR_TEMP_UNIT]
317
        elif sensor_type == 'fan_speed':
318
            ret = [s for s in self.sensors_list if s['unit'] == SENSOR_FAN_UNIT]
319
        else:
320
            # Unknown type
321
            logger.debug("Unknown sensor type %s" % sensor_type)
322
            ret = []
323
        return ret
324