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