QuicklookPlugin.msg_curse()   F
last analyzed

Complexity

Conditions 19

Size

Total Lines 70
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 38
nop 3
dl 0
loc 70
rs 0.5999
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.quicklook.QuicklookPlugin.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: 2024 Nicolas Hennion <[email protected]>
5
#
6
# SPDX-License-Identifier: LGPL-3.0-only
7
#
8
9
"""Quicklook plugin."""
10
11
import psutil
12
13
from glances.cpu_percent import cpu_percent
14
from glances.logger import logger
15
from glances.outputs.glances_bars import Bar
16
from glances.outputs.glances_sparklines import Sparkline
17
from glances.plugins.fs.zfs import zfs_enable, zfs_stats
18
from glances.plugins.load import get_load_average, get_nb_log_core, get_nb_phys_core
19
from glances.plugins.plugin.model import GlancesPluginModel
20
21
# Fields description
22
# description: human readable description
23
# short_name: shortname to use un UI
24
# unit: unit type
25
# rate: is it a rate ? If yes, // by time_since_update when displayed,
26
# min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)...
27
fields_description = {
28
    'cpu': {
29
        'description': 'CPU percent usage',
30
        'unit': 'percent',
31
    },
32
    'mem': {
33
        'description': 'MEM percent usage',
34
        'unit': 'percent',
35
    },
36
    'swap': {
37
        'description': 'SWAP percent usage',
38
        'unit': 'percent',
39
    },
40
    'load': {
41
        'description': 'LOAD percent usage',
42
        'unit': 'percent',
43
        'alert': True,
44
    },
45
    'cpu_log_core': {
46
        'description': 'Number of logical CPU core',
47
        'unit': 'number',
48
    },
49
    'cpu_phys_core': {
50
        'description': 'Number of physical CPU core',
51
        'unit': 'number',
52
    },
53
    'cpu_name': {
54
        'description': 'CPU name',
55
    },
56
    'cpu_hz_current': {
57
        'description': 'CPU current frequency',
58
        'unit': 'hertz',
59
    },
60
    'cpu_hz': {
61
        'description': 'CPU max frequency',
62
        'unit': 'hertz',
63
    },
64
}
65
66
# Define the history items list
67
# All items in this list will be historised if the --enable-history tag is set
68
items_history_list = [
69
    {'name': 'cpu', 'description': 'CPU percent usage', 'y_unit': '%'},
70
    {'name': 'percpu', 'description': 'PERCPU percent usage', 'y_unit': '%'},
71
    {'name': 'mem', 'description': 'MEM percent usage', 'y_unit': '%'},
72
    {'name': 'swap', 'description': 'SWAP percent usage', 'y_unit': '%'},
73
    {'name': 'load', 'description': 'LOAD percent usage', 'y_unit': '%'},
74
]
75
76
77
class QuicklookPlugin(GlancesPluginModel):
78
    """Glances quicklook plugin.
79
80
    'stats' is a dictionary.
81
    """
82
83
    AVAILABLE_STATS_LIST = ['cpu', 'mem', 'swap', 'load']
84
    DEFAULT_STATS_LIST = ['cpu', 'mem', 'load']
85
86
    def __init__(self, args=None, config=None):
87
        """Init the quicklook plugin."""
88
        super().__init__(
89
            args=args, config=config, items_history_list=items_history_list, fields_description=fields_description
90
        )
91
92
        # ZFS
93
        self.zfs_enabled = zfs_enable()
94
95
        # We want to display the stat in the curse interface
96
        self.display_curse = True
97
98
        # Manage the maximum number of CPU to display (related to enhancement request #2734)
99
        self.max_cpu_display = config.get_int_value('percpu', 'max_cpu_display', 4) if config else 4
100
101
        # Define the stats list
102
        self.stats_list = self.get_conf_value('list', default=self.DEFAULT_STATS_LIST)
103
        if not set(self.stats_list).issubset(self.AVAILABLE_STATS_LIST):
104
            logger.warning(f'Quicklook plugin: Invalid stats list: {self.stats_list}')
105
            self.stats_list = self.AVAILABLE_STATS_LIST
106
107
    @GlancesPluginModel._check_decorator
108
    @GlancesPluginModel._log_result_decorator
109
    def update(self):
110
        """Update quicklook stats using the input method."""
111
        # Init new stats
112
        stats = self.get_init_value()
113
114
        # Grab quicklook stats: CPU, MEM and SWAP
115
        if self.input_method == 'local':
116
            # Get system information
117
            cpu_info = cpu_percent.get_info()
