123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- # -*- coding: utf-8 -*-
- # Description: megacli netdata python.d module
- # Author: Ilya Mashchenko (ilyam8)
- # SPDX-License-Identifier: GPL-3.0-or-later
- import re
- from bases.FrameworkServices.ExecutableService import ExecutableService
- from bases.collection import find_binary
- disabled_by_default = True
- 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', 'percentage', '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
- # TODO: Rewrite all of this
- self.state = int(state in ("Partially Degraded", "Degraded", "Failed"))
- 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') or find_binary('MegaCli') # Binary on FreeBSD is MegaCli
- self.sudo_check = [self.s, '-n', '-l']
- self.disk_info = [self.s, '-n', self.m, '-LDPDInfo', '-aAll', '-NoLog']
- self.battery_info = [self.s, '-n', self.m, '-AdpBbuCmd', '-a0', '-NoLog']
- 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.do_battery = self.configuration.get('do_battery')
- self.megacli = Megacli()
- 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
|