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. # TODO: Rewrite all of this
  96. self.state = int(state in ("Partially Degraded", "Degraded", "Failed"))
  97. def data(self):
  98. return {
  99. 'adapter_{0}_degraded'.format(self.id): self.state,
  100. }
  101. class PD:
  102. def __init__(self, n, media_err, predict_fail):
  103. self.id = n
  104. self.media_err = media_err
  105. self.predict_fail = predict_fail
  106. def data(self):
  107. return {
  108. 'slot_{0}_media_error'.format(self.id): self.media_err,
  109. 'slot_{0}_predictive_failure'.format(self.id): self.predict_fail,
  110. }
  111. class Battery:
  112. def __init__(self, adapt_id, rel_charge, cycle_count):
  113. self.id = adapt_id
  114. self.rel_charge = rel_charge
  115. self.cycle_count = cycle_count
  116. def data(self):
  117. return {
  118. 'bbu_{0}_relative_charge'.format(self.id): self.rel_charge,
  119. 'bbu_{0}_cycle_count'.format(self.id): self.cycle_count,
  120. }
  121. # TODO: hardcoded sudo...
  122. class Megacli:
  123. def __init__(self):
  124. self.s = find_binary('sudo')
  125. self.m = find_binary('megacli') or find_binary('MegaCli') # Binary on FreeBSD is MegaCli
  126. self.sudo_check = [self.s, '-n', '-l']
  127. self.disk_info = [self.s, '-n', self.m, '-LDPDInfo', '-aAll', '-NoLog']
  128. self.battery_info = [self.s, '-n', self.m, '-AdpBbuCmd', '-a0', '-NoLog']
  129. def __bool__(self):
  130. return bool(self.s and self.m)
  131. def __nonzero__(self):
  132. return self.__bool__()
  133. class Service(ExecutableService):
  134. def __init__(self, configuration=None, name=None):
  135. ExecutableService.__init__(self, configuration=configuration, name=name)
  136. self.order = list()
  137. self.definitions = dict()
  138. self.do_battery = self.configuration.get('do_battery')
  139. self.megacli = Megacli()
  140. def check_sudo(self):
  141. err = self._get_raw_data(command=self.megacli.sudo_check, stderr=True)
  142. if err:
  143. self.error(''.join(err))
  144. return False
  145. return True
  146. def check_disk_info(self):
  147. d = self._get_raw_data(command=self.megacli.disk_info)
  148. if not d:
  149. return False
  150. ads = find_adapters(d)
  151. pds = find_pds(d)
  152. if not (ads and pds):
  153. self.error('failed to parse "{0}" output'.format(' '.join(self.megacli.disk_info)))
  154. return False
  155. o, c = adapter_charts(ads)
  156. self.order.extend(o)
  157. self.definitions.update(c)
  158. o, c = pd_charts(pds)
  159. self.order.extend(o)
  160. self.definitions.update(c)
  161. return True
  162. def check_battery(self):
  163. d = self._get_raw_data(command=self.megacli.battery_info)
  164. if not d:
  165. return False
  166. bats = find_batteries(d)
  167. if not bats:
  168. self.error('failed to parse "{0}" output'.format(' '.join(self.megacli.battery_info)))
  169. return False
  170. o, c = battery_charts(bats)
  171. self.order.extend(o)
  172. self.definitions.update(c)
  173. return True
  174. def check(self):
  175. if not self.megacli:
  176. self.error('can\'t locate "sudo" or "megacli" binary')
  177. return None
  178. if not (self.check_sudo() and self.check_disk_info()):
  179. return False
  180. if self.do_battery:
  181. self.do_battery = self.check_battery()
  182. return True
  183. def get_data(self):
  184. data = dict()
  185. data.update(self.get_adapter_pd_data())
  186. if self.do_battery:
  187. data.update(self.get_battery_data())
  188. return data or None
  189. def get_adapter_pd_data(self):
  190. raw = self._get_raw_data(command=self.megacli.disk_info)
  191. data = dict()
  192. if not raw:
  193. return data
  194. for a in find_adapters(raw):
  195. data.update(a.data())
  196. for p in find_pds(raw):
  197. data.update(p.data())
  198. return data
  199. def get_battery_data(self):
  200. raw = self._get_raw_data(command=self.megacli.battery_info)
  201. data = dict()
  202. if not raw:
  203. return data
  204. for b in find_batteries(raw):
  205. data.update(b.data())
  206. return data