Completed
Push — master ( d2dcdf...3f6257 )
by Nicolas
01:19
created

GlancesGrabSensors   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 95
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 95
rs 10
wmc 22

6 Methods

Rating   Name   Duplication   Size   Complexity  
A reset() 0 3 1
A __update__() 0 15 2
B __init__() 0 23 4
B build_sensors_list() 0 29 6
B get() 0 12 7
A quit() 0 4 2
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
        try:
228
            # psutil>=5.1.0 is required
229
            self.stemps = psutil.sensors_temperatures()
230
        except AttributeError:
231
            logger.warning("PsUtil 5.1.0 or higher is needed to grab temperatures sensors")
232
            self.initok = False
233
            self.stemps = {}
234
        else:
235
            self.initok = True
236
237
        # Fans
238
        try:
239
            # psutil>=5.2.0 is required
240
            self.sfans = psutil.sensors_fans()
241
        except AttributeError:
242
            logger.warning("PsUtil 5.2.0 or higher is needed to grab fans sensors")
243
            self.sfans = {}
244
245
        # Init the stats
246
        self.reset()
247
248
    def reset(self):
249
        """Reset/init the stats."""
250
        self.sensors_list = []
251
252
    def __update__(self):
253
        """Update the stats."""
254
        # Reset the list
255
        self.reset()
256
257
        if not self.initok:
258
            return self.sensors_list
259
260
        # Temperatures sensors
261
        self.sensors_list.extend(self.build_sensors_list(SENSOR_TEMP_UNIT))
262
263
        # Fans sensors
264
        self.sensors_list.extend(self.build_sensors_list(SENSOR_FAN_UNIT))
265
266
        return self.sensors_list
267
268
    def build_sensors_list(self, type):
269
        """Build the sensors list depending of the type.
270
271
        type: SENSOR_TEMP_UNIT or SENSOR_FAN_UNIT
272
273
        output: a list"""
274
        ret = []
275
        if type == SENSOR_TEMP_UNIT:
276
            input_list = self.stemps
277
        elif type == SENSOR_FAN_UNIT:
278
            input_list = self.sfans
279
        else:
280
            return ret
281
        for chipname, chip in iteritems(input_list):
282
            i = 1
283
            for feature in chip:
284
                sensors_current = {}
285
                # Sensor name
286
                if feature.label == '':
287
                    sensors_current['label'] = chipname + ' ' + str(i)
288
                else:
289
                    sensors_current['label'] = feature.label
290
                # Fan speed and unit
291
                sensors_current['value'] = int(feature.current)
292
                sensors_current['unit'] = type
293
                # Add sensor to the list
294
                ret.append(sensors_current)
295
                i += 1
296
        return ret
297
298
    def get(self, sensor_type='temperature_core'):
299
        """Get sensors list."""
300
        self.__update__()
301
        if sensor_type == 'temperature_core':
302
            ret = [s for s in self.sensors_list if s['unit'] == SENSOR_TEMP_UNIT]
303
        elif sensor_type == 'fan_speed':
304
            ret = [s for s in self.sensors_list if s['unit'] == SENSOR_FAN_UNIT]
305
        else:
306
            # Unknown type
307
            logger.debug("Unknown sensor type %s" % sensor_type)
308
            ret = []
309
        return ret
310
311
    def quit(self):
312
        """End of connection."""
313
        if self.initok:
314
            sensors.cleanup()
315