@cryptotaxi247 / netdata-1 / commits / b85833f08

adaptec_raid python module (#4429)

* adaptec_raid module init version * adaptec_raid minor * adaptec_raid minor * adaptec_raid minor * adaptec_raid arcconf command fix * adaptec_raid minor fixes * adaptec_raid add alarms * adaptec_raid add link to screenshot to the readme

Ilya Mashchenko committed Oct 23, 2018 at 20:48 UTC b85833f08135bb7b3c2a0001dac8742d1d62db91
7 files changed +370
collectors/python.d.plugin/Makefile.am
+1
@@ -37,6 +37,7 @@ dist_pythonconfig_DATA = \
37 $(top_srcdir)/installer/.keep \
38 $(NULL)
39
40 +include adaptec_raid/Makefile.inc
41 include apache/Makefile.inc
42 include beanstalk/Makefile.inc
43 include bind_rndc/Makefile.inc
collectors/python.d.plugin/adaptec_raid/Makefile.inc new
+13
@@ -0,0 +1,13 @@
1 +# SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +# THIS IS NOT A COMPLETE Makefile
4 +# IT IS INCLUDED BY ITS PARENT'S Makefile.am
5 +# IT IS REQUIRED TO REFERENCE ALL FILES RELATIVE TO THE PARENT
6 +
7 +# install these files
8 +dist_python_DATA += adaptec_raid/adaptec_raid.chart.py
9 +dist_pythonconfig_DATA += adaptec_raid/adaptec_raid.conf
10 +
11 +# do not install these files, but include them in the distribution
12 +dist_noinst_DATA += adaptec_raid/README.md adaptec_raid/Makefile.inc
13 +
collectors/python.d.plugin/adaptec_raid/README.md new
+27
@@ -0,0 +1,27 @@
1 +# adaptec raid
2 +
3 +Module collects logical and physical devices health metrics.
4 +
5 +**Requirements:**
6 + * `netdata` user needs to be able to sudo the `arcconf` program without password
7 +
8 +To grab stats it executes:
9 + * `sudo -n arcconf GETCONFIG 1 LD`
10 + * `sudo -n arcconf GETCONFIG 1 PD`
11 +
12 +
13 +It produces:
14 +
15 +1. **Logical Device Status**
16 +
17 +2. **Physical Device State**
18 +
19 +3. **Physical Device S.M.A.R.T warnings**
20 +
21 +4. **Physical Device Temperature**
22 +
23 +Screenshot:
24 +
25 +![image](https://user-images.githubusercontent.com/22274335/47278133-6d306680-d601-11e8-87c2-cc9c0f42d686.png)
26 +
27 +---
collectors/python.d.plugin/adaptec_raid/adaptec_raid.chart.py new
+245
@@ -0,0 +1,245 @@
1 +# -*- coding: utf-8 -*-
2 +# Description: adaptec_raid netdata python.d module
3 +# Author: Ilya Mashchenko (l2isbad)
4 +# SPDX-License-Identifier: GPL-3.0-or-later
5 +
6 +
7 +import re
8 +
9 +from copy import deepcopy
10 +
11 +from bases.FrameworkServices.ExecutableService import ExecutableService
12 +from bases.collection import find_binary
13 +
14 +
15 +update_every = 5
16 +
17 +ORDER = [
18 + 'ld_status',
19 + 'pd_state',
20 + 'pd_smart_warnings',
21 + 'pd_temperature',
22 +]
23 +
24 +CHARTS = {
25 + 'ld_status': {
26 + 'options': [None, 'Status Is Not OK', 'bool', 'logical devices', 'adapter_raid.ld_status', 'line'],
27 + 'lines': []
28 + },
29 + 'pd_state': {
30 + 'options': [None, 'State Is Not OK', 'bool', 'physical devices', 'adapter_raid.pd_state', 'line'],
31 + 'lines': []
32 + },
33 + 'pd_smart_warnings': {
34 + 'options': [None, 'S.M.A.R.T warnings', 'count', 'physical devices',
35 + 'adapter_raid.smart_warnings', 'line'],
36 + 'lines': []
37 + },
38 + 'pd_temperature': {
39 + 'options': [None, 'Temperature', 'celsius', 'physical devices', 'adapter_raid.temperature', 'line'],
40 + 'lines': []
41 + },
42 +}
43 +
44 +SUDO = 'sudo'
45 +ARCCONF = 'arcconf'
46 +
47 +BAD_LD_STATUS = (
48 + 'Degraded',
49 + 'Failed',
50 +)
51 +
52 +GOOD_PD_STATUS = (
53 + 'Online',
54 +)
55 +
56 +RE_LD = re.compile(
57 + r'Logical device number\s+([0-9]+).*?'
58 + r'Status of logical device\s+: ([a-zA-Z]+)'
59 +)
60 +
61 +
62 +def find_lds(d):
63 + d = ' '.join(v.strip() for v in d)
64 + return [LD(*v) for v in RE_LD.findall(d)]
65 +
66 +
67 +def find_pds(d):
68 + pds = list()
69 + pd = PD()
70 +
71 + for row in d:
72 + row = row.strip()
73 + if row.startswith('Device #'):
74 + pd = PD()
75 + pd.id = row.split('#')[-1]
76 + elif not pd.id:
77 + continue
78 +
79 + if row.startswith('State'):
80 + v = row.split()[-1]
81 + pd.state = v
82 + elif row.startswith('S.M.A.R.T. warnings'):
83 + v = row.split()[-1]
84 + pd.smart_warnings = v
85 + elif row.startswith('Temperature'):
86 + v = row.split(':')[-1].split()[0]
87 + pd.temperature = v
88 + elif row.startswith('NCQ status'):
89 + if pd.id and pd.state and pd.smart_warnings:
90 + pds.append(pd)
91 + pd = PD()
92 +
93 + return pds
94 +
95 +
96 +class LD:
97 + def __init__(self, ld_id, status):
98 + self.id = ld_id
99 + self.status = status
100 +
101 + def data(self):
102 + return {
103 + 'ld_{0}_status'.format(self.id): int(self.status in BAD_LD_STATUS)
104 + }
105 +
106 +
107 +class PD:
108 + def __init__(self):
109 + self.id = None
110 + self.state = None
111 + self.smart_warnings = None
112 + self.temperature = None
113 +
114 + def data(self):
115 + data = {
116 + 'pd_{0}_state'.format(self.id): int(self.state not in GOOD_PD_STATUS),
117 + 'pd_{0}_smart_warnings'.format(self.id): self.smart_warnings,
118 + }
119 + if self.temperature and self.temperature.isdigit():
120 + data['pd_{0}_temperature'.format(self.id)] = self.temperature
121 +
122 + return data
123 +
124 +
125 +class Arcconf:
126 + def __init__(self, arcconf):
127 + self.arcconf = arcconf
128 +
129 + def ld_info(self):
130 + return [self.arcconf, 'GETCONFIG', '1', 'LD']
131 +
132 + def pd_info(self):
133 + return [self.arcconf, 'GETCONFIG', '1', 'PD']
134 +
135 +
136 +# TODO: hardcoded sudo...
137 +class SudoArcconf:
138 + def __init__(self, arcconf, sudo):
139 + self.arcconf = Arcconf(arcconf)
140 + self.sudo = sudo
141 +
142 + def ld_info(self):
143 + return [self.sudo, '-n'] + self.arcconf.ld_info()
144 +
145 + def pd_info(self):
146 + return [self.sudo, '-n'] + self.arcconf.pd_info()
147 +
148 +
149 +class Service(ExecutableService):
150 + def __init__(self, configuration=None, name=None):
151 + ExecutableService.__init__(self, configuration=configuration, name=name)
152 + self.order = ORDER
153 + self.definitions = deepcopy(CHARTS)
154 + self.use_sudo = self.configuration.get('use_sudo', True)
155 + self.arcconf = None
156 +
157 + def execute(self, command, stderr=False):
158 + return self._get_raw_data(command=command, stderr=stderr)
159 +
160 + def check(self):
161 + sudo = find_binary(SUDO)
162 + if self.use_sudo:
163 + if not sudo:
164 + self.error('can\'t locate "{0}" binary'.format(SUDO))
165 + return False
166 + err = self.execute([sudo, '-n', '-v'], True)
167 + if err:
168 + self.error(' '.join(err))
169 + return False
170 +
171 + arcconf = find_binary(ARCCONF)
172 + if not arcconf:
173 + self.error('can\'t locate "{0}" binary'.format(ARCCONF))
174 + return False
175 +
176 + if self.use_sudo:
177 + self.arcconf = SudoArcconf(arcconf, sudo)
178 + else:
179 + self.arcconf = Arcconf(arcconf)
180 +
181 + lds = self.get_lds()
182 + if not lds:
183 + return False
184 +
185 + self.debug('discovered logical devices ids: {0}'.format([ld.id for ld in lds]))
186 +
187 + pds = self.get_pds()
188 + if not pds:
189 + return False
190 +
191 + self.debug('discovered physical devices ids: {0}'.format([pd.id for pd in pds]))
192 +
193 + self.update_charts(lds, pds)
194 + return True
195 +
196 + def get_data(self):
197 + data = dict()
198 +
199 + for ld in self.get_lds():
200 + data.update(ld.data())
201 +
202 + for pd in self.get_pds():
203 + data.update(pd.data())
204 +
205 + return data
206 +
207 + def get_lds(self):
208 + raw_lds = self.execute(self.arcconf.ld_info())
209 + if not raw_lds:
210 + return None
211 +
212 + lds = find_lds(raw_lds)
213 + if not lds:
214 + self.error('failed to parse "{0}" output'.format(' '.join(self.arcconf.ld_info())))
215 + self.debug('output: {0}'.format(raw_lds))
216 + return None
217 + return lds
218 +
219 + def get_pds(self):
220 + raw_pds = self.execute(self.arcconf.pd_info())
221 + if not raw_pds:
222 + return None
223 +
224 + pds = find_pds(raw_pds)
225 + if not pds:
226 + self.error('failed to parse "{0}" output'.format(' '.join(self.arcconf.pd_info())))
227 + self.debug('output: {0}'.format(raw_pds))
228 + return None
229 + return pds
230 +
231 + def update_charts(self, lds, pds):
232 + charts = self.definitions
233 + for ld in lds:
234 + dim = ['ld_{0}_status'.format(ld.id), 'ld {0}'.format(ld.id)]
235 + charts['ld_status']['lines'].append(dim)
236 +
237 + for pd in pds:
238 + dim = ['pd_{0}_state'.format(pd.id), 'pd {0}'.format(pd.id)]
239 + charts['pd_state']['lines'].append(dim)
240 +
241 + dim = ['pd_{0}_smart_warnings'.format(pd.id), 'pd {0}'.format(pd.id)]
242 + charts['pd_smart_warnings']['lines'].append(dim)
243 +
244 + dim = ['pd_{0}_temperature'.format(pd.id), 'pd {0}'.format(pd.id)]
245 + charts['pd_temperature']['lines'].append(dim)
collectors/python.d.plugin/adaptec_raid/adaptec_raid.conf new
+59
@@ -0,0 +1,59 @@
1 +# netdata python.d.plugin configuration for adaptec raid
2 +#
3 +# This file is in YaML format. Generally the format is:
4 +#
5 +# name: value
6 +#
7 +
8 +# ----------------------------------------------------------------------
9 +# Global Variables
10 +# These variables set the defaults for all JOBs, however each JOB
11 +# may define its own, overriding the defaults.
12 +
13 +# update_every sets the default data collection frequency.
14 +# If unset, the python.d.plugin default is used.
15 +# update_every: 1
16 +
17 +# priority controls the order of charts at the netdata dashboard.
18 +# Lower numbers move the charts towards the top of the page.
19 +# If unset, the default for python.d.plugin is used.
20 +# priority: 60000
21 +
22 +# retries sets the number of retries to be made in case of failures.
23 +# If unset, the default for python.d.plugin is used.
24 +# Attempts to restore the service are made once every update_every
25 +# and only if the module has collected values in the past.
26 +# retries: 60
27 +
28 +# autodetection_retry sets the job re-check interval in seconds.
29 +# The job is not deleted if check fails.
30 +# Attempts to start the job are made once every autodetection_retry.
31 +# This feature is disabled by default.
32 +# autodetection_retry: 0
33 +
34 +# ----------------------------------------------------------------------
35 +# JOBS (data collection sources)
36 +#
37 +# The default JOBS share the same *name*. JOBS with the same name
38 +# are mutually exclusive. Only one of them will be allowed running at
39 +# any time. This allows autodetection to try several alternatives and
40 +# pick the one that works.
41 +#
42 +# Any number of jobs is supported.
43 +#
44 +# All python.d.plugin JOBS (for all its modules) support a set of
45 +# predefined parameters. These are:
46 +#
47 +# job_name:
48 +# name: myname # the JOB's name as it will appear at the
49 +# # dashboard (by default is the job_name)
50 +# # JOBs sharing a name are mutually exclusive
51 +# update_every: 1 # the JOB's data collection frequency
52 +# priority: 60000 # the JOB's order on the dashboard
53 +# retries: 60 # the JOB's number of restoration attempts
54 +# autodetection_retry: 0 # the JOB's re-check interval in seconds
55 +# ----------------------------------------------------------------------
56 +
57 +# IMPORTANT
58 +# The netdata user needs to be able to sudo the arcconf program without password:
59 +# netdata ALL=(root) NOPASSWD: /path/to/arcconf
health/Makefile.am
+1
@@ -22,6 +22,7 @@ dist_userhealthconfig_DATA = \
22 healthconfigdir=$(libconfigdir)/health.d
23 dist_healthconfig_DATA = \
24 $(top_srcdir)/installer/.keep \
25 + health.d/adaptec_raid.conf \
26 health.d/apache.conf \
27 health.d/apcupsd.conf \
28 health.d/backend.conf \
health/health.d/adaptec_raid.conf new
+24
@@ -0,0 +1,24 @@
1 +
2 +# logical device status check
3 +
4 +template: adapter_raid_ld_status
5 + on: adapter_raid.ld_status
6 + lookup: max -5s
7 + units: bool
8 + every: 10s
9 + crit: $this > 0
10 + delay: down 5m multiplier 1.5 max 1h
11 + info: at least 1 logical device is failed or degraded
12 + to: sysadmin
13 +
14 +# physical device state check
15 +
16 +template: adapter_raid_pd_state
17 + on: adapter_raid.pd_state
18 + lookup: max -5s
19 + units: bool
20 + every: 10s
21 + crit: $this > 0
22 + delay: down 5m multiplier 1.5 max 1h
23 + info: at least 1 physical device is not in online state
24 + to: sysadmin