Completed
Push — master ( 1806d1...053f07 )
by Nicolas
01:42
created

glances/plugins/glances_sensors.py (1 issue)

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
This code seems to be duplicated in your project.
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