summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCosta Tsaousis <costa@tsaousis.gr>2016-07-08 11:48:21 +0300
committerGitHub <noreply@github.com>2016-07-08 11:48:21 +0300
commitf52e3f009b0c2140e3416058168f70cc090e6e4b (patch)
tree4cf59bb624d38d1859b28137ce9906c1269a47e1
parentc2f188f6db995d19c85823b829585d804a8ab026 (diff)
parentd94a6b8dab1f1a9e2cf90a02d30864b96a8e5c51 (diff)
Merge pull request #666 from paulfantom/master
python `sensors` module
-rw-r--r--LICENSE.md4
-rw-r--r--conf.d/Makefile.am3
-rw-r--r--conf.d/python.d/cpufreq.conf1
-rw-r--r--conf.d/python.d/sensors.conf14
-rw-r--r--python.d/Makefile.am3
-rw-r--r--python.d/cpufreq.chart.py5
-rw-r--r--python.d/python_modules/lm_sensors.py309
-rw-r--r--python.d/sensors.chart.py125
8 files changed, 461 insertions, 3 deletions
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/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)
-
-
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/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/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/cpufreq.chart.py b/python.d/cpufreq.chart.py
index 8677de544b..5246e3424e 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 specified. Using: '" + self.sys_dir + "'")
+
self._orig_name = self.chart_name
for dirpath, _, filenames in os.walk(self.sys_dir):
diff --git a/python.d/python_modules/lm_sensors.py b/python.d/python_modules/lm_sensors.py
new file mode 100644
index 0000000000..9da738879b
--- /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',
+ 16: 'vid',
+ 17: 'intrusion',
+ 18: 'max_other',
+ 24: '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
diff --git a/python.d/sensors.chart.py b/python.d/sensors.chart.py
new file mode 100644
index 0000000000..edfe125dbd
--- /dev/null
+++ b/python.d/sensors.chart.py
@@ -0,0 +1,125 @@
+# -*- 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', 'fan', 'voltage', 'current', 'power', '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 = {}
+ self.chips = []
+
+ 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() * 1000
+ 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 = []
+ 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:
+ continue
+ if sensors.TYPE_DICT[feature.type] == type:
+ name = pref + "_" + sensors.TYPE_DICT[feature.type]
+ if name not in self.order:
+ options = list(CHARTS[type]['options'])
+ options[1] = pref + 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)
+ 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.")
+ try:
+ sensors.init()
+ except Exception as e:
+ self.error(e)
+ return False
+ try:
+ self._create_definitions()
+ except:
+ return False
+
+ if len(self.definitions) == 0:
+ self.error("No sensors found")
+ return False
+
+ return True