118
            stats['cpu_name'] = cpu_info['cpu_name']
119
            stats['cpu_hz_current'] = (
120
                self._mhz_to_hz(cpu_info['cpu_hz_current']) if cpu_info['cpu_hz_current'] is not None else None
121
            )
122
            stats['cpu_hz'] = self._mhz_to_hz(cpu_info['cpu_hz']) if cpu_info['cpu_hz'] is not None else None
123
124
            # Get the CPU percent value (global and per core)
125
            # Stats is shared across all plugins
126
            stats['cpu'] = cpu_percent.get_cpu()
127
            stats['percpu'] = cpu_percent.get_percpu()
128
129
            # Get the virtual and swap memory
130
            vm_stats = psutil.virtual_memory()
131
            mem_total = vm_stats.total
132
            mem_available = vm_stats.available
133
            if self.zfs_enabled:
134
                zfs_cache_stats = zfs_stats()
135
                mem_available = (
136
                    mem_available + zfs_cache_stats.get('arcstats.size', 0) - zfs_cache_stats.get('arcstats.c_min', 0)
137
                )
138
139
            stats['mem'] = float((mem_total - mem_available) / mem_total * 100)
140
            try:
141
                stats['swap'] = psutil.swap_memory().percent
142
            except RuntimeError:
143
                # Correct issue in Illumos OS (see #1767)
144
                stats['swap'] = None
145
146
            # Get load
147
            stats['cpu_log_core'] = get_nb_log_core()
148
            stats['cpu_phys_core'] = get_nb_phys_core()
149
            try:
150
                # Load average is a tuple (1 min, 5 min, 15 min)
151
                # Process only the 15 min value (index 2)
152
                stats['load'] = get_load_average(percent=True)[2]
153
            except (TypeError, IndexError):
154
                stats['load'] = None
155
156
        elif self.input_method == 'snmp':
157
            # Not available
158
            pass
159
160
        # Update the stats
161
        self.stats = stats
162
163
        return self.stats
164
165
    def update_views(self):
166
        """Update stats views."""
167
        # Call the father's method
168
        super().update_views()
169
170
        # Alert for CPU, MEM and SWAP
171
        for key in self.stats_list:
172
            if key in self.stats:
173
                self.views[key]['decoration'] = self.get_alert(self.stats[key], header=key)
174
175
        # Define the list of stats to display
176
        self.views['list'] = self.stats_list
177
178
    def msg_curse(self, args=None, max_width=10):
179
        """Return the list to display in the UI."""
180
        # Init the return message
181
        ret = []
182
183
        # Only process if stats exist...
184
        if not self.stats or self.is_disabled():
185
            return ret
186
187
        if not max_width:
188
            # No max_width defined, return an empty message
189
            logger.debug(f"No max_width defined for the {self.plugin_name} plugin, it will not be displayed.")
190
            return ret
191
192
        # Define the data: Bar (default behavior) or Sparkline
193
        data = {}
194
        for key in self.stats_list:
195
            if self.args.sparkline and self.history_enable() and not self.args.client:
196
                data[key] = Sparkline(max_width)
197
            else:
198
                # Fallback to bar if Sparkline module is not installed
199
                data[key] = Bar(max_width, bar_char=self.get_conf_value('bar_char', default=['|'])[0])
200
201
        # Build the string message
202
        ##########################
203
204
        # System information
205
        if (
206
            'cpu_hz_current' in self.stats
207
            and self.stats['cpu_hz_current'] is not None
208
            and 'cpu_hz' in self.stats
209
            and self.stats['cpu_hz'] is not None
210
        ):
211
            msg_freq = ' {:.2f}/{:.2f}GHz'.format(
212
                self._hz_to_ghz(self.stats['cpu_hz_current']), self._hz_to_ghz(self.stats['cpu_hz'])
213
            )
214
        else:
215
            msg_freq = ''
216
217
        if 'cpu_name' in self.stats and (max_width - len(msg_freq) + 7) > 0:
218
            msg_name = '{:{width}}'.format(self.stats['cpu_name'], width=max_width - len(msg_freq) + 7)
219
        else:
220
            msg_name = '' if msg_freq == '' else 'Frequency'
221
222
        ret.append(self.curse_add_line(msg_name))
223
        ret.append(self.curse_add_line(msg_freq))
224
        ret.append(self.curse_new_line())
225
226
        # Loop over CPU, MEM and LOAD
227
        for key in self.stats_list:
228
            if key == 'cpu' and args.percpu:
