]> arthur.barton.de Git - netdata.git/commitdiff
sensors
authorpaulfantom <paulfantom@gmail.com>
Thu, 7 Jul 2016 16:31:04 +0000 (18:31 +0200)
committerpaulfantom <paulfantom@gmail.com>
Thu, 7 Jul 2016 16:31:04 +0000 (18:31 +0200)
python.d/Makefile.am
python.d/python_modules/lm_sensors.py [new file with mode: 0644]
python.d/sensors.chart.py [new file with mode: 0644]

index 132e551be81b4a0c29339867fc75f44ad5d8b7fd..933b567139e8ddd54f10e25b0b17c9a5e62354e9 100644 (file)
@@ -18,6 +18,7 @@ dist_python_SCRIPTS = \
        nginx.chart.py \
        phpfpm.chart.py \
        postfix.chart.py \
+       sensors.chart.py \
        squid.chart.py \
        tomcat.chart.py \
        python-modules-installer.sh \
@@ -32,5 +33,5 @@ dist_pythonmodules_DATA = \
        python_modules/__init__.py \
        python_modules/base.py \
        python_modules/msg.py \
+       python_modules/lm_sensors.py \
        $(NULL)
-
diff --git a/python.d/python_modules/lm_sensors.py b/python.d/python_modules/lm_sensors.py
new file mode 100644 (file)
index 0000000..4b111f5
--- /dev/null
@@ -0,0 +1,309 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+
+from ctypes import CDLL, c_char_p, c_int, c_void_p, c_uint, c_double, byref, Structure, get_errno,\
+    POINTER, c_short, c_size_t, create_string_buffer
+from ctypes.util import find_library
+
+version_info = (0, 0, 3)
+
+__version__ = '.'.join(map(str, version_info))
+__date__ = '2014-08-17'
+__author__ = "Marc 'BlackJack' Rintsch"
+__contact__ = 'marc@rintsch.de'
+__license__ = 'LGPL v2.1'
+
+API_VERSION = 4
+DEFAULT_CONFIG_FILENAME = '/etc/sensors3.conf'
+
+LIB_FILENAME = os.environ.get('SENSORS_LIB') or find_library('sensors')
+SENSORS_LIB = CDLL(LIB_FILENAME)
+VERSION = c_char_p.in_dll(SENSORS_LIB, 'libsensors_version').value
+MAJOR_VERSION = version_info[0]
+STDC_LIB = CDLL(find_library('c'), use_errno=True)
+
+TYPE_DICT = {
+    0: 'voltage',
+    1: 'fan',
+    2: 'temperature',
+    3: 'power',
+    4: 'energy',
+    5: 'current',
+    6: 'humidity',
+    7: 'max_main',
+    10: 'vid',
+    11: 'intrusion',
+    12: 'max_other',
+    18: 'beep_enable'
+}
+
+class SensorsError(Exception):
+    def __init__(self, message, error_number=None):
+        Exception.__init__(self, message)
+        self.error_number = error_number
+
+
+def _error_check(result, _func, _arguments):
+    if result < 0:
+        raise SensorsError(_strerror(result), result)
+    return result
+
+_strerror = SENSORS_LIB.sensors_strerror
+_strerror.argtypes = [c_int]
+_strerror.restype = c_char_p
+
+_init = SENSORS_LIB.sensors_init
+_init.argtypes = [c_void_p]
+_init.restype = c_int
+_init.errcheck = _error_check
+
+cleanup = SENSORS_LIB.sensors_cleanup
+cleanup.argtypes = None
+cleanup.restype = None
+
+
+def init(config_filename=DEFAULT_CONFIG_FILENAME):
+    file_p = STDC_LIB.fopen(config_filename.encode('utf-8'), b'r')
+    if file_p is None:
+        error_number = get_errno()
+        raise OSError(error_number, os.strerror(error_number), config_filename)
+    try:
+        _init(file_p)
+    finally:
+        STDC_LIB.fclose(file_p)
+
+
+class Subfeature(Structure):
+    _fields_ = [
+        ('name', c_char_p),
+        ('number', c_int),
+        ('type', c_int),
+        ('mapping', c_int),
+        ('flags', c_uint),
+    ]
+
+    def __repr__(self):
+        return '<%s name=%r number=%d type=%d mapping=%d flags=%08x>' % (
+            self.__class__.__name__,
+            self.name,
+            self.number,
+            self.type,
+            self.mapping,
+            self.flags
+        )
+
+    def get_value(self):
+        result = c_double()
+        _get_value(byref(self.parent.chip), self.number, byref(result))
+        return result.value
+
+SUBFEATURE_P = POINTER(Subfeature)
+
+
+class Feature(Structure):
+    _fields_ = [
+        ('name', c_char_p),
+        ('number', c_int),
+        ('type', c_int),
+        ('_first_subfeature', c_int),
+        ('_padding1', c_int),
+    ]
+
+    def __repr__(self):
+        return '<%s name=%r number=%r type=%r>' % (
+            self.__class__.__name__,
+            self.name,
+            self.number,
+            self.type
+        )
+
+    def __iter__(self):
+        number = c_int(0)
+        while True:
+            result_p = _get_all_subfeatures(
+                byref(self.chip),
+                byref(self),
+                byref(number)
+            )
+            if not result_p:
+                break
+            result = result_p.contents
+            result.chip = self.chip
+            result.parent = self
+            yield result
+
+    @property
+    def label(self):
+        #
+        # TODO Maybe this is a memory leak!
+        #
+        return _get_label(byref(self.chip), byref(self)).decode('utf-8')
+
+    def get_value(self):
+        #
+        # TODO Is the first always the correct one for all feature types?
+        #
+        return next(iter(self)).get_value()
+
+FEATURE_P = POINTER(Feature)
+
+
+class Bus(Structure):
+    TYPE_ANY = -1
+    NR_ANY = -1
+
+    _fields_ = [
+        ('type', c_short),
+        ('nr', c_short),
+    ]
+
+    def __str__(self):
+        return (
+            '*' if self.type == self.TYPE_ANY
+            else _get_adapter_name(byref(self)).decode('utf-8')
+        )
+
+    def __repr__(self):
+        return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.nr)
+
+    @property
+    def has_wildcards(self):
+        return self.type == self.TYPE_ANY or self.nr == self.NR_ANY
+
+BUS_P = POINTER(Bus)
+
+
+class Chip(Structure):
+    #
+    # TODO Move common stuff into `AbstractChip` class.
+    #
+    _fields_ = [
+        ('prefix', c_char_p),
+        ('bus', Bus),
+        ('addr', c_int),
+        ('path', c_char_p),
+    ]
+
+    PREFIX_ANY = None
+    ADDR_ANY = -1
+
+    def __new__(cls, *args):
+        result = super(Chip, cls).__new__(cls)
+        if args:
+            _parse_chip_name(args[0].encode('utf-8'), byref(result))
+        return result
+
+    def __init__(self, *_args):
+        Structure.__init__(self)
+        #
+        # Need to bind the following to the instance so it is available in
+        #  `__del__()` when the interpreter shuts down.
+        #
+        self._free_chip_name = _free_chip_name
+        self.byref = byref
+
+    def __del__(self):
+        if self._b_needsfree_:
+            self._free_chip_name(self.byref(self))
+
+    def __repr__(self):
+        return '<%s prefix=%r bus=%r addr=%r path=%r>' % (
+            (
+                self.__class__.__name__,
+                self.prefix,
+                self.bus,
+                self.addr,
+                self.path
+            )
+        )
+
+    def __str__(self):
+        buffer_size = 200
+        result = create_string_buffer(buffer_size)
+        used = _snprintf_chip_name(result, len(result), byref(self))
+        assert used < buffer_size
+        return result.value.decode('utf-8')
+
+    def __iter__(self):
+        number = c_int(0)
+        while True:
+            result_p = _get_features(byref(self), byref(number))
+            if not result_p:
+                break
+            result = result_p.contents
+            result.chip = self
+            yield result
+
+    @property
+    def adapter_name(self):
+        return str(self.bus)
+
+    @property
+    def has_wildcards(self):
+        return (
+            self.prefix == self.PREFIX_ANY
+            or self.addr == self.ADDR_ANY
+            or self.bus.has_wildcards
+        )
+
+CHIP_P = POINTER(Chip)
+
+
+_parse_chip_name = SENSORS_LIB.sensors_parse_chip_name
+_parse_chip_name.argtypes = [c_char_p, CHIP_P]
+_parse_chip_name.restype = c_int
+_parse_chip_name.errcheck = _error_check
+
+_free_chip_name = SENSORS_LIB.sensors_free_chip_name
+_free_chip_name.argtypes = [CHIP_P]
+_free_chip_name.restype = None
+
+_snprintf_chip_name = SENSORS_LIB.sensors_snprintf_chip_name
+_snprintf_chip_name.argtypes = [c_char_p, c_size_t, CHIP_P]
+_snprintf_chip_name.restype = c_int
+_snprintf_chip_name.errcheck = _error_check
+
+_get_adapter_name = SENSORS_LIB.sensors_get_adapter_name
+_get_adapter_name.argtypes = [BUS_P]
+_get_adapter_name.restype = c_char_p
+
+_get_label = SENSORS_LIB.sensors_get_label
+_get_label.argtypes = [CHIP_P, FEATURE_P]
+_get_label.restype = c_char_p
+
+_get_value = SENSORS_LIB.sensors_get_value
+_get_value.argtypes = [CHIP_P, c_int, POINTER(c_double)]
+_get_value.restype = c_int
+_get_value.errcheck = _error_check
+
+#
+# TODO sensors_set_value()
+# TODO sensors_do_chip_sets()
+#
+
+_get_detected_chips = SENSORS_LIB.sensors_get_detected_chips
+_get_detected_chips.argtypes = [CHIP_P, POINTER(c_int)]
+_get_detected_chips.restype = CHIP_P
+
+_get_features = SENSORS_LIB.sensors_get_features
+_get_features.argtypes = [CHIP_P, POINTER(c_int)]
+_get_features.restype = FEATURE_P
+
+_get_all_subfeatures = SENSORS_LIB.sensors_get_all_subfeatures
+_get_all_subfeatures.argtypes = [CHIP_P, FEATURE_P, POINTER(c_int)]
+_get_all_subfeatures.restype = SUBFEATURE_P
+
+#
+# TODO sensors_get_subfeature() ?
+#
+
+
+def iter_detected_chips(chip_name='*-*'):
+    chip = Chip(chip_name)
+    number = c_int(0)
+    while True:
+        result = _get_detected_chips(byref(chip), byref(number))
+        if not result:
+            break
+        yield result.contents
\ No newline at end of file
diff --git a/python.d/sensors.chart.py b/python.d/sensors.chart.py
new file mode 100644 (file)
index 0000000..0807f1a
--- /dev/null
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+# Description: sensors netdata python.d plugin
+# Author: Pawel Krupa (paulfantom)
+
+from base import SimpleService
+import lm_sensors as sensors
+
+# default module values (can be overridden per job in `config`)
+# update_every = 2
+
+ORDER = ['temperature', 'voltage', 'fan', 'power', 'current', 'energy', 'humidity']
+
+# This is a prototype of chart definition which is used to dynamically create self.definitions
+CHARTS = {
+    'temperature': {
+        'options': [None, ' temperature', 'Celsius', 'temperature', 'sensors.temp', 'line'],
+        'lines': [
+            [None, None, 'absolute', 1, 1000]
+        ]},
+    'voltage': {
+        'options': [None, ' voltage', 'Volts', 'voltage', 'sensors.volt', 'line'],
+        'lines': [
+            [None, None, 'absolute', 1, 1000]
+        ]},
+    'current': {
+        'options': [None, ' current', 'Ampere', 'current', 'sensors.curr', 'line'],
+        'lines': [
+            [None, None, 'absolute', 1, 1000]
+        ]},
+    'power': {
+        'options': [None, ' power', 'Watt', 'power', 'sensors.power', 'line'],
+        'lines': [
+            [None, None, 'absolute', 1, 1000000]
+        ]},
+    'fan': {
+        'options': [None, ' fans speed', 'Rotations/min', 'fans', 'sensors.fans', 'line'],
+        'lines': [
+            [None, None, 'absolute', 1, 1000]
+        ]},
+    'energy': {
+        'options': [None, ' energy', 'Joule', 'energy', 'sensors.energy', 'areastack'],
+        'lines': [
+            [None, None, 'incremental', 1, 1000000]
+        ]},
+    'humidity': {
+        'options': [None, ' humidity', 'Percent', 'humidity', 'sensors.humidity', 'line'],
+        'lines': [
+            [None, None, 'absolute', 1, 1000]
+        ]}
+}
+
+
+class Service(SimpleService):
+    def __init__(self, configuration=None, name=None):
+        SimpleService.__init__(self, configuration=configuration, name=name)
+        self.order = []
+        self.definitions = {}
+
+    def _get_data(self):
+        data = {}
+        try:
+            for chip in sensors.iter_detected_chips():
+                prefix = '_'.join(str(chip.path.decode()).split('/')[3:])
+                lines = {}
+                for feature in chip:
+                    data[prefix + "_" + str(feature.name.decode())] = feature.get_value()
+        except Exception as e:
+            self.error(e)
+            return None
+
+        if len(data) == 0:
+            return None
+        return data
+
+    def _create_definitions(self):
+        for type in ORDER:
+            for chip in sensors.iter_detected_chips():
+                prefix = '_'.join(str(chip.path.decode()).split('/')[3:])
+                name = ""
+                lines = []
+                for feature in chip:
+                    if sensors.TYPE_DICT[feature.type] == type:
+                        name = str(chip.prefix.decode()) + "_" + sensors.TYPE_DICT[feature.type]
+                        if name not in self.order:
+                            options = list(CHARTS[type]['options'])
+                            options[1] = str(chip.prefix) + options[1]
+                            self.definitions[name] = {'options': options}
+                            self.definitions[name]['lines'] = []
+                            self.order.append(name)
+                        line = list(CHARTS[type]['lines'][0])
+                        line[0] = prefix + "_" + str(feature.name.decode())
+                        line[1] = str(feature.label)
+                        print(line)
+                        self.definitions[name]['lines'].append(line)
+
+    def check(self):
+        try:
+            sensors.init()
+        except Exception as e:
+            print(e)
+            return False
+        try:
+            self._create_definitions()
+        except:
+            return False
+        return True
\ No newline at end of file