megacli.chart.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. # -*- coding: utf-8 -*-
  2. # Description: megacli netdata python.d module
  3. # Author: Ilya Mashchenko (ilyam8)
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. import re
  6. from bases.FrameworkServices.ExecutableService import ExecutableService
  7. from bases.collection import find_binary
  8. disabled_by_default = True
  9. update_every = 5
  10. def adapter_charts(ads):
  11. order = [
  12. 'adapter_degraded',
  13. ]
  14. def dims(ad):
  15. return [['adapter_{0}_degraded'.format(a.id), 'adapter {0}'.format(a.id)] for a in ad]
  16. charts = {
  17. 'adapter_degraded': {
  18. 'options': [None, 'Adapter State', 'is degraded', 'adapter', 'megacli.adapter_degraded', 'line'],
  19. 'lines': dims(ads)
  20. },
  21. }
  22. return order, charts
  23. def pd_charts(pds):
  24. order = [
  25. 'pd_media_error',
  26. 'pd_predictive_failure',
  27. ]
  28. def dims(k, pd):
  29. return [['slot_{0}_{1}'.format(p.id, k), 'slot {0}'.format(p.id), 'incremental'] for p in pd]
  30. charts = {
  31. 'pd_media_error': {
  32. 'options': [None, 'Physical Drives Media Errors', 'errors/s', 'pd', 'megacli.pd_media_error', 'line'],
  33. 'lines': dims('media_error', pds)
  34. },
  35. 'pd_predictive_failure': {
  36. 'options': [None, 'Physical Drives Predictive Failures', 'failures/s', 'pd',
  37. 'megacli.pd_predictive_failure', 'line'],
  38. 'lines': dims('predictive_failure', pds)
  39. }
  40. }
  41. return order, charts
  42. def battery_charts(bats):
  43. order = list()
  44. charts = dict()
  45. for b in bats:
  46. order.append('bbu_{0}_relative_charge'.format(b.id))
  47. charts.update(
  48. {
  49. 'bbu_{0}_relative_charge'.format(b.id): {
  50. 'options': [None, 'Relative State of Charge', 'percentage', 'battery',
  51. 'megacli.bbu_relative_charge', 'line'],
  52. 'lines': [
  53. ['bbu_{0}_relative_charge'.format(b.id), 'adapter {0}'.format(b.id)],
  54. ]
  55. }
  56. }
  57. )
  58. for b in bats:
  59. order.append('bbu_{0}_cycle_count'.format(b.id))
  60. charts.update(
  61. {
  62. 'bbu_{0}_cycle_count'.format(b.id): {
  63. 'options': [None, 'Cycle Count', 'cycle count', 'battery', 'megacli.bbu_cycle_count', 'line'],
  64. 'lines': [
  65. ['bbu_{0}_cycle_count'.format(b.id), 'adapter {0}'.format(b.id)],
  66. ]
  67. }
  68. }
  69. )
  70. return order, charts
  71. RE_ADAPTER = re.compile(
  72. r'Adapter #([0-9]+) State(?:\s+)?: ([a-zA-Z]+)'
  73. )
  74. RE_VD = re.compile(
  75. r'Slot Number: ([0-9]+) Media Error Count: ([0-9]+) Predictive Failure Count: ([0-9]+)'
  76. )
  77. RE_BATTERY = re.compile(
  78. r'BBU Capacity Info for Adapter: ([0-9]+) Relative State of Charge: ([0-9]+) % Cycle Count: ([0-9]+)'
  79. )
  80. def find_adapters(d):
  81. keys = ('Adapter #', 'State')
  82. d = ' '.join(v.strip() for v in d if v.startswith(keys))
  83. return [Adapter(*v) for v in RE_ADAPTER.findall(d)]
  84. def find_pds(d):
  85. keys = ('Slot Number', 'Media Error Count', 'Predictive Failure Count')
  86. d = ' '.join(v.strip() for v in d if v.startswith(keys))
  87. return [PD(*v) for v in RE_VD.findall(d)]
  88. def find_batteries(d):
  89. keys = ('BBU Capacity Info for Adapter', 'Relative State of Charge', 'Cycle Count')
  90. d = ' '.join(v.strip() for v in d if v.strip().startswith(keys))
  91. return [Battery(*v) for v in RE_BATTERY.findall(d)]
  92. class Adapter:
  93. def __init__(self, n, state):
  94. self.id = n
  95. self.state = int(state == 'Degraded')
  96. def data(self):
  97. return {
  98. 'adapter_{0}_degraded'.format(self.id): self.state,
  99. }
  100. class PD:
  101. def __init__(self, n, media_err, predict_fail):
  102. self.id = n
  103. self.media_err = media_err
  104. self.predict_fail = predict_fail
  105. def data(self):
  106. return {
  107. 'slot_{0}_media_error'.format(self.id): self.media_err,
  108. 'slot_{0}_predictive_failure'.format(self.id): self.predict_fail,
  109. }
  110. class Battery:
  111. def __init__(self, adapt_id, rel_charge, cycle_count):
  112. self.id = adapt_id
  113. self.rel_charge = rel_charge
  114. self.cycle_count = cycle_count
  115. def data(self):
  116. return {
  117. 'bbu_{0}_relative_charge'.format(self.id): self.rel_charge,
  118. 'bbu_{0}_cycle_count'.format(self.id): self.cycle_count,
  119. }
  120. # TODO: hardcoded sudo...
  121. class Megacli:
  122. def __init__(self):
  123. self.s = find_binary('sudo')
  124. self.m = find_binary('megacli') or find_binary('MegaCli') # Binary on FreeBSD is MegaCli
  125. self.sudo_check = [self.s, '-n', '-l']
  126. self.disk_info = [self.s, '-n', self.m, '-LDPDInfo', '-aAll', '-NoLog']
  127. self.battery_info = [self.s, '-n', self.m, '-AdpBbuCmd', '-a0', '-NoLog']
  128. def __bool__(self):
  129. return bool(self.s and self.m)
  130. def __nonzero__(self):
  131. return self.__bool__()
  132. class Service(ExecutableService):
  133. def __init__(self, configuration=None, name=None):
  134. ExecutableService.__init__(self, configuration=configuration, name=name)
  135. self.order = list()
  136. self.definitions = dict()
  137. self.do_battery = self.configuration.get('do_battery')
  138. self.megacli = Megacli()
  139. def check_sudo(self):
  140. err = self._get_raw_data(command=self.megacli.sudo_check, stderr=True)
  141. if err:
  142. self.error(''.join(err))
  143. return False
  144. return True
  145. def check_disk_info(self):
  146. d = self._get_raw_data(command=self.megacli.disk_info)
  147. if not d:
  148. return False
  149. ads = find_adapters(d)
  150. pds = find_pds(d)
  151. if not (ads and pds):
  152. self.error('failed to parse "{0}" output'.format(' '.join(self.megacli.disk_info)))
  153. return False
  154. o, c = adapter_charts(ads)
  155. self.order.extend(o)
  156. self.definitions.update(c)
  157. o, c = pd_charts(pds)
  158. self.order.extend(o)
  159. self.definitions.update(c)
  160. return True
  161. def check_battery(self):
  162. d = self._get_raw_data(command=self.megacli.battery_info)
  163. if not d:
  164. return False
  165. bats = find_batteries(d)
  166. if not bats:
  167. self.error('failed to parse "{0}" output'.format(' '.join(self.megacli.battery_info)))
  168. return False
  169. o, c = battery_charts(bats)
  170. self.order.extend(o)
  171. self.definitions.update(c)
  172. return True
  173. def check(self):
  174. if not self.megacli:
  175. self.error('can\'t locate "sudo" or "megacli" binary')
  176. return None
  177. if not (self.check_sudo() and self.check_disk_info()):
  178. return False
  179. if self.do_battery:
  180. self.do_battery = self.check_battery()
  181. return True
  182. def get_data(self):
  183. data = dict()
  184. data.update(self.get_adapter_pd_data())
  185. if self.do_battery:
  186. data.update(self.get_battery_data())
  187. return data or None
  188. def get_adapter_pd_data(self):
  189. raw = self._get_raw_data(command=self.megacli.disk_info)
  190. data = dict()
  191. if not raw:
  192. return data
  193. for a in find_adapters(raw):
  194. data.update(a.data())
  195. for p in find_pds(raw):
  196. data.update(p.data())
  197. return data
  198. def get_battery_data(self):
  199. raw = self._get_raw_data(command=self.megacli.battery_info)
  200. data = dict()
  201. if not raw:
  202. return data
  203. for b in find_batteries(raw):
  204. data.update(b.data())
  205. return data