From 7f801f557a25f61ee63c38b6d0377c662130a426 Mon Sep 17 00:00:00 2001 From: paulfantom Date: Thu, 7 Jul 2016 15:36:19 +0200 Subject: enable different path for `/sys/devices` in `cpufreq.chart.py` --- conf.d/python.d/cpufreq.conf | 1 + python.d/cpufreq.chart.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/conf.d/python.d/cpufreq.conf b/conf.d/python.d/cpufreq.conf index e25910e37d..d08c2df7f0 100644 --- a/conf.d/python.d/cpufreq.conf +++ b/conf.d/python.d/cpufreq.conf @@ -2,3 +2,4 @@ # YAML format update_every : 2 +sys_dir: "/sys/devices" diff --git a/python.d/cpufreq.chart.py b/python.d/cpufreq.chart.py index 8677de544b..b7d912dfe0 100644 --- a/python.d/cpufreq.chart.py +++ b/python.d/cpufreq.chart.py @@ -41,6 +41,11 @@ class Service(SimpleService): return data def check(self): + try: + self.sys_dir = str(self.configuration['sys_dir']) + except (KeyError, TypeError): + self.error("No path to log specified. Using: '" + self.sys_dir + "'") + self._orig_name = self.chart_name for dirpath, _, filenames in os.walk(self.sys_dir): -- cgit v1.2.3 From 8a9b25e9a1c94f76ba748b116c654c5a1f25c3ad Mon Sep 17 00:00:00 2001 From: paulfantom Date: Thu, 7 Jul 2016 18:31:04 +0200 Subject: 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 132e551be8..933b567139 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 0000000000..4b111f5692 --- /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 0000000000..0807f1aa7a --- /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 -- cgit v1.2.3 From 2e6820a9b03a8e99c935bd04c5a0efb6dae7e585 Mon Sep 17 00:00:00 2001 From: paulfantom Date: Thu, 7 Jul 2016 19:04:19 +0200 Subject: fixed sensors --- python.d/python_modules/lm_sensors.py | 8 ++++---- python.d/sensors.chart.py | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/python.d/python_modules/lm_sensors.py b/python.d/python_modules/lm_sensors.py index 4b111f5692..106cf374e7 100644 --- a/python.d/python_modules/lm_sensors.py +++ b/python.d/python_modules/lm_sensors.py @@ -32,10 +32,10 @@ TYPE_DICT = { 5: 'current', 6: 'humidity', 7: 'max_main', - 10: 'vid', - 11: 'intrusion', - 12: 'max_other', - 18: 'beep_enable' + 16: 'vid', + 17: 'intrusion', + 18: 'max_other', + 24: 'beep_enable' } class SensorsError(Exception): diff --git a/python.d/sensors.chart.py b/python.d/sensors.chart.py index 0807f1aa7a..38c4565715 100644 --- a/python.d/sensors.chart.py +++ b/python.d/sensors.chart.py @@ -8,7 +8,7 @@ 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'] +ORDER = ['temperature', 'fan', 'voltage', 'current', 'power', 'energy', 'humidity'] # This is a prototype of chart definition which is used to dynamically create self.definitions CHARTS = { @@ -63,7 +63,7 @@ class Service(SimpleService): prefix = '_'.join(str(chip.path.decode()).split('/')[3:]) lines = {} for feature in chip: - data[prefix + "_" + str(feature.name.decode())] = feature.get_value() + data[prefix + "_" + str(feature.name.decode())] = feature.get_value() * 1000 except Exception as e: self.error(e) return None @@ -79,6 +79,8 @@ class Service(SimpleService): name = "" lines = [] for feature in chip: + if feature.get_value() != 0: + continue if sensors.TYPE_DICT[feature.type] == type: name = str(chip.prefix.decode()) + "_" + sensors.TYPE_DICT[feature.type] if name not in self.order: @@ -90,14 +92,13 @@ class Service(SimpleService): 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) + self.error(e) return False try: self._create_definitions() -- cgit v1.2.3 From 38f585562117504dc77c2901c79b34d79ce964a8 Mon Sep 17 00:00:00 2001 From: paulfantom Date: Thu, 7 Jul 2016 19:07:47 +0200 Subject: minor fixes --- python.d/cpufreq.chart.py | 2 +- python.d/python_modules/lm_sensors.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python.d/cpufreq.chart.py b/python.d/cpufreq.chart.py index b7d912dfe0..5246e3424e 100644 --- a/python.d/cpufreq.chart.py +++ b/python.d/cpufreq.chart.py @@ -44,7 +44,7 @@ class Service(SimpleService): try: self.sys_dir = str(self.configuration['sys_dir']) except (KeyError, TypeError): - self.error("No path to log specified. Using: '" + self.sys_dir + "'") + self.error("No path specified. Using: '" + self.sys_dir + "'") self._orig_name = self.chart_name diff --git a/python.d/python_modules/lm_sensors.py b/python.d/python_modules/lm_sensors.py index 106cf374e7..9da738879b 100644 --- a/python.d/python_modules/lm_sensors.py +++ b/python.d/python_modules/lm_sensors.py @@ -306,4 +306,4 @@ def iter_detected_chips(chip_name='*-*'): result = _get_detected_chips(byref(chip), byref(number)) if not result: break - yield result.contents \ No newline at end of file + yield result.contents -- cgit v1.2.3 From 72ffc4fe21135b9678249672451b12ab83131661 Mon Sep 17 00:00:00 2001 From: paulfantom Date: Fri, 8 Jul 2016 10:30:48 +0200 Subject: select chips + license --- LICENSE.md | 4 ++++ conf.d/python.d/sensors.conf | 14 ++++++++++++++ python.d/sensors.chart.py | 30 +++++++++++++++++++++++++----- 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 conf.d/python.d/sensors.conf diff --git a/LICENSE.md b/LICENSE.md index 380e86eeee..2f4dfe68a0 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -138,4 +138,8 @@ connectivity is not available. Copyright 2015, Joseph Huckaby [MIT License](https://github.com/jhuckaby/pixl-xml) +- [PySensors](https://bitbucket.org/blackjack/pysensors) + + Copyright 2014, Marc 'BlackJack' Rintsch + [LGPL 2.1 License](https://bitbucket.org/blackjack/pysensors) diff --git a/conf.d/python.d/sensors.conf b/conf.d/python.d/sensors.conf new file mode 100644 index 0000000000..8d4965182d --- /dev/null +++ b/conf.d/python.d/sensors.conf @@ -0,0 +1,14 @@ +# Example configuration of sensors.chart.py +# YAML format + +#update_every: 2 +#types: +# - temperature +# - fan +# - voltage +# - current +# - power +# - energy +# - humidity +#chips: +# - i8k diff --git a/python.d/sensors.chart.py b/python.d/sensors.chart.py index 38c4565715..b805b1bf0d 100644 --- a/python.d/sensors.chart.py +++ b/python.d/sensors.chart.py @@ -55,6 +55,7 @@ class Service(SimpleService): SimpleService.__init__(self, configuration=configuration, name=name) self.order = [] self.definitions = {} + self.chips = [] def _get_data(self): data = {} @@ -78,14 +79,17 @@ class Service(SimpleService): prefix = '_'.join(str(chip.path.decode()).split('/')[3:]) name = "" lines = [] + pref = str(chip.prefix.decode()) + if len(self.chips) != 0 and not any([ex.startswith(pref) for ex in self.chips]): + continue for feature in chip: - if feature.get_value() != 0: + if feature.get_value() == 0: continue if sensors.TYPE_DICT[feature.type] == type: - name = str(chip.prefix.decode()) + "_" + sensors.TYPE_DICT[feature.type] + name = pref + "_" + sensors.TYPE_DICT[feature.type] if name not in self.order: options = list(CHARTS[type]['options']) - options[1] = str(chip.prefix) + options[1] + options[1] = pref + options[1] self.definitions[name] = {'options': options} self.definitions[name]['lines'] = [] self.order.append(name) @@ -95,6 +99,16 @@ class Service(SimpleService): self.definitions[name]['lines'].append(line) def check(self): + try: + self.chips = list(self.configuration['chips']) + except (KeyError, TypeError): + self.error("No path to log specified. Using all chips.") + try: + global ORDER + ORDER = list(self.configuration['types']) + except (KeyError, TypeError): + self.error("No path to log specified. Using all sensor types.") + print(ORDER) try: sensors.init() except Exception as e: @@ -102,6 +116,12 @@ class Service(SimpleService): return False try: self._create_definitions() - except: + except Exception as e: + print(e) return False - return True \ No newline at end of file + + if len(self.definitions) == 0: + self.error("No sensors found") + return False + + return True -- cgit v1.2.3 From 72fc6382c368a2b2934dbabbab5c90a27b98484f Mon Sep 17 00:00:00 2001 From: paulfantom Date: Fri, 8 Jul 2016 10:33:09 +0200 Subject: remove debugging info --- python.d/sensors.chart.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python.d/sensors.chart.py b/python.d/sensors.chart.py index b805b1bf0d..edfe125dbd 100644 --- a/python.d/sensors.chart.py +++ b/python.d/sensors.chart.py @@ -108,7 +108,6 @@ class Service(SimpleService): ORDER = list(self.configuration['types']) except (KeyError, TypeError): self.error("No path to log specified. Using all sensor types.") - print(ORDER) try: sensors.init() except Exception as e: @@ -116,8 +115,7 @@ class Service(SimpleService): return False try: self._create_definitions() - except Exception as e: - print(e) + except: return False if len(self.definitions) == 0: -- cgit v1.2.3 From d94a6b8dab1f1a9e2cf90a02d30864b96a8e5c51 Mon Sep 17 00:00:00 2001 From: paulfantom Date: Fri, 8 Jul 2016 10:34:33 +0200 Subject: conf makefile --- conf.d/Makefile.am | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conf.d/Makefile.am b/conf.d/Makefile.am index ea19e0ab80..dd5d510256 100644 --- a/conf.d/Makefile.am +++ b/conf.d/Makefile.am @@ -29,8 +29,7 @@ dist_pythonconfig_DATA = \ python.d/nginx.conf \ python.d/phpfpm.conf \ python.d/postfix.conf \ + python.d/sensors.conf \ python.d/squid.conf \ python.d/tomcat.conf \ $(NULL) - - -- cgit v1.2.3