Test Failed
Pull Request — develop (#2891)
by
unknown
02:11
created

PluginModel.display_cpu_header_in_columns()   A

Complexity

Conditions 4

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nop 3
dl 0
loc 15
rs 9.8
c 0
b 0
f 0
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
"""Per-CPU plugin."""
10
11
from glances.cpu_percent import cpu_percent
12
from glances.globals import BSD, LINUX, MACOS, WINDOWS
13
from glances.plugins.plugin.model import GlancesPluginModel
14
15
# Fields description
16
# description: human readable description
17
# short_name: shortname to use un UI
18
# unit: unit type
19
# rate: is it a rate ? If yes, // by time_since_update when displayed,
20
# min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)...
21
fields_description = {
22
    'cpu_number': {
23
        'description': 'CPU number',
24
    },
25
    'total': {
26
        'description': 'Sum of CPU percentages (except idle) for current CPU number.',
27
        'unit': 'percent',
28
    },
29
    'system': {
30
        'description': 'Percent time spent in kernel space. System CPU time is the \
31
time spent running code in the Operating System kernel.',
32
        'unit': 'percent',
33
    },
34
    'user': {
35
        'description': 'CPU percent time spent in user space. \
36
User CPU time is the time spent on the processor running your program\'s code (or code in libraries).',
37
        'unit': 'percent',
38
    },
39
    'iowait': {
40
        'description': '*(Linux)*: percent time spent by the CPU waiting for I/O \
41
operations to complete.',
42
        'unit': 'percent',
43
    },
44
    'idle': {
45
        'description': 'percent of CPU used by any program. Every program or task \
46
that runs on a computer system occupies a certain amount of processing \
47
time on the CPU. If the CPU has completed all tasks it is idle.',
48
        'unit': 'percent',
49
    },
50
    'irq': {
51
        'description': '*(Linux and BSD)*: percent time spent servicing/handling \
52
hardware/software interrupts. Time servicing interrupts (hardware + \
53
software).',
54
        'unit': 'percent',
55
    },
56
    'nice': {
57
        'description': '*(Unix)*: percent time occupied by user level processes with \
58
a positive nice value. The time the CPU has spent running users\' \
59
processes that have been *niced*.',
60
        'unit': 'percent',
61
    },
62
    'steal': {
63
        'description': '*(Linux)*: percentage of time a virtual CPU waits for a real \
64
CPU while the hypervisor is servicing another virtual processor.',
65
        'unit': 'percent',
66
    },
67
    'guest': {
68
        'description': '*(Linux)*: percent of time spent running a virtual CPU for \
69
guest operating systems under the control of the Linux kernel.',
70
        'unit': 'percent',
71
    },
72
    'guest_nice': {
73
        'description': '*(Linux)*: percent of time spent running a niced guest (virtual CPU).',
74
        'unit': 'percent',
75
    },
76
    'softirq': {
77
        'description': '*(Linux)*: percent of time spent handling software interrupts.',
78
        'unit': 'percent',
79
    },
80
    'dpc': {
81
        'description': '*(Windows)*: percent of time spent handling deferred procedure calls.',
82
        'unit': 'percent',
83
    },
84
    'interrupt': {
85
        'description': '*(Windows)*: percent of time spent handling software interrupts.',
86
        'unit': 'percent',
87
    },
88
}
89
90
# Define the history items list
91
items_history_list = [
92
    {'name': 'user', 'description': 'User CPU usage', 'y_unit': '%'},
93
    {'name': 'system', 'description': 'System CPU usage', 'y_unit': '%'},
94
]
95
96
97
class PluginModel(GlancesPluginModel):
98
    """Glances per-CPU plugin.
99
100
    'stats' is a list of dictionaries that contain the utilization percentages
101
    for each CPU.
102
    """
103
104
    def __init__(self, args=None, config=None):
105
        """Init the plugin."""
106
        super().__init__(
107
            args=args,
108
            config=config,
109
            items_history_list=items_history_list,
110
            stats_init_value=[],
111
            fields_description=fields_description,
112
        )
113
114
        # We want to display the stat in the curse interface
115
        self.display_curse = True
116
117
        # Manage the maximum number of CPU to display (related to enhancement request #2734)
118
        if config:
119
            self.max_cpu_display = config.get_int_value('percpu', 'max_cpu_display', 4)
120
        else:
121
            self.max_cpu_display = 4
122
123
    def get_key(self):
124
        """Return the key of the list."""
125
        return 'cpu_number'
126
127
    @GlancesPluginModel._check_decorator
128
    @GlancesPluginModel._log_result_decorator
129
    def update(self):
130
        """Update per-CPU stats using the input method."""
131
        # Grab per-CPU stats using psutil's
132
        if self.input_method == 'local':
133
            stats = cpu_percent.get_percpu()
134
        else:
135
            # Update stats using SNMP
136
            stats = self.get_init_value()
137
138
        # Update the stats
139
        self.stats = stats
140
141
        return self.stats
142
143
    def define_headers_from_os(self):
144
        base = ['user', 'system']
145
146
        if LINUX:
147
            extension = ['iowait', 'idle', 'irq', 'nice', 'steal', 'guest']
148
        elif MACOS:
149
            extension = ['idle', 'nice']
150
        elif BSD:
151
            extension = ['idle', 'irq', 'nice']
152
        elif WINDOWS:
153
            extension = ['dpc', 'interrupt']
154
155
        return base + extension
0 ignored issues
show
introduced by
The variable extension does not seem to be defined for all execution paths.
Loading history...
156
157
    def maybe_build_string_msg(self, header, return_):
158
        if self.is_disabled('quicklook'):
159
            msg = '{:5}'.format('CPU')
160
            return_.append(self.curse_add_line(msg, "TITLE"))
161
            header.insert(0, 'total')
162
163
        return (header, return_)
164
165
    def display_cpu_stats_per_line(self, header, return_):
166
        for stat in header:
167
            msg = f'{stat:>7}'
168
            return_.append(self.curse_add_line(msg))
169
170
        return return_
171
172
    def manage_max_cpu_to_display(self):
173
        if len(self.stats) > self.max_cpu_display:
174
            # sort and display top 'n'
175
            percpu_list = sorted(self.stats, key=lambda x: x['total'], reverse=True)
176
        else:
177
            percpu_list = self.stats
178
179
        return percpu_list
180
181
    def display_cpu_header_in_columns(self, cpu, return_):
182
        return_.append(self.curse_new_line())
183
        if self.is_disabled('quicklook'):
184
            try:
185
                cpu_id = cpu[cpu['key']]
186
                if cpu_id < 10:
187
                    msg = f'CPU{cpu_id:1} '
188
                else:
189
                    msg = f'{cpu_id:4} '
190
            except TypeError:
191
                # TypeError: string indices must be integers (issue #1027)
192
                msg = '{:4} '.format('?')
193
            return_.append(self.curse_add_line(msg))
194
195
        return return_
196
197
    def display_cpu_stats_in_columns(self, cpu, header, return_):
198
        for stat in header:
199
            try:
200
                msg = f'{cpu[stat]:6.1f}%'
201
            except TypeError:
202
                msg = '{:>6}%'.format('?')
203
                return_.append(self.curse_add_line(msg, self.get_alert(cpu[stat], header=stat)))
204
        return return_
205
206
    def summarize_all_cpus_not_displayed(self, percpu_list, header, return_):
207
        if len(self.stats) > self.max_cpu_display:
208
            return_.append(self.curse_new_line())
209
            if self.is_disabled('quicklook'):
210
                return_.append(self.curse_add_line('CPU* '))
211
212
            for stat in header:
213
                percpu_stats = [i[stat] for i in percpu_list[0 : self.max_cpu_display]]
214
                cpu_stat = sum(percpu_stats) / len(percpu_stats)
215
                try:
216
                    msg = f'{cpu_stat:6.1f}%'
217
                except TypeError:
218
                    msg = '{:>6}%'.format('?')
219
                return_.append(self.curse_add_line(msg, self.get_alert(cpu_stat, header=stat)))
220
        return return_
221
222
    def msg_curse(self, args=None, max_width=None):
223
        """Return the dict to display in the curse interface."""
224
        # Init the return message
225
        return_ = []
226
227
        # Only process if stats exist...
228
        missing = [not self.stats, not self.args.percpu, self.is_disabled()]
229
        if any(missing):
230
            return return_
231
232
        # Define the headers based on OS
233
        header = self.define_headers_from_os()
234
235
        # Build the string message
236
        header, return_ = self.maybe_build_string_msg(header, return_)
237
238
        # Per CPU stats displayed per line
239
        return_ = self.display_cpu_stats_per_line(header, return_)
240
241
        # Manage the maximum number of CPU to display (related to enhancement request #2734)
242
        percpu_list = self.manage_max_cpu_to_display()
243
244
        # Per CPU stats displayed per column
245
        for cpu in percpu_list[0 : self.max_cpu_display]:
246
            header_added = self.display_cpu_header_in_columns(cpu, return_)
247
            stats_added = self.display_cpu_stats_in_columns(cpu, header, header_added)
248
249
        # Add a new line with sum of all others CPU
250
        return_ = self.summarize_all_cpus_not_displayed(percpu_list, header, stats_added)
0 ignored issues
show
introduced by
The variable stats_added does not seem to be defined in case the for loop on line 245 is not entered. Are you sure this can never be the case?
Loading history...
251
252
        return return_
253