]> arthur.barton.de Git - netdata.git/commitdiff
Merge pull request #774 from paulfantom/master
authorPaweł Krupa <paulfantom@gmail.com>
Sun, 14 Aug 2016 21:54:41 +0000 (23:54 +0200)
committerGitHub <noreply@github.com>
Sun, 14 Aug 2016 21:54:41 +0000 (23:54 +0200)
New python modules + bug fixes

13 files changed:
LICENSE.md
conf.d/Makefile.am
conf.d/python.d.conf
conf.d/python.d/hddtemp.conf
conf.d/python.d/nginx_log.conf [new file with mode: 0644]
plugins.d/python.d.plugin
python.d/Makefile.am
python.d/hddtemp.chart.py
python.d/nginx_log.chart.py [new file with mode: 0644]
python.d/python_modules/base.py
python.d/python_modules/lm_sensors.py
python.d/python_modules/msg.py
python.d/sensors.chart.py

index a79fbf80c29bbf65c196b15c0a24a163064b9369..9b6415d1092868ec30c56b0794ea0115e5babc51 100644 (file)
@@ -138,10 +138,10 @@ connectivity is not available.
     Copyright 2015, Joseph Huckaby
     [MIT License](https://github.com/jhuckaby/pixl-xml)
     
-- [PySensors](https://bitbucket.org/blackjack/pysensors)
+- [sensors](https://github.com/paroj/sensors.py)
 
-    Copyright 2014, Marc 'BlackJack' Rintsch
-    [LGPL 2.1 License](https://bitbucket.org/blackjack/pysensors)
+    Copyright 2014, Pavel Rojtberg
+    [LGPL 2.1 License](http://opensource.org/licenses/LGPL-2.1)
 
 - [PyYAML](https://bitbucket.org/blackjack/pysensors)
 
index 6be4945e6c440189345c98af54af5d5ba4d7568b..f628a042bee0049dbe36583cdfb9206b19fea0ce 100644 (file)
@@ -30,6 +30,7 @@ dist_pythonconfig_DATA = \
        python.d/memcached.conf \
        python.d/mysql.conf \
        python.d/nginx.conf \
+       python.d/nginx_log.conf \
        python.d/phpfpm.conf \
        python.d/postfix.conf \
        python.d/redis.conf \
index 18558fbd2294f1ae33308ede95949d0a4583b319..940bd918345b6c9cdcae2ac1e6fc14760dace610 100644 (file)
@@ -9,6 +9,13 @@
 # Enable / disable the whole python.d.plugin (all its modules)
 enabled: yes
 
+# Prevent log flood
+# Define how many log messages can be written to log file in one log_interval
+logs_per_interval: 200
+
+# Define how long is one logging interval (in seconds)
+log_interval: 3600
+
 # ----------------------------------------------------------------------
 # Enable / Disable python.d.plugin modules
 #
@@ -18,12 +25,15 @@ enabled: yes
 # apache: yes
 # apache_cache: yes
 # cpufreq: yes
+# dovecot: yes
 example: no
 # exim: yes
 # hddtemp: yes
 # ipfs: yes
+# memcached: yes
 # mysql: yes
 # nginx: yes
+# nginx_log: yes
 # phpfpm: yes
 # postfix: yes
 # redis: yes
index f3cb667a5e5b0256b636d89dd003a7a475cabfd4..0c78449b42eaecdc589a25f812d9a2fb013607c2 100644 (file)
 #     port: PORT             # the port to connect to
 #
 
-# By default this module will try to autodetect number of disks.
-# However this can be overridden by setting variable `disk_count` to
-# desired number of disks. Example for two disks:
+# By default this module will try to autodetect disks
+# (autodetection works only for disk which names start with "sd").
+# However this can be overridden by setting variable `disks` to
+# array of desired disks. Example for two disks:
 #
-# disk_count: 2
+# devices:
+#   - sda
+#   - sdb
 #
 
 # ----------------------------------------------------------------------
diff --git a/conf.d/python.d/nginx_log.conf b/conf.d/python.d/nginx_log.conf
new file mode 100644 (file)
index 0000000..6a53c52
--- /dev/null
@@ -0,0 +1,72 @@
+# netdata python.d.plugin configuration for nginx log
+#
+# This file is in YaML format. Generally the format is:
+#
+# name: value
+#
+# There are 2 sections:
+#  - global variables
+#  - one or more JOBS
+#
+# JOBS allow you to collect values from multiple sources.
+# Each source will have its own set of charts.
+#
+# JOB parameters have to be indented (using spaces only, example below).
+
+# ----------------------------------------------------------------------
+# Global Variables
+# These variables set the defaults for all JOBs, however each JOB
+# may define its own, overriding the defaults.
+
+# update_every sets the default data collection frequency.
+# If unset, the python.d.plugin default is used.
+# update_every: 1
+
+# priority controls the order of charts at the netdata dashboard.
+# Lower numbers move the charts towards the top of the page.
+# If unset, the default for python.d.plugin is used.
+# priority: 60000
+
+# retries sets the number of retries to be made in case of failures.
+# If unset, the default for python.d.plugin is used.
+# Attempts to restore the service are made once every update_every
+# and only if the module has collected values in the past.
+# retries: 5
+
+# ----------------------------------------------------------------------
+# JOBS (data collection sources)
+#
+# The default JOBS share the same *name*. JOBS with the same name
+# are mutually exclusive. Only one of them will be allowed running at
+# any time. This allows autodetection to try several alternatives and
+# pick the one that works.
+#
+# Any number of jobs is supported.
+#
+# All python.d.plugin JOBS (for all its modules) support a set of
+# predefined parameters. These are:
+#
+# job_name:
+#     name: myname     # the JOB's name as it will appear at the
+#                      # dashboard (by default is the job_name)
+#                      # JOBs sharing a name are mutually exclusive
+#     update_every: 1  # the JOB's data collection frequency
+#     priority: 60000  # the JOB's order on the dashboard
+#     retries: 5       # the JOB's number of restoration attempts
+#
+# Additionally to the above, nginx_log also supports the following:
+#
+#     path: 'PATH'     # the path to nginx's access.log
+#
+
+# ----------------------------------------------------------------------
+# AUTO-DETECTION JOBS
+# only one of them will run (they have the same name)
+
+nginx_log:
+  name: 'local'
+  path: '/var/log/nginx/access.log'
+
+nginx_log2:
+  name: 'local'
+  path: '/var/log/nginx/nginx-access.log'
index 1301777bee9debbd70108b093a4116297b8d459c..5a8768d42321c95656dfb1a52335007d9b16a9d3 100755 (executable)
@@ -495,6 +495,14 @@ def run():
             DEBUG_FLAG = conf['debug']
         except (KeyError, TypeError):
             pass
+        try:
+            log_counter = conf['logs_per_interval']
+        except (KeyError, TypeError):
+            log_counter = 200
+        try:
+            log_interval = conf['log_interval']
+        except (KeyError, TypeError):
+            log_interval = 3600
         for k, v in conf.items():
             if k in ("update_every", "debug", "enabled"):
                 continue
@@ -504,6 +512,8 @@ def run():
     # parse passed command line arguments
     modules = parse_cmdline(MODULES_DIR, *sys.argv)
     msg.DEBUG_FLAG = DEBUG_FLAG
+    msg.LOG_COUNTER = log_counter
+    msg.LOG_INTERVAL = log_interval
     msg.info("MODULES_DIR='" + MODULES_DIR +
              "', CONFIG_DIR='" + CONFIG_DIR +
              "', UPDATE_EVERY=" + str(BASE_CONFIG['update_every']) +
index 57d89bad251ed1510bcd6d573f83c92db5d68052..8bccba3780d706fce07c682c030b311ff484dd72 100644 (file)
@@ -19,6 +19,7 @@ dist_python_SCRIPTS = \
        memcached.chart.py \
        mysql.chart.py \
        nginx.chart.py \
+       nginx_log.chart.py \
        phpfpm.chart.py \
        postfix.chart.py \
        redis.chart.py \
index beef64161645ff47363f3f4109ace960fe4e3514..8fe592beb696ceda5e83fcdefbc0492675ddee63 100644 (file)
@@ -39,23 +39,31 @@ class Service(SocketService):
         self.port = 7634
         self.order = ORDER
         self.definitions = CHARTS
-        self.disk_count = 1
-        self.exclude = []
+        self.disks = []
 
-    def _get_disk_count(self):
-        all_disks = [f for f in os.listdir("/dev") if len(f) == 3 and f.startswith("sd")]
-        for disk in self.exclude:
-            try:
-                all_disks.remove(disk)
-            except:
-                self.debug("Disk not found")
-        return len(all_disks)
+    def _get_disks(self):
+        try:
+            disks = self.configuration['devices']
+            print(disks)
+        except (KeyError, TypeError) as e:
+            self.info("Autodetecting disks")
+            return ["/dev/" + f for f in os.listdir("/dev") if len(f) == 3 and f.startswith("sd")]
+
+        ret = []
+        for disk in disks:
+            if not disk.startswith('/dev/'):
+                disk = "/dev/" + disk
+            if os.path.exists(disk):
+                ret.append(disk)
+        if len(ret) == 0:
+            self.error("Provided disks cannot be found in /dev directory.")
+        return ret
 
     def _check_raw_data(self, data):
         if not data.endswith('|'):
             return False
 
-        if data.count('|') % (5 * self.disk_count) == 0:
+        if all(disk in data for disk in self.disks):
             return True
 
         return False
@@ -72,6 +80,8 @@ class Service(SocketService):
             return None
         data = {}
         for i in range(len(raw) // 5):
+            if not raw[i*5+1] in self.disks:
+                continue
             try:
                 val = int(raw[i*5+3])
             except ValueError:
@@ -90,18 +100,7 @@ class Service(SocketService):
         :return: boolean
         """
         self._parse_config()
-        try:
-            self.exclude = list(self.configuration['exlude'])
-        except (KeyError, TypeError) as e:
-            self.info("No excluded disks")
-            self.debug(str(e))
-
-        try:
-            self.disk_count = int(self.configuration['disk_count'])
-        except (KeyError, TypeError) as e:
-            self.info("Autodetecting number of disks")
-            self.disk_count = self._get_disk_count()
-            self.debug(str(e))
+        self.disks = self._get_disks()
 
         data = self._get_data()
         if data is None:
diff --git a/python.d/nginx_log.chart.py b/python.d/nginx_log.chart.py
new file mode 100644 (file)
index 0000000..ea9f10b
--- /dev/null
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# Description: nginx log netdata python.d module
+# Author: Pawel Krupa (paulfantom)
+
+from base import LogService
+import re
+
+priority = 60000
+retries = 60
+# update_every = 3
+
+ORDER = ['codes']
+CHARTS = {
+    'codes': {
+        'options': [None, 'nginx status codes', 'requests/s', 'requests', 'nginx_log.codes', 'stacked'],
+        'lines': [
+            ["20X", None, "incremental"],
+            ["30X", None, "incremental"],
+            ["40X", None, "incremental"],
+            ["50X", None, "incremental"]
+        ]}
+}
+
+
+class Service(LogService):
+    def __init__(self, configuration=None, name=None):
+        LogService.__init__(self, configuration=configuration, name=name)
+        if len(self.log_path) == 0:
+            self.log_path = "/var/log/nginx/access.log"
+        self.order = ORDER
+        self.definitions = CHARTS
+        pattern = r'" ([0-9]{3}) ?'
+        #pattern = r'(?:" )([0-9][0-9][0-9]) ?'
+        self.regex = re.compile(pattern)
+
+    def _get_data(self):
+        """
+        Parse new log lines
+        :return: dict
+        """
+        data = {'20X': 0,
+                '30X': 0,
+                '40X': 0,
+                '50X': 0}
+        try:
+            raw = self._get_raw_data()
+            if raw is None:
+                return None
+            elif not raw:
+                return data
+        except (ValueError, AttributeError):
+            return None
+
+        regex = self.regex
+        for line in raw:
+            code = regex.search(line)
+            beginning = code.group(1)[0]
+
+            if beginning == '2':
+                data["20X"] += 1
+            elif beginning == '3':
+                data["30X"] += 1
+            elif beginning == '4':
+                data["40X"] += 1
+            elif beginning == '5':
+                data["50X"] += 1
+
+        return data
+
index 87c55830c32e3293d0ae1639149c815cb1a0c790..324d3e591c14958769ece5e4e741e7a26c4268f3 100644 (file)
@@ -672,6 +672,9 @@ class SocketService(SimpleService):
             self.debug("No request specified. Using: '" + str(self.request) + "'")
         self.request = self.request.encode()
 
+    def check(self):
+        return SimpleService.check(self)
+
 
 class LogService(SimpleService):
     def __init__(self, configuration=None, name=None):
@@ -775,20 +778,20 @@ class ExecutableService(SimpleService):
             self.command = str(self.configuration['command'])
         except (KeyError, TypeError):
             self.error("No command specified. Using: '" + self.command + "'")
-        self.command = self.command.split(' ')
+        command = self.command.split(' ')
 
-        for arg in self.command[1:]:
+        for arg in command[1:]:
             if any(st in arg for st in self.bad_substrings):
                 self.error("Bad command argument:" + " ".join(self.command[1:]))
                 return False
         # test command and search for it in /usr/sbin or /sbin when failed
-        base = self.command[0].split('/')[-1]
+        base = command[0].split('/')[-1]
         if self._get_raw_data() is None:
             for prefix in ['/sbin/', '/usr/sbin/']:
-                self.command[0] = prefix + base
-                if os.path.isfile(self.command[0]):
+                command[0] = prefix + base
+                if os.path.isfile(command[0]):
                     break
-
+        self.command = command
         if self._get_data() is None or len(self._get_data()) == 0:
             self.error("Command", self.command, "returned no data")
             return False
index 9da738879bcb061d8d273be193f704620b2521e6..1d868f0e249ad2c50f2084b113c5161ac4e202b7 100644 (file)
-#!/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
-        )
+"""
+@package sensors.py
+Python Bindings for libsensors3
+
+use the documentation of libsensors for the low level API.
+see example.py for high level API usage.
+
+@author: Pavel Rojtberg (http://www.rojtberg.net)
+@see: https://github.com/paroj/sensors.py
+@copyright: LGPLv2 (same as libsensors) <http://opensource.org/licenses/LGPL-2.1>
+"""
+
+from ctypes import *
+import ctypes.util
+
+_libc = cdll.LoadLibrary(ctypes.util.find_library("c"))
+# see https://github.com/paroj/sensors.py/issues/1
+_libc.free.argtypes = [c_void_p]
+_hdl = cdll.LoadLibrary(ctypes.util.find_library("sensors"))
+
+version = c_char_p.in_dll(_hdl, "libsensors_version").value.decode("ascii")
+
+
+class bus_id(Structure):
+    _fields_ = [("type", c_short),
+                ("nr", c_short)]
+
+
+class chip_name(Structure):
+    _fields_ = [("prefix", c_char_p),
+                ("bus", bus_id),
+                ("addr", c_int),
+                ("path", c_char_p)]
+
+
+class feature(Structure):
+    _fields_ = [("name", c_char_p),
+                ("number", c_int),
+                ("type", c_int)]
+
+    # sensors_feature_type
+    IN = 0x00
+    FAN = 0x01
+    TEMP = 0x02
+    POWER = 0x03
+    ENERGY = 0x04
+    CURR = 0x05
+    HUMIDITY = 0x06
+    MAX_MAIN = 0x7
+    VID = 0x10
+    INTRUSION = 0x11
+    MAX_OTHER = 0x12
+    BEEP_ENABLE = 0x18
+
+
+class subfeature(Structure):
+    _fields_ = [("name", c_char_p),
+                ("number", c_int),
+                ("type", c_int),
+                ("mapping", c_int),
+                ("flags", c_uint)]
+
+
+_hdl.sensors_get_detected_chips.restype = POINTER(chip_name)
+_hdl.sensors_get_features.restype = POINTER(feature)
+_hdl.sensors_get_all_subfeatures.restype = POINTER(subfeature)
+_hdl.sensors_get_label.restype = c_void_p  # return pointer instead of str so we can free it
+_hdl.sensors_get_adapter_name.restype = c_char_p  # docs do not say whether to free this or not
+_hdl.sensors_strerror.restype = c_char_p
+
+### RAW API ###
+MODE_R = 1
+MODE_W = 2
+COMPUTE_MAPPING = 4
+
+
+def init(cfg_file=None):
+    file = _libc.fopen(cfg_file.encode("utf-8"), "r") if cfg_file is not None else None
+
+    if _hdl.sensors_init(file) != 0:
+        raise Exception("sensors_init failed")
+
+    if file is not None:
+        _libc.fclose(file)
+
+
+def cleanup():
+    _hdl.sensors_cleanup()
+
+
+def parse_chip_name(orig_name):
+    ret = chip_name()
+    err = _hdl.sensors_parse_chip_name(orig_name.encode("utf-8"), byref(ret))
+
+    if err < 0:
+        raise Exception(strerror(err))
+
+    return ret
+
+
+def strerror(errnum):
+    return _hdl.sensors_strerror(errnum).decode("utf-8")
+
+
+def free_chip_name(chip):
+    _hdl.sensors_free_chip_name(byref(chip))
+
+
+def get_detected_chips(match, nr):
+    """
+    @return: (chip, next nr to query)
+    """
+    _nr = c_int(nr)
+
+    if match is not None:
+        match = byref(match)
+
+    chip = _hdl.sensors_get_detected_chips(match, byref(_nr))
+    chip = chip.contents if bool(chip) else None
+    return chip, _nr.value
+
+
+def chip_snprintf_name(chip, buffer_size=200):
+    """
+    @param buffer_size defaults to the size used in the sensors utility
+    """
+    ret = create_string_buffer(buffer_size)
+    err = _hdl.sensors_snprintf_chip_name(ret, buffer_size, byref(chip))
+
+    if err < 0:
+        raise Exception(strerror(err))
+
+    return ret.value.decode("utf-8")
+
+
+def do_chip_sets(chip):
+    """
+    @attention this function was not tested
+    """
+    err = _hdl.sensors_do_chip_sets(byref(chip))
+    if err < 0:
+        raise Exception(strerror(err))
+
+
+def get_adapter_name(bus):
+    return _hdl.sensors_get_adapter_name(byref(bus)).decode("utf-8")
+
+
+def get_features(chip, nr):
+    """
+    @return: (feature, next nr to query)
+    """
+    _nr = c_int(nr)
+    feature = _hdl.sensors_get_features(byref(chip), byref(_nr))
+    feature = feature.contents if bool(feature) else None
+    return feature, _nr.value
+
+
+def get_label(chip, feature):
+    ptr = _hdl.sensors_get_label(byref(chip), byref(feature))
+    val = cast(ptr, c_char_p).value.decode("utf-8")
+    _libc.free(ptr)
+    return val
+
+
+def get_all_subfeatures(chip, feature, nr):
+    """
+    @return: (subfeature, next nr to query)
+    """
+    _nr = c_int(nr)
+    subfeature = _hdl.sensors_get_all_subfeatures(byref(chip), byref(feature), byref(_nr))
+    subfeature = subfeature.contents if bool(subfeature) else None
+    return subfeature, _nr.value
+
+
+def get_value(chip, subfeature_nr):
+    val = c_double()
+    err = _hdl.sensors_get_value(byref(chip), subfeature_nr, byref(val))
+    if err < 0:
+        raise Exception(strerror(err))
+    return val.value
+
+
+def set_value(chip, subfeature_nr, value):
+    """
+    @attention this function was not tested
+    """
+    val = c_double(value)
+    err = _hdl.sensors_set_value(byref(chip), subfeature_nr, byref(val))
+    if err < 0:
+        raise Exception(strerror(err))
+
+
+### Convenience API ###
+class ChipIterator:
+    def __init__(self, match=None):
+        self.match = parse_chip_name(match) if match is not None else None
+        self.nr = 0
 
     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
+        return self
+
+    def __next__(self):
+        chip, self.nr = get_detected_chips(self.match, self.nr)
+
+        if chip is None:
+            raise StopIteration
+
+        return chip
 
     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')
+        if self.match is not None:
+            free_chip_name(self.match)
+
+    def next(self):  # python2 compability
+        return self.__next__()
+
+
+class FeatureIterator:
+    def __init__(self, chip):
+        self.chip = chip
+        self.nr = 0
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        feature, self.nr = get_features(self.chip, self.nr)
+
+        if feature is None:
+            raise StopIteration
+
+        return feature
+
+    def next(self):  # python2 compability
+        return self.__next__()
+
+
+class SubFeatureIterator:
+    def __init__(self, chip, feature):
+        self.chip = chip
+        self.feature = feature
+        self.nr = 0
 
     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
+        return self
+
+    def __next__(self):
+        subfeature, self.nr = get_all_subfeatures(self.chip, self.feature, self.nr)
+
+        if subfeature is None:
+            raise StopIteration
+
+        return subfeature
+
+    def next(self):  # python2 compability
+        return self.__next__()
\ No newline at end of file
index 5ff4e79ba92b7ead2d28bd13f79d5324f4760898..ecefba0a4fa185b38cb4e02b5a1c8b4f07651dc4 100644 (file)
@@ -2,9 +2,16 @@
 # Description: logging for netdata python.d modules
 
 import sys
+from time import time, strftime
 
 DEBUG_FLAG = False
 PROGRAM = ""
+LOG_COUNTER = 2
+LOG_INTERVAL = 5
+NEXT_CHECK = 0
+
+WRITE = sys.stderr.write
+FLUSH = sys.stderr.flush
 
 
 def log_msg(msg_type, *args):
@@ -12,10 +19,24 @@ def log_msg(msg_type, *args):
     Print message on stderr.
     :param msg_type: str
     """
-    msg = "%s %s: %s" % (PROGRAM, str(msg_type), " ".join(args))
+    global LOG_COUNTER
+    if not DEBUG_FLAG:
+        LOG_COUNTER -= 1
+    now = time()
+    if LOG_COUNTER >= 0:
+        timestamp = strftime('%y-%m-%d %X')
+        msg = "%s: %s %s: %s" % (timestamp, PROGRAM, str(msg_type), " ".join(args))
+        WRITE(msg + "\n")
+        FLUSH()
 
-    sys.stderr.write(msg + "\n")
-    sys.stderr.flush()
+    global NEXT_CHECK
+    if NEXT_CHECK <= now:
+        NEXT_CHECK = now - (now % LOG_INTERVAL) + LOG_INTERVAL
+        if LOG_COUNTER < 0:
+            timestamp = strftime('%y-%m-%d %X')
+            msg = "%s: Prevented %s log messages from displaying" % (timestamp, str(0 - LOG_COUNTER))
+            WRITE(msg + "\n")
+            FLUSH()
 
 
 def debug(*args):
@@ -47,5 +68,7 @@ def fatal(*args):
     Print message on stderr and exit.
     """
     log_msg("FATAL", *args)
-    sys.stdout.write('DISABLE\n')
+    # using sys.stdout causes IOError: Broken Pipe
+    print('DISABLE')
+    # sys.stdout.write('DISABLE\n')
     sys.exit(1)
\ No newline at end of file
index 1288931add6a8d3c56e5663526c54d89c00b3944..8078fcf8521399c64ad19cd8ad0006dc0ec3ece7 100644 (file)
@@ -3,7 +3,7 @@
 # Author: Pawel Krupa (paulfantom)
 
 from base import SimpleService
-import lm_sensors as sensors
+import new_sensors as sensors
 
 # default module values (can be overridden per job in `config`)
 # update_every = 2
@@ -49,6 +49,21 @@ CHARTS = {
         ]}
 }
 
+TYPE_MAP = {
+    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 Service(SimpleService):
     def __init__(self, configuration=None, name=None):
@@ -60,11 +75,14 @@ class Service(SimpleService):
     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
+            for chip in sensors.ChipIterator():
+                prefix = sensors.chip_snprintf_name(chip)
+                for feature in sensors.FeatureIterator(chip):
+                    sfi = sensors.SubFeatureIterator(chip, feature)
+                    for sf in sfi:
+                        val = sensors.get_value(chip, sf.number)
+                        break
+                    data[prefix + "_" + str(feature.name.decode())] = int(val * 1000)
         except Exception as e:
             self.error(e)
             return None
@@ -74,57 +92,43 @@ class Service(SimpleService):
         return data
 
     def _create_definitions(self):
+        prev_chip = ""
         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]):
+            for chip in sensors.ChipIterator():
+                chip_name = sensors.chip_snprintf_name(chip)
+                if len(self.chips) != 0 and not any([chip_name.startswith(ex) for ex in self.chips]):
                     continue
-                pref = pref + '_' + str(chip.addr)
-                for feature in chip:
-                    try:
-                        float(feature.get_value())
-                    except ValueError:
-                        continue
-                    if feature.get_value() < 0:
+                for feature in sensors.FeatureIterator(chip):
+                    sfi = sensors.SubFeatureIterator(chip, feature)
+                    vals = [sensors.get_value(chip, sf.number) for sf in sfi]
+                    if vals[0] == 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)
+                    if TYPE_MAP[feature.type] == type:
+                        # create chart
+                        if chip_name != prev_chip:
+                            name = chip_name + "_" + TYPE_MAP[feature.type]
+                            if name not in self.order:
+                                self.order.append(name)
+                                chart_def = list(CHARTS[type]['options'])
+                                chart_def[1] = chip_name + chart_def[1]
+                                self.definitions[name] = {'options': chart_def}
+                                self.definitions[name]['lines'] = []
                         line = list(CHARTS[type]['lines'][0])
-                        line[0] = prefix + "_" + str(feature.name.decode())
-                        line[1] = str(feature.label)
+                        line[0] = chip_name + "_" + str(feature.name.decode())
+                        line[1] = sensors.get_label(chip, feature)
                         self.definitions[name]['lines'].append(line)
+                prev_chip = chip_name
 
     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")
+        except Exception as e:
+            self.error(e)
             return False
-
       return True
+        return True
No newline at end of file