From 8a9b25e9a1c94f76ba748b116c654c5a1f25c3ad Mon Sep 17 00:00:00 2001 From: paulfantom Date: Thu, 7 Jul 2016 18:31:04 +0200 Subject: [PATCH] sensors --- python.d/Makefile.am | 3 +- python.d/python_modules/lm_sensors.py | 309 ++++++++++++++++++++++++++ python.d/sensors.chart.py | 106 +++++++++ 3 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 python.d/python_modules/lm_sensors.py create mode 100644 python.d/sensors.chart.py diff --git a/python.d/Makefile.am b/python.d/Makefile.am index 132e551b..933b5671 100644 --- a/python.d/Makefile.am +++ b/python.d/Makefile.am @@ -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 index 00000000..4b111f56 --- /dev/null +++ b/python.d/python_modules/lm_sensors.py @@ -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 index 00000000..0807f1aa --- /dev/null +++ b/python.d/sensors.chart.py @@ -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 -- 2.39.2