glances.plugins.raid.PluginModel.msg_curse()   F
last analyzed

Complexity

Conditions 18

Size

Total Lines 87
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 63
nop 3
dl 0
loc 87
rs 1.2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like glances.plugins.raid.PluginModel.msg_curse() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#
2
# This file is part of Glances.
3
#
4
# SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
"""RAID plugin."""
10
11
from glances.globals import iterkeys
12
from glances.logger import logger
13
from glances.plugins.plugin.model import GlancesPluginModel
14
15
# Import plugin specific dependency
16
try:
17
    from pymdstat import MdStat
18
except ImportError as e:
19
    import_error_tag = True
20
    logger.warning(f"Missing Python Lib ({e}), Raid plugin is disabled")
21
else:
22
    import_error_tag = False
23
24
25
class PluginModel(GlancesPluginModel):
26
    """Glances RAID plugin.
27
28
    stats is a dict (see pymdstat documentation)
29
    """
30
31
    def __init__(self, args=None, config=None):
32
        """Init the plugin."""
33
        super().__init__(args=args, config=config)
34
35
        # We want to display the stat in the curse interface
36
        self.display_curse = True
37
38
    @GlancesPluginModel._check_decorator
39
    @GlancesPluginModel._log_result_decorator
40
    def update(self):
41
        """Update RAID stats using the input method."""
42
        # Init new stats
43
        stats = self.get_init_value()
44
45
        if import_error_tag:
46
            return self.stats
47
48
        if self.input_method == 'local':
49
            # Update stats using the PyMDstat lib (https://github.com/nicolargo/pymdstat)
50
            try:
51
                mds = MdStat()
52
                # Just for test: uncomment the following line to use a local file
53
                # mds = MdStat(path='/home/nicolargo/dev/pymdstat/tests/mdstat.10')
54
                stats = mds.get_stats()['arrays']
55
            except Exception as e:
56
                logger.debug(f"Can not grab RAID stats ({e})")
57
                return self.stats
58
59
        elif self.input_method == 'snmp':
60
            # Update stats using SNMP
61
            # No standard way for the moment...
62
            pass
63
64
        # Update the stats
65
        self.stats = stats
66
67
        return self.stats
68
69
    def msg_curse(self, args=None, max_width=None):
70
        """Return the dict to display in the curse interface."""
71
        # Init the return message
72
        ret = []
73
74
        # Only process if stats exist...
75
        if not self.stats or self.is_disabled():
76
            return ret
77
78
        # Max size for the interface name
79
        if max_width:
80
            name_max_width = max_width - 12
81
        else:
82
            # No max_width defined, return an empty curse message
83
            logger.debug(f"No max_width defined for the {self.plugin_name} plugin, it will not be displayed.")
84
            return ret
85
86
        # Header
87
        msg = '{:{width}}'.format('RAID disks', width=name_max_width)
88
        ret.append(self.curse_add_line(msg, "TITLE"))
89
        msg = '{:>7}'.format('Used')
90
        ret.append(self.curse_add_line(msg))
91
        msg = '{:>7}'.format('Avail')
92
        ret.append(self.curse_add_line(msg))
93
        # Data
94
        arrays = sorted(iterkeys(self.stats))
95
        for array in arrays:
96
            array_stats = self.stats[array]
97
98
            if not isinstance(array_stats, dict):
99
                continue
100
101
            # Display the current status
102
            status = self.raid_alert(
103
                array_stats['status'],
104
                array_stats['used'],
105
                array_stats['available'],
106
                array_stats['type'],
107
            )
108
109
            # New line
110
            ret.append(self.curse_new_line())
111
            # Data: RAID type name | disk used | disk available
112
            array_type = array_stats['type'].upper() if array_stats['type'] is not None else 'UNKNOWN'
113
            # Build the full name = array type + array name
114
            full_name = f'{array_type} {array}'
115
            msg = '{:{width}}'.format(full_name, width=name_max_width)
116
            ret.append(self.curse_add_line(msg))
117
            if array_stats['type'] == 'raid0' and array_stats['status'] == 'active':
118
                msg = '{:>7}'.format(len(array_stats['components']))
119
                ret.append(self.curse_add_line(msg, status))
120
                msg = '{:>7}'.format('-')
121
                ret.append(self.curse_add_line(msg, status))
122
            elif array_stats['status'] == 'active':
123
                msg = '{:>7}'.format(array_stats['used'])
124
                ret.append(self.curse_add_line(msg, status))
125
                msg = '{:>7}'.format(array_stats['available'])
126
                ret.append(self.curse_add_line(msg, status))
127
            elif array_stats['status'] == 'inactive':
128
                ret.append(self.curse_new_line())
129
                msg = '└─ Status {}'.format(array_stats['status'])
130
                ret.append(self.curse_add_line(msg, status))
131
                components = sorted(iterkeys(array_stats['components']))
132
                for i, component in enumerate(components):
133
                    if i == len(components) - 1:
134
                        tree_char = '└─'
135
                    else:
136
                        tree_char = '├─'
137
                    ret.append(self.curse_new_line())
138
                    msg = '   {} disk {}: '.format(tree_char, array_stats['components'][component])
139
                    ret.append(self.curse_add_line(msg))
140
                    msg = f'{component}'
141
                    ret.append(self.curse_add_line(msg))
142
143
            if array_stats['type'] != 'raid0' and (
144
                array_stats['used'] and array_stats['available'] and array_stats['used'] < array_stats['available']
145
            ):
146
                # Display current array configuration
147
                ret.append(self.curse_new_line())
148
                msg = '└─ Degraded mode'
149
                ret.append(self.curse_add_line(msg, status))
150
                if len(array_stats['config']) < 17:
151
                    ret.append(self.curse_new_line())
152
                    msg = '   └─ {}'.format(array_stats['config'].replace('_', 'A'))
153
                    ret.append(self.curse_add_line(msg))
154
155
        return ret
156
157
    @staticmethod
158
    def raid_alert(status, used, available, raid_type) -> str:
159
        """RAID alert messages.
160
161
        [available/used] means that ideally the array may have _available_
162
        devices however, _used_ devices are in use.
163
        Obviously when used >= available then things are good.
164
        """
165
        if raid_type == 'raid0':
166
            return 'OK'
167
        if status == 'inactive':
168
            return 'CRITICAL'
169
        if used is None or available is None:
170
            return 'DEFAULT'
171
        if used < available:
172
            return 'WARNING'
173
        return 'OK'
174