@cryptotaxi247 / netdata-1 / commits / dc3529ef1

mdstat: refactor + read mismatch_cnt feature added

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