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