@cryptotaxi247 / netdata-1 / commits / 1b5fa8d77

mdstat: split oper status chart and adding MDs in runtime

lgz committed Jan 24, 2018 at 18:55 UTC 1b5fa8d77f3750c2d48f3a7fbed8992a45d667ef
1 file changed +116 -51
python.d/mdstat.chart.py
+116 -51
@@ -2,8 +2,9 @@
2 # Description: mdstat netdata python.d module
3 # Author: l2isbad
4
5 +import re
6 +
7 from collections import defaultdict
6 -from re import compile as re_compile
8
9 from bases.FrameworkServices.SimpleService import SimpleService
10
@@ -11,28 +12,99 @@ priority = 60000
12 retries = 60
13 update_every = 1
14
15 +ORDER = ['mdstat_health']
16 +CHARTS = {
17 + 'mdstat_health': {
18 + 'options': [None, 'Faulty Devices In MD', 'failed disks', 'health', 'md.health', 'line'],
19 + 'lines': list()
20 + }
21 +}
22 +
23 OPERATIONS = ('check', 'resync', 'reshape', 'recovery', 'finish', 'speed')
24
25 +RE_DISKS = re.compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+\['
26 + r'(?P<total_disks>[0-9]+)/'
27 + r'(?P<inuse_disks>[0-9]+)\]')
28 +
29 +RE_STATUS = re.compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+ '
30 + r'(?P<operation>[a-z]+) =[ ]{1,2}'
31 + r'(?P<operation_status>[0-9.]+).+finish='
32 + r'(?P<finish>([0-9.]+))min speed='
33 + r'(?P<speed>[0-9]+)')
34 +
35 +
36 +def md_charts(md):
37 + order = ['{0}_disks'.format(md.name),
38 + '{0}_operation'.format(md.name),
39 + '{0}_finish'.format(md.name),
40 + '{0}_speed'.format(md.name)
41 + ]
42 +
43 + charts = dict()
44 + charts[order[0]] = {
45 + 'options': [None, 'Disks Stats', 'disks', md.name, 'md.disks', 'stacked'],
46 + 'lines': [
47 + ['{0}_total_disks'.format(md.name), 'total', 'absolute'],
48 + ['{0}_inuse_disks'.format(md.name), 'inuse', 'absolute']
49 + ]
50 + }
51 +
52 + charts['_'.join([md.name, 'operation'])] = {
53 + 'options': [None, 'Current Status', 'percent', md.name, 'md.status', 'line'],
54 + 'lines': [
55 + ['{0}_resync'.format(md.name), 'resync', 'absolute', 1, 100],
56 + ['{0}_recovery'.format(md.name), 'recovery', 'absolute', 1, 100],
57 + ['{0}_reshape'.format(md.name), 'reshape', 'absolute', 1, 100],
58 + ['{0}_check'.format(md.name), 'check', 'absolute', 1, 100]
59 + ]
60 + }
61 +
62 + charts['_'.join([md.name, 'finish'])] = {
63 + 'options': [None, 'Approximate Time Until Finish', 'seconds', md.name, 'md.rate', 'line'],
64 + 'lines': [
65 + ['{0}_finish'.format(md.name), 'finish in', 'absolute', 1, 1000]
66 + ]
67 + }
68 +
69 + charts['_'.join([md.name, 'speed'])] = {
70 + 'options': [None, 'Operation Speed', 'KB/s', md.name, 'md.rate', 'line'],
71 + 'lines': [
72 + ['{0}_speed'.format(md.name), 'speed', 'absolute', 1, 1000]
73 + ]
74 + }
75 +
76 + return order, charts
77 +
78 +
79 +class MD:
80 + def __init__(self, name, stats):
81 + self.name = name
82 + self.stats = stats
83 +
84 + def __eq__(self, other):
85 + return self.name == other
86 +
87 + def update_stats(self, stats):
88 + self.stats = stats
89 +
90 + def data(self):
91 + stats = dict(('_'.join([self.name, k]), v) for k, v in self.stats.items())
92 + stats['{0}_health'.format(self.name)] = int(self.stats['total_disks']) - int(self.stats['inuse_disks'])
93 + return stats
94 +
95
96 class Service(SimpleService):
97 def __init__(self, configuration=None, name=None):
98 SimpleService.__init__(self, configuration=configuration, name=name)
20 - self.regex = dict(disks=re_compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+\['
21 - r'(?P<total_disks>[0-9]+)/'
22 - r'(?P<inuse_disks>[0-9]+)\]'),
23 - status=re_compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+ '
24 - r'(?P<operation>[a-z]+) =[ ]{1,2}'
25 - r'(?P<operation_status>[0-9.]+).+finish='
26 - r'(?P<finish>([0-9.]+))min speed='
27 - r'(?P<speed>[0-9]+)'))
99 + self.order = ORDER
100 + self.definitions = CHARTS
101 + self.mds = dict()
102
103 def check(self):
30 - arrays = find_arrays(self._get_raw_data(), self.regex)
104 + arrays = find_arrays(self._get_raw_data())
105 if not arrays:
106 self.error('Failed to read data from /proc/mdstat or there is no active arrays')
107 return None
34 -
35 - self.order, self.definitions = create_charts(arrays.keys())
108 return True
109
110 @staticmethod
@@ -47,25 +119,44 @@ class Service(SimpleService):
119 except (OSError, IOError):
120 return None
121
50 - def _get_data(self):
122 + def get_data(self):
123 """
124 Parse data from _get_raw_data()
125 :return: dict
126 """
55 - raw_data = self._get_raw_data()
56 - arrays = find_arrays(raw_data, self.regex)
127 + arrays = find_arrays(self._get_raw_data())
128 if not arrays:
129 return None
130
60 - to_netdata = dict()
131 + data = dict()
132 for array, values in arrays.items():
62 - for key, value in values.items():
63 - to_netdata['_'.join([array, key])] = value
133
65 - return to_netdata
134 + if array not in self.mds:
135 + md = MD(array, values)
136 + self.mds[md.name] = md
137 + self.create_new_array_charts(md)
138 + else:
139 + md = self.mds[array]
140 + md.update_stats(values)
141 +
142 + data.update(md.data())
143 +
144 + return data
145
146 + def create_new_array_charts(self, md):
147 + order, charts = md_charts(md)
148
68 -def find_arrays(raw_data, regex):
149 + self.charts['mdstat_health'].add_dimension(['{0}_health'.format(md.name), md.name])
150 + for chart_name in order:
151 + params = [chart_name] + charts[chart_name]['options']
152 + dimensions = charts[chart_name]['lines']
153 +
154 + new_chart = self.charts.add_chart(params)
155 + for dimension in dimensions:
156 + new_chart.add_dimension(dimension)
157 +
158 +
159 +def find_arrays(raw_data):
160 if raw_data is None:
161 return None
162 data = defaultdict(str)
@@ -79,49 +170,23 @@ def find_arrays(raw_data, regex):
170
171 arrays = dict()
172 for value in data.values():
82 - match = regex['disks'].search(value)
173 + match = RE_DISKS.search(value)
174 if not match:
175 continue
176
177 match = match.groupdict()
178 array = match.pop('array')
179 arrays[array] = match
89 - arrays[array]['health'] = int(match['total_disks']) - int(match['inuse_disks'])
180 for operation in OPERATIONS:
181 arrays[array][operation] = 0
182
93 - match = regex['status'].search(value)
183 + match = RE_STATUS.search(value)
184 if match:
185 match = match.groupdict()
186 if match['operation'] in OPERATIONS:
187 + arrays[array]['operation'] = match['operation']
188 arrays[array][match['operation']] = float(match['operation_status']) * 100
98 - arrays[array]['finish'] = float(match['finish']) * 100
99 - arrays[array]['speed'] = float(match['speed']) / 1000 * 100
189 + arrays[array]['finish'] = float(match['finish']) * 1000 * 60
190 + arrays[array]['speed'] = float(match['speed']) * 1000
191
192 return arrays or None
102 -
103 -
104 -def create_charts(arrays):
105 - order = ['mdstat_health']
106 - definitions = dict(mdstat_health={'options': [None, 'Faulty devices in MD', 'failed disks',
107 - 'health', 'md.health', 'line'],
108 - 'lines': []})
109 - for md in arrays:
110 - order.append(md)
111 - order.append(md + '_status')
112 - order.append(md + '_rate')
113 - definitions['mdstat_health']['lines'].append([md + '_health', md, 'absolute'])
114 - definitions[md] = {'options': [None, '%s disks stats' % md, 'disks', md, 'md.disks', 'stacked'],
115 - 'lines': [[md + '_total_disks', 'total', 'absolute'],
116 - [md + '_inuse_disks', 'inuse', 'absolute']]}
117 - definitions[md + '_status'] = {'options': [None, '%s current status' % md,
118 - 'percent', md, 'md.status', 'line'],
119 - 'lines': [[md + '_resync', 'resync', 'absolute', 1, 100],
120 - [md + '_recovery', 'recovery', 'absolute', 1, 100],
121 - [md + '_reshape', 'reshape', 'absolute', 1, 100],
122 - [md + '_check', 'check', 'absolute', 1, 100]]}
123 - definitions[md + '_rate'] = {'options': [None, '%s operation status' % md,
124 - 'rate', md, 'md.rate', 'line'],
125 - 'lines': [[md + '_finish', 'finish min', 'absolute', 1, 100],
126 - [md + '_speed', 'MB/s', 'absolute', -1, 100]]}
127 - return order, definitions