summaryrefslogtreecommitdiffstats
path: root/python.d/mdstat.chart.py
blob: 7ce7b193290f58cf2136435e3c85f20b356a418e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# -*- coding: utf-8 -*-
# Description: mdstat netdata python.d module
# Author: l2isbad

from re import compile as re_compile
from collections import defaultdict
from base import SimpleService

priority = 60000
retries = 60
update_every = 1

OPERATIONS = ('check', 'resync', 'reshape', 'recovery', 'finish', 'speed')


class Service(SimpleService):
    def __init__(self, configuration=None, name=None):
        SimpleService.__init__(self, configuration=configuration, name=name)
        self.regex = dict(disks=re_compile(r' (?P<array>[a-zA-Z_0-9]+) : active .+\['
                                           r'(?P<total_disks>[0-9]+)/'
                                           r'(?P<inuse_disks>[0-9])\]'),
                          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<speed>[0-9]+)'))

    def check(self):
        arrays = find_arrays(self._get_raw_data(), self.regex)
        if not arrays:
            self.error('Failed to read data from /proc/mdstat or there is no active arrays')
            return None

        self.order, self.definitions = create_charts(arrays.keys())
        return True

    @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):
            return None

    def _get_data(self):
        """
        Parse data from _get_raw_data()
        :return: dict
        """
        raw_data = self._get_raw_data()
        arrays = find_arrays(raw_data, self.regex)
        if not arrays:
            return None

        to_netdata = dict()
        for array, values in arrays.items():
            for key, value in values.items():
                to_netdata['_'.join([array, key])] = value

        return to_netdata


def find_arrays(raw_data, regex):
    if raw_data is None:
        return None
    data = defaultdict(str)
    counter = 1

    for row in (elem.strip() for elem in raw_data):
        if not row:
            counter += 1
            continue
        data[counter] = ' '.join([data[counter], row])

    arrays = dict()
    for value in data.values():
        match = regex['disks'].search(value)
        if not match:
            continue

        match = match.groupdict()
        array = match.pop('array')
        arrays[array] = match
        arrays[array]['health'] = int(match['total_disks']) - int(match['inuse_disks'])
        for operation in OPERATIONS:
            arrays[array][operation] = 0

        match = regex['status'].search(value)
        if match:
            match = match.groupdict()
            if match['operation'] in OPERATIONS:
                arrays[array][match['operation']] = float(match['operation_status']) * 100
                arrays[array]['finish'] = float(match['finish']) * 100
                arrays[array]['speed'] = float(match['speed']) / 1000 * 100

    return arrays or None


def create_charts(arrays):
    order = ['mdstat_health']
    definitions = dict(mdstat_health={'options': [None, 'Faulty devices in MD', 'failed disks',
                                                  'health', 'md.health', 'line'],
                                      'lines': []})
    for md in arrays:
        order.append(md)
        order.append(md + '_status')
        order.append(md + '_rate')
        definitions['mdstat_health']['lines'].append([md + '_health', md, 'absolute'])
        definitions[md] = {'options': [None, '%s disks stats' % md, 'disks', md, 'md.disks', 'stacked'],
                           'lines': [[md + '_total_disks', 'total', 'absolute'],
                                     [md + '_inuse_disks', 'inuse', 'absolute']]}
        definitions[md + '_status'] = {'options': [None, '%s current status' % md,
                                                   'percent', md, 'md.status', 'line'],
                                       'lines': [[md + '_resync', 'resync', 'absolute', 1, 100],
                                                 [md + '_recovery', 'recovery', 'absolute', 1, 100],
                                                 [md + '_reshape', 'reshape', 'absolute', 1, 100],
                                                 [md + '_check', 'check', 'absolute', 1, 100]]}
        definitions[md + '_rate'] = {'options': [None, '%s operation status' % md,
                                                 'rate', md, 'md.rate', 'line'],
                                     'lines': [[md + '_finish', 'finish min', 'absolute', 1, 100],
                                               [md + '_speed', 'MB/s', 'absolute', -1, 100]]}
    return order, definitions