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 |
|
|
|
|
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
|
|
|
|
205
|
|
|
return return_ |
206
|
|
|
|
207
|
|
|
def summarize_all_cpus_not_displayed(self, percpu_list, header, return_): |
208
|
|
|
if len(self.stats) > self.max_cpu_display: |
209
|
|
|
return_.append(self.curse_new_line()) |
210
|
|
|
if self.is_disabled('quicklook'): |
211
|
|
|
return_.append(self.curse_add_line('CPU* ')) |
212
|
|
|
|
213
|
|
|
for stat in header: |
214
|
|
|
percpu_stats = [i[stat] for i in percpu_list[0 : self.max_cpu_display]] |
215
|
|
|
cpu_stat = sum(percpu_stats) / len(percpu_stats) |
216
|
|
|
try: |
217
|
|
|
msg = f'{cpu_stat:6.1f}%' |
218
|
|
|
except TypeError: |
219
|
|
|
msg = '{:>6}%'.format('?') |
220
|
|
|
return_.append(self.curse_add_line(msg, self.get_alert(cpu_stat, header=stat))) |
221
|
|
|
|
222
|
|
|
return return_ |
223
|
|
|
|
224
|
|
|
def msg_curse(self, args=None, max_width=None): |
225
|
|
|
"""Return the list of dict to display in the curse interface.""" |
226
|
|
|
|
227
|
|
|
# Init the return message |
228
|
|
|
return_ = [] |
229
|
|
|
|
230
|
|
|
# Only process if stats exist... |
231
|
|
|
missing = [not self.stats, not self.args.percpu, self.is_disabled()] |
232
|
|
|
if any(missing): |
233
|
|
|
return return_ |
234
|
|
|
|
235
|
|
|
# Define the headers based on OS |
236
|
|
|
header = self.define_headers_from_os() |
237
|
|
|
|
238
|
|
|
# Build the string message |
239
|
|
|
header, return_ = self.maybe_build_string_msg(header, return_) |
240
|
|
|
|
241
|
|
|
# Per CPU stats displayed per line |
242
|
|
|
return_ = self.display_cpu_stats_per_line(header, return_) |
243
|
|
|
|
244
|
|
|
# Manage the maximum number of CPU to display (related to enhancement request #2734) |
245
|
|
|
percpu_list = self.manage_max_cpu_to_display() |
246
|
|
|
|
247
|
|
|
# Per CPU stats displayed per column |
248
|
|
|
for cpu in percpu_list[0 : self.max_cpu_display]: |
249
|
|
|
header_added = self.display_cpu_header_in_columns(cpu, return_) |
250
|
|
|
stats_added = self.display_cpu_stats_in_columns(cpu, header, header_added) |
251
|
|
|
|
252
|
|
|
# Add a new line with sum of all others CPU |
253
|
|
|
return self.summarize_all_cpus_not_displayed(percpu_list, header, stats_added) |
|
|
|
|
254
|
|
|
|