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