229
                ret.extend(self._msg_per_cpu(data, key, max_width))
230
            else:
231
                if type(data[key]).__name__ == 'Sparkline':
232
                    # Sparkline display an history
233
                    data[key].percents = [i[1] for i in self.get_raw_history(item=key, nb=data[key].size)]
234
                    # A simple padding in order to align metrics to the right
235
                    data[key].percents += [None] * (data[key].size - len(data[key].percents))
236
                else:
237
                    # Bar only the last value
238
                    data[key].percent = self.stats[key]
239
                msg = f'{key.upper():4} '
240
                ret.extend(self._msg_create_line(msg, data[key], key))
241
                ret.append(self.curse_new_line())
242
243
        # Remove the last new line
244
        ret.pop()
245
246
        # Return the message with decoration
247
        return ret
248
249
    def _msg_per_cpu(self, data, key, max_width):
250
        """Create per-cpu view"""
251
        ret = []
252
253
        # Get history (only used with the sparkline option)
254
        if type(data[key]).__name__ == 'Sparkline':
255
            raw_cpu = self.get_raw_history(item='percpu', nb=data[key].size)
256
257
        # Manage the maximum number of CPU to display (related to enhancement request #2734)
258
        if len(self.stats['percpu']) > self.max_cpu_display:
259
            # If the number of CPU is > max_cpu_display then sort and display top 'n'
260
            percpu_list = sorted(self.stats['percpu'], key=lambda x: x['total'], reverse=True)
261
        else:
262
            percpu_list = self.stats['percpu']
263
264
        # Display the first max_cpu_display CPU
265
        for cpu in percpu_list[0 : self.max_cpu_display]:
266
            cpu_id = cpu[cpu['key']]
267
            if type(data[key]).__name__ == 'Sparkline':
268
                # Sparkline display an history
269
                data[key].percents = [i[1][cpu_id]['total'] for i in raw_cpu]
0 ignored issues
show
introduced by
The variable raw_cpu does not seem to be defined in case type(SubscriptNode).__name__ == 'Sparkline' on line 254 is False. Are you sure this can never be the case?
Loading history...
270
                # A simple padding in order to align metrics to the right
271
                data[key].percents += [None] * (data[key].size - len(data[key].percents))
272
            else:
273
                # Bar will only display the last value
274
                data[key].percent = cpu['total']
275
            if cpu_id < 10:
276
                msg = f'{key.upper():3}{cpu_id} '
277
            else:
278
                msg = f'{cpu_id:4} '
279
            ret.extend(self._msg_create_line(msg, data[key], key))
280
            ret.append(self.curse_new_line())
281
282
        # Add a new line with sum of all others CPU
283
        if len(self.stats['percpu']) > self.max_cpu_display:
284
            if type(data[key]).__name__ == 'Sparkline':
285
                sum_other = Sparkline(max_width)
286
                # Sparkline display an history
287
                sum_other.percents = [
288
                    sum([i['total'] for i in r[1] if i[i['key']] >= self.max_cpu_display])
289
                    / len([i['total'] for i in r[1] if i[i['key']] >= self.max_cpu_display])
290
                    for r in raw_cpu
291
                ]
292
                # A simple padding in order to align metrics to the right
293
                sum_other.percents += [None] * (sum_other.size - len(sum_other.percents))
294
            else:
295
                # Bar will only display the last value
296
                sum_other = Bar(max_width, bar_char=self.get_conf_value('bar_char', default=['|'])[0])
297
                sum_other.percent = sum([i['total'] for i in percpu_list[self.max_cpu_display :]]) / len(
298
                    percpu_list[self.max_cpu_display :]
299
                )
300
            msg = msg = f'{key.upper():3}* '
301
            ret.extend(self._msg_create_line(msg, sum_other, key))
302
            ret.append(self.curse_new_line())
303
304
        return ret
305
306
    def _msg_create_line(self, msg, data, key):
307
        """Create a new line to the Quick view."""
308
        return [
309
            self.curse_add_line(msg),
310
            self.curse_add_line('[', decoration='BOLD'),
311
            self.curse_add_line(data.get(), self.get_views(key=key, option='decoration')),
312
            self.curse_add_line(']', decoration='BOLD'),
313
            self.curse_add_line('  '),
314
        ]
315
316
    def _hz_to_ghz(self, hz):
317
        """Convert Hz to Ghz."""
318
        return hz / 1000000000.0
319
320
    def _mhz_to_hz(self, hz):
321
        """Convert Mhz to Hz."""
322
        return hz * 1000000.0
323