sensors
paulfantom committed
Jul 7, 2016 at 18:31 UTC
8a9b25e9a1c94f76ba748b116c654c5a1f25c3ad
3 files changed
+417
-1
python.d/Makefile.am
+2
-1
@@ -18,6 +18,7 @@ dist_python_SCRIPTS = \
18
nginx.chart.py \
19
phpfpm.chart.py \
20
postfix.chart.py \
21
+ sensors.chart.py \
22
squid.chart.py \
23
tomcat.chart.py \
24
python-modules-installer.sh \
@@ -32,5 +33,5 @@ dist_pythonmodules_DATA = \
33
python_modules/__init__.py \
34
python_modules/base.py \
35
python_modules/msg.py \
36
+ python_modules/lm_sensors.py \
37
$(NULL)
36
-
python.d/python_modules/lm_sensors.py
new
+309
@@ -0,0 +1,309 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8 -*-
3
+import os
4
+
5
+from ctypes import CDLL, c_char_p, c_int, c_void_p, c_uint, c_double, byref, Structure, get_errno,\
6
+ POINTER, c_short, c_size_t, create_string_buffer
7
+from ctypes.util import find_library
8
+
9
+version_info = (0, 0, 3)
10
+
11
+__version__ = '.'.join(map(str, version_info))
12
+__date__ = '2014-08-17'
13
+__author__ = "Marc 'BlackJack' Rintsch"
14
+__contact__ = 'marc@rintsch.de'
15
+__license__ = 'LGPL v2.1'
16
+
17
+API_VERSION = 4
18
+DEFAULT_CONFIG_FILENAME = '/etc/sensors3.conf'
19
+
20
+LIB_FILENAME = os.environ.get('SENSORS_LIB') or find_library('sensors')
21
+SENSORS_LIB = CDLL(LIB_FILENAME)
22
+VERSION = c_char_p.in_dll(SENSORS_LIB, 'libsensors_version').value
23
+MAJOR_VERSION = version_info[0]
24
+STDC_LIB = CDLL(find_library('c'), use_errno=True)
25
+
26
+TYPE_DICT = {
27
+ 0: 'voltage',
28
+ 1: 'fan',
29
+ 2: 'temperature',
30
+ 3: 'power',
31
+ 4: 'energy',
32
+ 5: 'current',
33
+ 6: 'humidity',
34
+ 7: 'max_main',
35
+ 10: 'vid',
36
+ 11: 'intrusion',
37
+ 12: 'max_other',
38
+ 18: 'beep_enable'
39
+}
40
+
41
+class SensorsError(Exception):
42
+ def __init__(self, message, error_number=None):
43
+ Exception.__init__(self, message)
44
+ self.error_number = error_number
45
+
46
+
47
+def _error_check(result, _func, _arguments):
48
+ if result < 0:
49
+ raise SensorsError(_strerror(result), result)
50
+ return result
51
+
52
+_strerror = SENSORS_LIB.sensors_strerror
53
+_strerror.argtypes = [c_int]
54
+_strerror.restype = c_char_p
55
+
56
+_init = SENSORS_LIB.sensors_init
57
+_init.argtypes = [c_void_p]
58
+_init.restype = c_int
59
+_init.errcheck = _error_check
60
+
61
+cleanup = SENSORS_LIB.sensors_cleanup
62
+cleanup.argtypes = None
63
+cleanup.restype = None
64
+
65
+
66
+def init(config_filename=DEFAULT_CONFIG_FILENAME):
67
+ file_p = STDC_LIB.fopen(config_filename.encode('utf-8'), b'r')
68
+ if file_p is None:
69
+ error_number = get_errno()
70
+ raise OSError(error_number, os.strerror(error_number), config_filename)
71
+ try:
72
+ _init(file_p)
73
+ finally:
74
+ STDC_LIB.fclose(file_p)
75
+
76
+
77
+class Subfeature(Structure):
78
+ _fields_ = [
79
+ ('name', c_char_p),
80
+ ('number', c_int),
81
+ ('type', c_int),
82
+ ('mapping', c_int),
83
+ ('flags', c_uint),
84
+ ]
85
+
86
+ def __repr__(self):
87
+ return '<%s name=%r number=%d type=%d mapping=%d flags=%08x>' % (
88
+ self.__class__.__name__,
89
+ self.name,
90
+ self.number,
91
+ self.type,
92
+ self.mapping,
93
+ self.flags
94
+ )
95
+
96
+ def get_value(self):
97
+ result = c_double()
98
+ _get_value(byref(self.parent.chip), self.number, byref(result))
99
+ return result.value
100
+
101
+SUBFEATURE_P = POINTER(Subfeature)
102
+
103
+
104
+class Feature(Structure):
105
+ _fields_ = [
106
+ ('name', c_char_p),
107
+ ('number', c_int),
108
+ ('type', c_int),
109
+ ('_first_subfeature', c_int),
110
+ ('_padding1', c_int),
111
+ ]
112
+
113
+ def __repr__(self):
114
+ return '<%s name=%r number=%r type=%r>' % (
115
+ self.__class__.__name__,
116
+ self.name,
117
+ self.number,
118
+ self.type
119
+ )
120
+
121
+ def __iter__(self):
122
+ number = c_int(0)
123
+ while True:
124
+ result_p = _get_all_subfeatures(
125
+ byref(self.chip),
126
+ byref(self),
127
+ byref(number)
128
+ )
129
+ if not result_p:
130
+ break
131
+ result = result_p.contents
132
+ result.chip = self.chip
133
+ result.parent = self
134
+ yield result
135
+
136
+ @property
137
+ def label(self):
138
+ #
139
+ # TODO Maybe this is a memory leak!
140
+ #
141
+ return _get_label(byref(self.chip), byref(self)).decode('utf-8')
142
+
143
+ def get_value(self):
144
+ #
145
+ # TODO Is the first always the correct one for all feature types?
146
+ #
147
+ return next(iter(self)).get_value()
148
+
149
+FEATURE_P = POINTER(Feature)
150
+
151
+
152
+class Bus(Structure):
153
+ TYPE_ANY = -1
154
+ NR_ANY = -1
155
+
156
+ _fields_ = [
157
+ ('type', c_short),
158
+ ('nr', c_short),
159
+ ]
160
+
161
+ def __str__(self):
162
+ return (
163
+ '*' if self.type == self.TYPE_ANY
164
+ else _get_adapter_name(byref(self)).decode('utf-8')
165
+ )
166
+
167
+ def __repr__(self):
168
+ return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.nr)
169
+
170
+ @property
171
+ def has_wildcards(self):
172
+ return self.type == self.TYPE_ANY or self.nr == self.NR_ANY
173
+
174
+BUS_P = POINTER(Bus)
175
+
176
+
177
+class Chip(Structure):
178
+ #
179
+ # TODO Move common stuff into `AbstractChip` class.
180
+ #
181
+ _fields_ = [
182
+ ('prefix', c_char_p),
183
+ ('bus', Bus),
184
+ ('addr', c_int),
185
+ ('path', c_char_p),
186
+ ]
187
+
188
+ PREFIX_ANY = None
189
+ ADDR_ANY = -1
190
+
191
+ def __new__(cls, *args):
192
+ result = super(Chip, cls).__new__(cls)
193
+ if args:
194
+ _parse_chip_name(args[0].encode('utf-8'), byref(result))
195
+ return result
196
+
197
+ def __init__(self, *_args):
198
+ Structure.__init__(self)
199
+ #
200
+ # Need to bind the following to the instance so it is available in
201
+ # `__del__()` when the interpreter shuts down.
202
+ #
203
+ self._free_chip_name = _free_chip_name
204
+ self.byref = byref
205
+
206
+ def __del__(self):
207
+ if self._b_needsfree_:
208
+ self._free_chip_name(self.byref(self))
209
+
210
+ def __repr__(self):
211
+ return '<%s prefix=%r bus=%r addr=%r path=%r>' % (
212
+ (
213
+ self.__class__.__name__,
214
+ self.prefix,
215
+ self.bus,
216
+ self.addr,
217
+ self.path
218
+ )
219
+ )
220
+
221
+ def __str__(self):
222
+ buffer_size = 200
223
+ result = create_string_buffer(buffer_size)
224
+ used = _snprintf_chip_name(result, len(result), byref(self))
225
+ assert used < buffer_size
226
+ return result.value.decode('utf-8')
227
+
228
+ def __iter__(self):
229
+ number = c_int(0)
230
+ while True:
231
+ result_p = _get_features(byref(self), byref(number))
232
+ if not result_p:
233
+ break
234
+ result = result_p.contents
235
+ result.chip = self
236
+ yield result
237
+
238
+ @property
239
+ def adapter_name(self):
240
+ return str(self.bus)
241
+
242
+ @property
243
+ def has_wildcards(self):
244
+ return (
245
+ self.prefix == self.PREFIX_ANY
246
+ or self.addr == self.ADDR_ANY
247
+ or self.bus.has_wildcards
248
+ )
249
+
250
+CHIP_P = POINTER(Chip)
251
+
252
+
253
+_parse_chip_name = SENSORS_LIB.sensors_parse_chip_name
254
+_parse_chip_name.argtypes = [c_char_p, CHIP_P]
255
+_parse_chip_name.restype = c_int
256
+_parse_chip_name.errcheck = _error_check
257
+
258
+_free_chip_name = SENSORS_LIB.sensors_free_chip_name
259
+_free_chip_name.argtypes = [CHIP_P]
260
+_free_chip_name.restype = None
261
+
262
+_snprintf_chip_name = SENSORS_LIB.sensors_snprintf_chip_name
263
+_snprintf_chip_name.argtypes = [c_char_p, c_size_t, CHIP_P]
264
+_snprintf_chip_name.restype = c_int
265
+_snprintf_chip_name.errcheck = _error_check
266
+
267
+_get_adapter_name = SENSORS_LIB.sensors_get_adapter_name
268
+_get_adapter_name.argtypes = [BUS_P]
269
+_get_adapter_name.restype = c_char_p
270
+
271
+_get_label = SENSORS_LIB.sensors_get_label
272
+_get_label.argtypes = [CHIP_P, FEATURE_P]
273
+_get_label.restype = c_char_p
274
+
275
+_get_value = SENSORS_LIB.sensors_get_value
276
+_get_value.argtypes = [CHIP_P, c_int, POINTER(c_double)]
277
+_get_value.restype = c_int
278
+_get_value.errcheck = _error_check
279
+
280
+#
281
+# TODO sensors_set_value()
282
+# TODO sensors_do_chip_sets()
283
+#
284
+
285
+_get_detected_chips = SENSORS_LIB.sensors_get_detected_chips
286
+_get_detected_chips.argtypes = [CHIP_P, POINTER(c_int)]
287
+_get_detected_chips.restype = CHIP_P
288
+
289
+_get_features = SENSORS_LIB.sensors_get_features
290
+_get_features.argtypes = [CHIP_P, POINTER(c_int)]
291
+_get_features.restype = FEATURE_P
292
+
293
+_get_all_subfeatures = SENSORS_LIB.sensors_get_all_subfeatures
294
+_get_all_subfeatures.argtypes = [CHIP_P, FEATURE_P, POINTER(c_int)]
295
+_get_all_subfeatures.restype = SUBFEATURE_P
296
+
297
+#
298
+# TODO sensors_get_subfeature() ?
299
+#
300
+
301
+
302
+def iter_detected_chips(chip_name='*-*'):
303
+ chip = Chip(chip_name)
304
+ number = c_int(0)
305
+ while True:
306
+ result = _get_detected_chips(byref(chip), byref(number))
307
+ if not result:
308
+ break
309
+ yield result.contents
\ No newline at end of file
python.d/sensors.chart.py
new
+106
@@ -0,0 +1,106 @@
1
+# -*- coding: utf-8 -*-
2
+# Description: sensors netdata python.d plugin
3
+# Author: Pawel Krupa (paulfantom)
4
+
5
+from base import SimpleService
6
+import lm_sensors as sensors
7
+
8
+# default module values (can be overridden per job in `config`)
9
+# update_every = 2
10
+
11
+ORDER = ['temperature', 'voltage', 'fan', 'power', 'current', 'energy', 'humidity']
12
+
13
+# This is a prototype of chart definition which is used to dynamically create self.definitions
14
+CHARTS = {
15
+ 'temperature': {
16
+ 'options': [None, ' temperature', 'Celsius', 'temperature', 'sensors.temp', 'line'],
17
+ 'lines': [
18
+ [None, None, 'absolute', 1, 1000]
19
+ ]},
20
+ 'voltage': {
21
+ 'options': [None, ' voltage', 'Volts', 'voltage', 'sensors.volt', 'line'],
22
+ 'lines': [
23
+ [None, None, 'absolute', 1, 1000]
24
+ ]},
25
+ 'current': {
26
+ 'options': [None, ' current', 'Ampere', 'current', 'sensors.curr', 'line'],
27
+ 'lines': [
28
+ [None, None, 'absolute', 1, 1000]
29
+ ]},
30
+ 'power': {
31
+ 'options': [None, ' power', 'Watt', 'power', 'sensors.power', 'line'],
32
+ 'lines': [
33
+ [None, None, 'absolute', 1, 1000000]
34
+ ]},
35
+ 'fan': {
36
+ 'options': [None, ' fans speed', 'Rotations/min', 'fans', 'sensors.fans', 'line'],
37
+ 'lines': [
38
+ [None, None, 'absolute', 1, 1000]
39
+ ]},
40
+ 'energy': {
41
+ 'options': [None, ' energy', 'Joule', 'energy', 'sensors.energy', 'areastack'],
42
+ 'lines': [
43
+ [None, None, 'incremental', 1, 1000000]
44
+ ]},
45
+ 'humidity': {
46
+ 'options': [None, ' humidity', 'Percent', 'humidity', 'sensors.humidity', 'line'],
47
+ 'lines': [
48
+ [None, None, 'absolute', 1, 1000]
49
+ ]}
50
+}
51
+
52
+
53
+class Service(SimpleService):
54
+ def __init__(self, configuration=None, name=None):
55
+ SimpleService.__init__(self, configuration=configuration, name=name)
56
+ self.order = []
57
+ self.definitions = {}
58
+
59
+ def _get_data(self):
60
+ data = {}
61
+ try:
62
+ for chip in sensors.iter_detected_chips():
63
+ prefix = '_'.join(str(chip.path.decode()).split('/')[3:])
64
+ lines = {}
65
+ for feature in chip:
66
+ data[prefix + "_" + str(feature.name.decode())] = feature.get_value()
67
+ except Exception as e:
68
+ self.error(e)
69
+ return None
70
+
71
+ if len(data) == 0:
72
+ return None
73
+ return data
74
+
75
+ def _create_definitions(self):
76
+ for type in ORDER:
77
+ for chip in sensors.iter_detected_chips():
78
+ prefix = '_'.join(str(chip.path.decode()).split('/')[3:])
79
+ name = ""
80
+ lines = []
81
+ for feature in chip:
82
+ if sensors.TYPE_DICT[feature.type] == type:
83
+ name = str(chip.prefix.decode()) + "_" + sensors.TYPE_DICT[feature.type]
84
+ if name not in self.order:
85
+ options = list(CHARTS[type]['options'])
86
+ options[1] = str(chip.prefix) + options[1]
87
+ self.definitions[name] = {'options': options}
88
+ self.definitions[name]['lines'] = []
89
+ self.order.append(name)
90
+ line = list(CHARTS[type]['lines'][0])
91
+ line[0] = prefix + "_" + str(feature.name.decode())
92
+ line[1] = str(feature.label)
93
+ print(line)
94
+ self.definitions[name]['lines'].append(line)
95
+
96
+ def check(self):
97
+ try:
98
+ sensors.init()
99
+ except Exception as e:
100
+ print(e)
101
+ return False
102
+ try:
103
+ self._create_definitions()
104
+ except:
105
+ return False
106
+ return True
\ No newline at end of file