adaptec_raid.chart.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. # -*- coding: utf-8 -*-
  2. # Description: adaptec_raid netdata python.d module
  3. # Author: Ilya Mashchenko (ilyam8)
  4. # SPDX-License-Identifier: GPL-3.0-or-later
  5. import re
  6. from copy import deepcopy
  7. from bases.FrameworkServices.ExecutableService import ExecutableService
  8. from bases.collection import find_binary
  9. disabled_by_default = True
  10. update_every = 5
  11. ORDER = [
  12. 'ld_status',
  13. 'pd_state',
  14. 'pd_smart_warnings',
  15. 'pd_temperature',
  16. ]
  17. CHARTS = {
  18. 'ld_status': {
  19. 'options': [None, 'Status of logical devices (1: Failed or Degraded)', 'bool', 'logical devices',
  20. 'adaptec_raid.ld_status', 'line'],
  21. 'lines': []
  22. },
  23. 'pd_state': {
  24. 'options': [None, 'State of physical devices (1: not Online)', 'bool', 'physical devices',
  25. 'adaptec_raid.pd_state', 'line'],
  26. 'lines': []
  27. },
  28. 'pd_smart_warnings': {
  29. 'options': [None, 'S.M.A.R.T warnings', 'count', 'physical devices',
  30. 'adaptec_raid.smart_warnings', 'line'],
  31. 'lines': []
  32. },
  33. 'pd_temperature': {
  34. 'options': [None, 'Temperature', 'celsius', 'physical devices', 'adaptec_raid.temperature', 'line'],
  35. 'lines': []
  36. },
  37. }
  38. SUDO = 'sudo'
  39. ARCCONF = 'arcconf'
  40. BAD_LD_STATUS = (
  41. 'Degraded',
  42. 'Failed',
  43. )
  44. GOOD_PD_STATUS = (
  45. 'Online',
  46. )
  47. RE_LD = re.compile(
  48. r'Logical [dD]evice number\s+([0-9]+).*?'
  49. r'Status of [lL]ogical [dD]evice\s+: ([a-zA-Z]+)'
  50. )
  51. def find_lds(d):
  52. d = ' '.join(v.strip() for v in d)
  53. return [LD(*v) for v in RE_LD.findall(d)]
  54. def find_pds(d):
  55. pds = list()
  56. pd = PD()
  57. for row in d:
  58. row = row.strip()
  59. if row.startswith('Device #'):
  60. pd = PD()
  61. pd.id = row.split('#')[-1]
  62. elif not pd.id:
  63. continue
  64. if row.startswith('State'):
  65. v = row.split()[-1]
  66. pd.state = v
  67. elif row.startswith('S.M.A.R.T. warnings'):
  68. v = row.split()[-1]
  69. pd.smart_warnings = v
  70. elif row.startswith('Temperature'):
  71. v = row.split(':')[-1].split()[0]
  72. pd.temperature = v
  73. elif row.startswith('NCQ status'):
  74. if pd.id and pd.state and pd.smart_warnings:
  75. pds.append(pd)
  76. pd = PD()
  77. return pds
  78. class LD:
  79. def __init__(self, ld_id, status):
  80. self.id = ld_id
  81. self.status = status
  82. def data(self):
  83. return {
  84. 'ld_{0}_status'.format(self.id): int(self.status in BAD_LD_STATUS)
  85. }
  86. class PD:
  87. def __init__(self):
  88. self.id = None
  89. self.state = None
  90. self.smart_warnings = None
  91. self.temperature = None
  92. def data(self):
  93. data = {
  94. 'pd_{0}_state'.format(self.id): int(self.state not in GOOD_PD_STATUS),
  95. 'pd_{0}_smart_warnings'.format(self.id): self.smart_warnings,
  96. }
  97. if self.temperature and self.temperature.isdigit():
  98. data['pd_{0}_temperature'.format(self.id)] = self.temperature
  99. return data
  100. class Arcconf:
  101. def __init__(self, arcconf):
  102. self.arcconf = arcconf
  103. def ld_info(self):
  104. return [self.arcconf, 'GETCONFIG', '1', 'LD']
  105. def pd_info(self):
  106. return [self.arcconf, 'GETCONFIG', '1', 'PD']
  107. # TODO: hardcoded sudo...
  108. class SudoArcconf:
  109. def __init__(self, arcconf, sudo):
  110. self.arcconf = Arcconf(arcconf)
  111. self.sudo = sudo
  112. def ld_info(self):
  113. return [self.sudo, '-n'] + self.arcconf.ld_info()
  114. def pd_info(self):
  115. return [self.sudo, '-n'] + self.arcconf.pd_info()
  116. class Service(ExecutableService):
  117. def __init__(self, configuration=None, name=None):
  118. ExecutableService.__init__(self, configuration=configuration, name=name)
  119. self.order = ORDER
  120. self.definitions = deepcopy(CHARTS)
  121. self.use_sudo = self.configuration.get('use_sudo', True)
  122. self.arcconf = None
  123. def execute(self, command, stderr=False):
  124. return self._get_raw_data(command=command, stderr=stderr)
  125. def check(self):
  126. arcconf = find_binary(ARCCONF)
  127. if not arcconf:
  128. self.error('can\'t locate "{0}" binary'.format(ARCCONF))
  129. return False
  130. sudo = find_binary(SUDO)
  131. if self.use_sudo:
  132. if not sudo:
  133. self.error('can\'t locate "{0}" binary'.format(SUDO))
  134. return False
  135. err = self.execute([sudo, '-n', '-v'], True)
  136. if err:
  137. self.error(' '.join(err))
  138. return False
  139. if self.use_sudo:
  140. self.arcconf = SudoArcconf(arcconf, sudo)
  141. else:
  142. self.arcconf = Arcconf(arcconf)
  143. lds = self.get_lds()
  144. if not lds:
  145. return False
  146. self.debug('discovered logical devices ids: {0}'.format([ld.id for ld in lds]))
  147. pds = self.get_pds()
  148. if not pds:
  149. return False
  150. self.debug('discovered physical devices ids: {0}'.format([pd.id for pd in pds]))
  151. self.update_charts(lds, pds)
  152. return True
  153. def get_data(self):
  154. data = dict()
  155. for ld in self.get_lds():
  156. data.update(ld.data())
  157. for pd in self.get_pds():
  158. data.update(pd.data())
  159. return data
  160. def get_lds(self):
  161. raw_lds = self.execute(self.arcconf.ld_info())
  162. if not raw_lds:
  163. return None
  164. lds = find_lds(raw_lds)
  165. if not lds:
  166. self.error('failed to parse "{0}" output'.format(' '.join(self.arcconf.ld_info())))
  167. self.debug('output: {0}'.format(raw_lds))
  168. return None
  169. return lds
  170. def get_pds(self):
  171. raw_pds = self.execute(self.arcconf.pd_info())
  172. if not raw_pds:
  173. return None
  174. pds = find_pds(raw_pds)
  175. if not pds:
  176. self.error('failed to parse "{0}" output'.format(' '.join(self.arcconf.pd_info())))
  177. self.debug('output: {0}'.format(raw_pds))
  178. return None
  179. return pds
  180. def update_charts(self, lds, pds):
  181. charts = self.definitions
  182. for ld in lds:
  183. dim = ['ld_{0}_status'.format(ld.id), 'ld {0}'.format(ld.id)]
  184. charts['ld_status']['lines'].append(dim)
  185. for pd in pds:
  186. dim = ['pd_{0}_state'.format(pd.id), 'pd {0}'.format(pd.id)]
  187. charts['pd_state']['lines'].append(dim)
  188. dim = ['pd_{0}_smart_warnings'.format(pd.id), 'pd {0}'.format(pd.id)]
  189. charts['pd_smart_warnings']['lines'].append(dim)
  190. dim = ['pd_{0}_temperature'.format(pd.id), 'pd {0}'.format(pd.id)]
  191. charts['pd_temperature']['lines'].append(dim)