Browse Source

mdstat: refactor + read mismatch_cnt feature added

lgz 6 years ago
parent
commit
dc3529ef14
1 changed files with 110 additions and 96 deletions
  1. 110 96
      python.d/mdstat.chart.py

+ 110 - 96
python.d/mdstat.chart.py

@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 # Description: mdstat netdata python.d module
-# Author: l2isbad
+# Author: Ilya Mashchenko (l2isbad)
 # SPDX-License-Identifier: GPL-3.0+
 
 import re
@@ -9,11 +9,11 @@ from collections import defaultdict
 
 from bases.FrameworkServices.SimpleService import SimpleService
 
-priority = 60000
-retries = 60
-update_every = 1
+MDSTAT = '/proc/mdstat'
+MISMATCH_CNT = '/sys/block/{0}/md/mismatch_cnt'
 
 ORDER = ['mdstat_health']
+
 CHARTS = {
     'mdstat_health': {
         'options': [None, 'Faulty Devices In MD', 'failed disks', 'health', 'md.health', 'line'],
@@ -21,8 +21,6 @@ CHARTS = {
     }
 }
 
-OPERATIONS = ('check', 'resync', 'reshape', 'recovery', 'finish', 'speed')
-
 RE_DISKS = re.compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+\['
                       r'(?P<total_disks>[0-9]+)/'
                       r'(?P<inuse_disks>[0-9]+)\]')
@@ -30,47 +28,55 @@ RE_DISKS = re.compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+\['
 RE_STATUS = re.compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+ '
                        r'(?P<operation>[a-z]+) =[ ]{1,2}'
                        r'(?P<operation_status>[0-9.]+).+finish='
-                       r'(?P<finish>([0-9.]+))min speed='
+                       r'(?P<finish_in>([0-9.]+))min speed='
                        r'(?P<speed>[0-9]+)')
 
 
-def md_charts(md):
-    order = ['{0}_disks'.format(md.name),
-             '{0}_operation'.format(md.name),
-             '{0}_finish'.format(md.name),
-             '{0}_speed'.format(md.name)
+def md_charts(name):
+    order = ['{0}_disks'.format(name),
+             '{0}_operation'.format(name),
+             '{0}_mismatch_cnt'.format(name),
+             '{0}_finish'.format(name),
+             '{0}_speed'.format(name)
              ]
 
     charts = dict()
     charts[order[0]] = {
-        'options': [None, 'Disks Stats', 'disks', md.name, 'md.disks', 'stacked'],
+        'options': [None, 'Disks Stats', 'disks', name, 'md.disks', 'stacked'],
+        'lines': [
+            ['{0}_total_disks'.format(name), 'total', 'absolute'],
+            ['{0}_inuse_disks'.format(name), 'inuse', 'absolute']
+        ]
+    }
+
+    charts[order[1]] = {
+        'options': [None, 'Current Status', 'percent', name, 'md.status', 'line'],
         'lines': [
-            ['{0}_total_disks'.format(md.name), 'total', 'absolute'],
-            ['{0}_inuse_disks'.format(md.name), 'inuse', 'absolute']
+            ['{0}_resync'.format(name), 'resync', 'absolute', 1, 100],
+            ['{0}_recovery'.format(name), 'recovery', 'absolute', 1, 100],
+            ['{0}_reshape'.format(name), 'reshape', 'absolute', 1, 100],
+            ['{0}_check'.format(name), 'check', 'absolute', 1, 100],
         ]
     }
 
-    charts['_'.join([md.name, 'operation'])] = {
-        'options': [None, 'Current Status', 'percent', md.name, 'md.status', 'line'],
+    charts[order[2]] = {
+        'options': [None, 'Mismatch Count', 'unsynchronized blocks', name, 'md.mismatch_cnt', 'line'],
         'lines': [
-            ['{0}_resync'.format(md.name), 'resync', 'absolute', 1, 100],
-            ['{0}_recovery'.format(md.name), 'recovery', 'absolute', 1, 100],
-            ['{0}_reshape'.format(md.name), 'reshape', 'absolute', 1, 100],
-            ['{0}_check'.format(md.name), 'check', 'absolute', 1, 100]
+            ['{0}_mismatch_cnt'.format(name), 'count', 'absolute']
         ]
     }
 
-    charts['_'.join([md.name, 'finish'])] = {
-        'options': [None, 'Approximate Time Until Finish', 'seconds', md.name, 'md.rate', 'line'],
+    charts[order[3]] = {
+        'options': [None, 'Approximate Time Until Finish', 'seconds', name, 'md.rate', 'line'],
         'lines': [
-            ['{0}_finish'.format(md.name), 'finish in', 'absolute', 1, 1000]
+            ['{0}_finish_in'.format(name), 'finish in', 'absolute', 1, 1000]
         ]
     }
 
-    charts['_'.join([md.name, 'speed'])] = {
-        'options': [None, 'Operation Speed', 'KB/s', md.name, 'md.rate', 'line'],
+    charts[order[4]] = {
+        'options': [None, 'Operation Speed', 'KB/s', name, 'md.rate', 'line'],
         'lines': [
-            ['{0}_speed'.format(md.name), 'speed', 'absolute', 1, 1000]
+            ['{0}_speed'.format(name), 'speed', 'absolute', 1, 1000]
         ]
     }
 
@@ -78,17 +84,33 @@ def md_charts(md):
 
 
 class MD:
-    def __init__(self, name, stats):
-        self.name = name
-        self.stats = stats
-
-    def update_stats(self, stats):
-        self.stats = stats
+    def __init__(self, raw_data):
+        self.name = raw_data["array"]
+        self.d = raw_data
 
     def data(self):
-        stats = dict(('_'.join([self.name, k]), v) for k, v in self.stats.items())
-        stats['{0}_health'.format(self.name)] = int(self.stats['total_disks']) - int(self.stats['inuse_disks'])
-        return stats
+        rv = {
+            'total_disks': self.d['total_disks'],
+            'inuse_disks': self.d['inuse_disks'],
+            'health': int(self.d['total_disks']) - int(self.d['inuse_disks']),
+            'resync': 0,
+            'recovery': 0,
+            'reshape': 0,
+            'check': 0,
+            'finish_in': 0,
+            'speed': 0,
+        }
+
+        v = read_lines(MISMATCH_CNT.format(self.name))
+        if v:
+            rv['mismatch_cnt'] = v
+
+        if self.d.get('operation'):
+            rv[self.d['operation']] = float(self.d['operation_status']) * 100
+            rv['finish_in'] = float(self.d['finish_in']) * 1000 * 60
+            rv['speed'] = float(self.d['speed']) * 1000
+
+        return dict(('{0}_{1}'.format(self.name, k), v) for k, v in rv.items())
 
 
 class Service(SimpleService):
@@ -96,67 +118,56 @@ class Service(SimpleService):
         SimpleService.__init__(self, configuration=configuration, name=name)
         self.order = ORDER
         self.definitions = CHARTS
-        self.mds = dict()
-
-    def check(self):
-        arrays = find_arrays(self._get_raw_data())
-        if not arrays:
-            self.error('Failed to read data from /proc/mdstat or there is no active arrays')
-            return None
-        return True
+        self.mds = list()
 
     @staticmethod
-    def _get_raw_data():
-        """
-        Read data from /proc/mdstat
-        :return: str
-        """
-        try:
-            with open('/proc/mdstat', 'rt') as proc_mdstat:
-                return proc_mdstat.readlines() or None
-        except (OSError, IOError):
+    def get_mds():
+        raw = read_lines(MDSTAT)
+
+        if not raw:
             return None
 
+        return find_mds(raw)
+
     def get_data(self):
         """
         Parse data from _get_raw_data()
         :return: dict
         """
-        arrays = find_arrays(self._get_raw_data())
-        if not arrays:
+        mds = self.get_mds()
+
+        if not mds:
             return None
 
         data = dict()
-        for array, values in arrays.items():
-
-            if array not in self.mds:
-                md = MD(array, values)
-                self.mds[md.name] = md
-                self.create_new_array_charts(md)
-            else:
-                md = self.mds[array]
-                md.update_stats(values)
-
+        for md in mds:
+            if md.name not in self.mds:
+                self.mds.append(md.name)
+                self.add_new_md_charts(md.name)
             data.update(md.data())
-
         return data
 
-    def create_new_array_charts(self, md):
-        order, charts = md_charts(md)
+    def check(self):
+        if not self.get_mds():
+            self.error('Failed to read data from {0} or there is no active arrays'.format(MDSTAT))
+            return False
+        return True
+
+    def add_new_md_charts(self, name):
+        order, charts = md_charts(name)
+
+        self.charts['mdstat_health'].add_dimension(['{0}_health'.format(name), name])
 
-        self.charts['mdstat_health'].add_dimension(['{0}_health'.format(md.name), md.name])
         for chart_name in order:
             params = [chart_name] + charts[chart_name]['options']
-            dimensions = charts[chart_name]['lines']
+            dims = charts[chart_name]['lines']
 
-            new_chart = self.charts.add_chart(params)
-            for dimension in dimensions:
-                new_chart.add_dimension(dimension)
+            chart = self.charts.add_chart(params)
+            for dim in dims:
+                chart.add_dimension(dim)
 
 
-def find_arrays(raw_data):
-    if raw_data is None:
-        return None
+def find_mds(raw_data):
     data = defaultdict(str)
     counter = 1
 
@@ -166,25 +177,28 @@ def find_arrays(raw_data):
             continue
         data[counter] = ' '.join([data[counter], row])
 
-    arrays = dict()
-    for value in data.values():
-        match = RE_DISKS.search(value)
-        if not match:
+    mds = list()
+
+    for v in data.values():
+        m = RE_DISKS.search(v)
+
+        if not m:
             continue
 
-        match = match.groupdict()
-        array = match.pop('array')
-        arrays[array] = match
-        for operation in OPERATIONS:
-            arrays[array][operation] = 0
-
-        match = RE_STATUS.search(value)
-        if match:
-            match = match.groupdict()
-            if match['operation'] in OPERATIONS:
-                arrays[array]['operation'] = match['operation']
-                arrays[array][match['operation']] = float(match['operation_status']) * 100
-                arrays[array]['finish'] = float(match['finish']) * 1000 * 60
-                arrays[array]['speed'] = float(match['speed']) * 1000
-
-    return arrays or None
+        d = m.groupdict()
+
+        m = RE_STATUS.search(v)
+        if m:
+            d.update(m.groupdict())
+
+        mds.append(MD(d))
+
+    return sorted(mds, key=lambda md: md.name)
+
+
+def read_lines(path):
+    try:
+        with open(path) as f:
+            return f.readlines()
+    except (IOError, OSError):
+        return None