diff options
author | Costa Tsaousis <costa@tsaousis.gr> | 2018-07-12 02:09:19 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-12 02:09:19 +0300 |
commit | a1e6fab43f67022857fd979de0368da2b4e499b3 (patch) | |
tree | d1796cdcb3c46819be4b3bfabedd7abeaef3db1e | |
parent | 546e3601cd9a508a88b5cba64a0d4dad0a77112f (diff) | |
parent | be414540bc5e3730c0cb3a70476db8587db868a7 (diff) |
Merge branch 'master' into ms_team_notification_support
-rw-r--r-- | CONTRIBUTORS.md | 7 | ||||
-rw-r--r-- | conf.d/Makefile.am | 2 | ||||
-rw-r--r-- | conf.d/health.d/megacli.conf | 48 | ||||
-rw-r--r-- | conf.d/python.d/megacli.conf | 68 | ||||
-rw-r--r-- | configs.signatures | 3 | ||||
-rwxr-xr-x | netdata-installer.sh | 1 | ||||
-rw-r--r-- | python.d/Makefile.am | 1 | ||||
-rw-r--r-- | python.d/README.md | 29 | ||||
-rw-r--r-- | python.d/megacli.chart.py | 280 | ||||
-rw-r--r-- | python.d/python_modules/bases/FrameworkServices/ExecutableService.py | 6 | ||||
-rw-r--r-- | python.d/python_modules/bases/loaders.py | 12 |
11 files changed, 452 insertions, 5 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 31d74c06a1..72c5fe5be5 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -65,11 +65,13 @@ This is the list of contributors that have signed this agreement: username|name|email (optional) :--------:|:----:|:---------------- +@lets00|Luís Eduardo|leduardo@lsd.ufcg.edu.br @ktsaou|Costa Tsaousis|costa@tsaousis.gr @tycho|Steven Noonan|steven@uplinklabs.net @philwhineray|Phil Whineray| @paulfantom|Pawel Krupa|pawel@krupa.net.pl @Ferroin|Austin S. Hemmelgarn|ahferroin7@gmail.com +@glensc|Elan Ruusamäe| @l2isbad|Ilya Mashchenko|ilyamaschenko@gmail.com @rlefevre|Rémi Lefèvre| @vlvkobal|Vladimir Kobal|vlad@prokk.net @@ -94,3 +96,8 @@ username|name|email (optional) @Flums|Philip Gabrielsen|philip@digno.no @domschl|Dominik Schlösser|dominik.schloesser@gmail.com @tioumen|Guillaume Hospital| +@arch273|Jacob Ayres +@x4FF3|David Fuellgraf| +@jasonwbarnett|Jason Barnett| +@ecowed|Ed Wade| +@wungad|Rob Man| diff --git a/conf.d/Makefile.am b/conf.d/Makefile.am index 7bd3622f65..0026e3f130 100644 --- a/conf.d/Makefile.am +++ b/conf.d/Makefile.am @@ -53,6 +53,7 @@ dist_pythonconfig_DATA = \ python.d/litespeed.conf \ python.d/logind.conf \ python.d/mdstat.conf \ + python.d/megacli.conf \ python.d/memcached.conf \ python.d/mongodb.conf \ python.d/mysql.conf \ @@ -111,6 +112,7 @@ dist_healthconfig_DATA = \ health.d/isc_dhcpd.conf \ health.d/lighttpd.conf \ health.d/mdstat.conf \ + health.d/megacli.conf \ health.d/memcached.conf \ health.d/memory.conf \ health.d/mongodb.conf \ diff --git a/conf.d/health.d/megacli.conf b/conf.d/health.d/megacli.conf new file mode 100644 index 0000000000..1881a7be14 --- /dev/null +++ b/conf.d/health.d/megacli.conf @@ -0,0 +1,48 @@ + alarm: adapter_state + on: megacli.adapter_degraded + units: is degraded + lookup: sum -10s + every: 10s + crit: $this > 0 + info: adapter state + to: sysadmin + + template: bbu_relative_charge + on: megacli.bbu_relative_charge + units: percent + lookup: average -10s + every: 10s + warn: $this <= (($status >= $WARNING) ? (85) : (80)) + crit: $this <= (($status == $CRITICAL) ? (50) : (40)) + info: BBU relative state of charge + to: sysadmin + + template: bbu_cycle_count + on: megacli.bbu_cycle_count + units: cycle count + lookup: average -10s + every: 10s + warn: $this >= 100 + crit: $this >= 500 + info: BBU cycle count + to: sysadmin + + alarm: pd_media_errors + on: megacli.pd_media_error + units: media errors + lookup: sum -10s + every: 10s + warn: $this > 0 + delay: down 1m multiplier 2 max 10m + info: physical drive media errors + to: sysadmin + + alarm: pd_predictive_failures + on: megacli.pd_predictive_failure + units: predictive failures + lookup: sum -10s + every: 10s + warn: $this > 0 + delay: down 1m multiplier 2 max 10m + info: physical drive predictive failures + to: sysadmin diff --git a/conf.d/python.d/megacli.conf b/conf.d/python.d/megacli.conf new file mode 100644 index 0000000000..d84078ecb1 --- /dev/null +++ b/conf.d/python.d/megacli.conf @@ -0,0 +1,68 @@ +# netdata python.d.plugin configuration for megacli +# +# This file is in YaML format. Generally the format is: +# +# name: value +# + +# ---------------------------------------------------------------------- +# 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: 60 + +# autodetection_retry sets the job re-check interval in seconds. +# The job is not deleted if check fails. +# Attempts to start the job are made once every autodetection_retry. +# This feature is disabled by default. +# autodetection_retry: 0 + +# ---------------------------------------------------------------------- +# 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: 60 # the JOB's number of restoration attempts +# autodetection_retry: 0 # the JOB's re-check interval in seconds +# +# Additionally to the above, megacli also supports the following: +# +# do_battery: yes/no # default is no. Battery stats (adds additional call to megacli `megacli -AdpBbuCmd -a0`). +# +# ---------------------------------------------------------------------- + +# IMPORTANT +# The netdata user needs to be able to be able to sudo the megacli program without password: +# netdata ALL=(root) NOPASSWD: /path/to/megacli + + +# uncomment the line below to collect battery statistics +# do_battery: yes diff --git a/configs.signatures b/configs.signatures index 04b2a79205..9f1dd24441 100644 --- a/configs.signatures +++ b/configs.signatures @@ -129,6 +129,7 @@ declare -A configs_signatures=( ['2da0a2e7117292ece11d69723a294bd7']='python.d/mongodb.conf' ['2ee5df033fe9c65a45566b6760b856e3']='python.d/web_log.conf' ['2f05e09b69ea20cda56d8f8b6fd3e86d']='health.d/couchdb.conf' + ['2f13a6b7d11eda826ff26569b2a77080']='health.d/apcupsd.conf' ['2f3a8e33df83f14e0af8ca2465697215']='python.d/exim.conf' ['2f4a85fedecce1bf425fa1039f6b021e']='apps_groups.conf' ['2fa8fb929fd597f2ab97b6efc540a043']='health_alarm_notify.conf' @@ -251,6 +252,7 @@ declare -A configs_signatures=( ['579c7c41c756745f57afd11c94a879d7']='python.d.conf' ['57be306944cb09b7f024079728fd04b9']='apps_groups.conf' ['5829812db29598db5857c9f433e96fef']='python.d/apache.conf' + ['58439d9c1253e33c74b399e6853143ab']='health.d/apcupsd.conf' ['5855dd70d71c8497e5591b0690162c9c']='health.d/tcp_resets.conf' ['58660dfcc260f77deec94b328b3838e8']='health_alarm_notify.conf' ['58e835b7176865ec5a6f59f7aba832bf']='health.d/named.conf' @@ -386,6 +388,7 @@ declare -A configs_signatures=( ['87615ae5ac2412d853c717383fa53781']='python.d/chrony.conf' ['87642c568093daf3b2c30c5beffe2225']='python.d/elasticsearch.conf' ['8810140ce9c09af1d18b9602c4003904']='health_alarm_notify.conf' + ['886975ecb9a4e856151dc71024b122e6']='health.d/apcupsd.conf' ['8891fb423f6b987281d7913bb6c1c024']='health.d/ipc.conf' ['88e3b51b6b3fe8f317df82a2d4fbb990']='python.d.conf' ['88f77865f75c9fb61c97d700bd4561ee']='python.d/mysql.conf' diff --git a/netdata-installer.sh b/netdata-installer.sh index 59436a2104..1c77e8f86a 100755 --- a/netdata-installer.sh +++ b/netdata-installer.sh @@ -80,6 +80,7 @@ REINSTALL_COMMAND="$(printf "%q " "$0" "${@}"; printf "\n")" # remove options that shown not be inherited by netdata-updater.sh REINSTALL_COMMAND="${REINSTALL_COMMAND// --dont-wait/}" REINSTALL_COMMAND="${REINSTALL_COMMAND// --dont-start-it/}" +[ "${REINSTALL_COMMAND:0:1}" != "." -a "${REINSTALL_COMMAND:0:1}" != "/" -a -f "./${0}" ] && REINSTALL_COMMAND="./${REINSTALL_COMMAND}" setcap="$(which setcap 2>/dev/null || command -v setcap 2>/dev/null)" diff --git a/python.d/Makefile.am b/python.d/Makefile.am index caf4a6210b..43fcef6c69 100644 --- a/python.d/Makefile.am +++ b/python.d/Makefile.am @@ -41,6 +41,7 @@ dist_python_DATA = \ litespeed.chart.py \ logind.chart.py \ mdstat.chart.py \ + megacli.chart.py \ memcached.chart.py \ mongodb.chart.py \ mysql.chart.py \ diff --git a/python.d/README.md b/python.d/README.md index 2417fc4302..3a246cfbd1 100644 --- a/python.d/README.md +++ b/python.d/README.md @@ -1141,6 +1141,35 @@ No configuration is needed. --- +# megacli + +Module collects adapter, physical drives and battery stats. + +**Requirements:** + * `netdata` user needs to be able to be able to sudo the `megacli` program without password + +To grab stats it executes: + * `sudo -n megacli -LDPDInfo -aAll` + * `sudo -n megacli -AdpBbuCmd -a0` + + +It produces: + +1. **Adapter State** + +2. **Physical Drives Media Errors** + +3. **Physical Drives Predictive Failures** + +4. **Battery Relative State of Charge** + +5. **Battery Cycle Count** + +### configuration +Battery stats disabled by default in the module configuration file. + +--- + # memcached Memcached monitoring module. Data grabbed from [stats interface](https://github.com/memcached/memcached/wiki/Commands#stats). diff --git a/python.d/megacli.chart.py b/python.d/megacli.chart.py new file mode 100644 index 0000000000..594c4fb17b --- /dev/null +++ b/python.d/megacli.chart.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +# Description: megacli netdata python.d module +# Author: Ilya Mashchenko (l2isbad) +# SPDX-License-Identifier: GPL-3.0+ + + +import re + +from bases.FrameworkServices.ExecutableService import ExecutableService +from bases.collection import find_binary + + +update_every = 5 + + +def adapter_charts(ads): + order = [ + 'adapter_degraded', + ] + + def dims(ad): + return [['adapter_{0}_degraded'.format(a.id), 'adapter {0}'.format(a.id)] for a in ad] + + charts = { + 'adapter_degraded': { + 'options': [ + None, 'Adapter State', 'is degraded', 'adapter', 'megacli.adapter_degraded', 'line'], + 'lines': dims(ads) + }, + } + + return order, charts + + +def pd_charts(pds): + order = [ + 'pd_media_error', + 'pd_predictive_failure', + ] + + def dims(k, pd): + return [['slot_{0}_{1}'.format(p.id, k), 'slot {0}'.format(p.id), 'incremental'] for p in pd] + + charts = { + 'pd_media_error': { + 'options': [ + None, 'Physical Drives Media Errors', 'errors/s', 'pd', 'megacli.pd_media_error', 'line' + ], + 'lines': dims("media_error", pds)}, + 'pd_predictive_failure': { + 'options': [ + None, 'Physical Drives Predictive Failures', 'failures/s', 'pd', 'megacli.pd_predictive_failure', 'line' + ], + 'lines': dims("predictive_failure", pds)} + } + + return order, charts + + +def battery_charts(bats): + order = list() + charts = dict() + + for b in bats: + order.append('bbu_{0}_relative_charge'.format(b.id)) + charts.update( + { + 'bbu_{0}_relative_charge'.format(b.id): { + 'options': [ + None, 'Relative State of Charge', '%', 'battery', 'megacli.bbu_relative_charge', 'line'], + 'lines': [ + ['bbu_{0}_relative_charge'.format(b.id), 'adapter {0}'.format(b.id)], + ] + } + } + ) + + for b in bats: + order.append('bbu_{0}_cycle_count'.format(b.id)) + charts.update( + { + 'bbu_{0}_cycle_count'.format(b.id): { + 'options': [ + None, 'Cycle Count', 'cycle count', 'battery', 'megacli.bbu_cycle_count', 'line'], + 'lines': [ + ['bbu_{0}_cycle_count'.format(b.id), 'adapter {0}'.format(b.id)], + ] + } + } + ) + + return order, charts + + +RE_ADAPTER = re.compile( + r'Adapter #([0-9]+) State\s+: ([a-zA-Z]+)' +) + +RE_VD = re.compile( + r'Slot Number: ([0-9]+) Media Error Count: ([0-9]+) Predictive Failure Count: ([0-9]+)' +) + +RE_BATTERY = re.compile( + r'BBU Capacity Info for Adapter: ([0-9]+) Relative State of Charge: ([0-9]+) % Cycle Count: ([0-9]+)' +) + + +def find_adapters(d): + keys = ("Adapter #", "State") + d = ' '.join(v.strip() for v in d if v.startswith(keys)) + return [Adapter(*v) for v in RE_ADAPTER.findall(d)] + + +def find_pds(d): + keys = ("Slot Number", "Media Error Count", "Predictive Failure Count") + d = ' '.join(v.strip() for v in d if v.startswith(keys)) + return [PD(*v) for v in RE_VD.findall(d)] + + +def find_batteries(d): + keys = ('BBU Capacity Info for Adapter', 'Relative State of Charge', 'Cycle Count') + d = ' '.join(v.strip() for v in d if v.strip().startswith(keys)) + return [Battery(*v) for v in RE_BATTERY.findall(d)] + + +class Adapter: + def __init__(self, n, state): + self.id = n + self.state = int(state == 'Degraded') + + def data(self): + return { + 'adapter_{0}_degraded'.format(self.id): self.state, + } + + +class PD: + def __init__(self, n, media_err, predict_fail): + self.id = n + self.media_err = media_err + self.predict_fail = predict_fail + + def data(self): + return { + 'slot_{0}_media_error'.format(self.id): self.media_err, + 'slot_{0}_predictive_failure'.format(self.id): self.predict_fail, + } + + +class Battery: + def __init__(self, adapt_id, rel_charge, cycle_count): + self.id = adapt_id + self.rel_charge = rel_charge + self.cycle_count = cycle_count + + def data(self): + return { + 'bbu_{0}_relative_charge'.format(self.id): self.rel_charge, + 'bbu_{0}_cycle_count'.format(self.id): self.cycle_count, + } + + +# TODO: hardcoded sudo... +class Megacli: + def __init__(self): + self.s = find_binary('sudo') + self.m = find_binary('megacli') + self.sudo_check = [self.s, '-n', '-v'] + self.disk_info = [self.s, '-n', self.m, '-LDPDInfo', '-aAll'] + self.battery_info = [self.s, '-n', self.m, '-AdpBbuCmd', '-a0'] + + def __bool__(self): + return bool(self.s and self.m) + + def __nonzero__(self): + return self.__bool__() + + +class Service(ExecutableService): + def __init__(self, configuration=None, name=None): + ExecutableService.__init__(self, configuration=configuration, name=name) + self.order = list() + self.definitions = dict() + self.megacli = Megacli() + self.do_battery = self.configuration.get('do_battery') + + def check_sudo(self): + err = self._get_raw_data(command=self.megacli.sudo_check, stderr=True) + if err: + self.error(''.join(err)) + return False + return True + + def check_disk_info(self): + d = self._get_raw_data(command=self.megacli.disk_info) + if not d: + return False + + ads = find_adapters(d) + pds = find_pds(d) + + if not (ads and pds): + self.error('failed to parse "{0}" output'.format(' '.join(self.megacli.disk_info))) + return False + + o, c = adapter_charts(ads) + self.order.extend(o) + self.definitions.update(c) + + o, c = pd_charts(pds) + self.order.extend(o) + self.definitions.update(c) + + return True + + def check_battery(self): + d = self._get_raw_data(command=self.megacli.battery_info) + if not d: + return False + + bats = find_batteries(d) + + if not bats: + self.error('failed to parse "{0}" output'.format(' '.join(self.megacli.battery_info))) + return False + + o, c = battery_charts(bats) + self.order.extend(o) + self.definitions.update(c) + return True + + def check(self): + if not self.megacli: + self.error('can\'t locate "sudo" or "megacli" binary') + return None + + if not (self.check_sudo() and self.check_disk_info()): + return False + + if self.do_battery: + self.do_battery = self.check_battery() + + return True + + def get_data(self): + data = dict() + + data.update(self.get_adapter_pd_data()) + + if self.do_battery: + data.update(self.get_battery_data()) + + return data or None + + def get_adapter_pd_data(self): + raw = self._get_raw_data(command=self.megacli.disk_info) + data = dict() + + if not raw: + return data + + for a in find_adapters(raw): + data.update(a.data()) + + for p in find_pds(raw): + data.update(p.data()) + + return data + + def get_battery_data(self): + raw = self._get_raw_data(command=self.megacli.battery_info) + data = dict() + + if not raw: + return data + + for b in find_batteries(raw): + data.update(b.data()) + + return data diff --git a/python.d/python_modules/bases/FrameworkServices/ExecutableService.py b/python.d/python_modules/bases/FrameworkServices/ExecutableService.py index 9b2e945e79..625f643234 100644 --- a/python.d/python_modules/bases/FrameworkServices/ExecutableService.py +++ b/python.d/python_modules/bases/FrameworkServices/ExecutableService.py @@ -17,15 +17,15 @@ class ExecutableService(SimpleService): SimpleService.__init__(self, configuration=configuration, name=name) self.command = None - def _get_raw_data(self, stderr=False): + def _get_raw_data(self, stderr=False, command=None): """ Get raw data from executed command :return: <list> """ try: - p = Popen(self.command, stdout=PIPE, stderr=PIPE) + p = Popen(command if command else self.command, stdout=PIPE, stderr=PIPE) except Exception as error: - self.error('Executing command {command} resulted in error: {error}'.format(command=self.command, + self.error('Executing command {command} resulted in error: {error}'.format(command=command or self.command, error=error)) return None data = list() diff --git a/python.d/python_modules/bases/loaders.py b/python.d/python_modules/bases/loaders.py index 6f2cf7bd09..debc69f239 100644 --- a/python.d/python_modules/bases/loaders.py +++ b/python.d/python_modules/bases/loaders.py @@ -4,16 +4,24 @@ # SPDX-License-Identifier: GPL-3.0+ import types + from sys import version_info PY_VERSION = version_info[:2] +try: + if PY_VERSION > (3, 1): + from pyyaml3 import SafeLoader as YamlSafeLoader + else: + from pyyaml2 import SafeLoader as YamlSafeLoader +except ImportError: + from yaml import SafeLoader as YamlSafeLoader + + if PY_VERSION > (3, 1): - from pyyaml3 import SafeLoader as YamlSafeLoader from importlib.machinery import SourceFileLoader DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map' else: - from pyyaml2 import SafeLoader as YamlSafeLoader from imp import load_source as SourceFileLoader DEFAULT_MAPPING_TAG = u'tag:yaml.org,2002:map' |