1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
# |
3
|
|
|
# This file is part of Glances. |
4
|
|
|
# |
5
|
|
|
# SPDX-FileCopyrightText: 2022 Nicolas Hennion <[email protected]> |
6
|
|
|
# |
7
|
|
|
# SPDX-License-Identifier: LGPL-3.0-only |
8
|
|
|
# |
9
|
|
|
|
10
|
|
|
"""Per-CPU plugin.""" |
11
|
|
|
|
12
|
|
|
from glances.cpu_percent import cpu_percent |
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
|
|
|
} |
81
|
|
|
|
82
|
|
|
|
83
|
|
|
# Define the history items list |
84
|
|
|
items_history_list = [ |
85
|
|
|
{'name': 'user', 'description': 'User CPU usage', 'y_unit': '%'}, |
86
|
|
|
{'name': 'system', 'description': 'System CPU usage', 'y_unit': '%'}, |
87
|
|
|
] |
88
|
|
|
|
89
|
|
|
|
90
|
|
|
class PluginModel(GlancesPluginModel): |
91
|
|
|
"""Glances per-CPU plugin. |
92
|
|
|
|
93
|
|
|
'stats' is a list of dictionaries that contain the utilization percentages |
94
|
|
|
for each CPU. |
95
|
|
|
""" |
96
|
|
|
|
97
|
|
|
def __init__(self, args=None, config=None): |
98
|
|
|
"""Init the plugin.""" |
99
|
|
|
super(PluginModel, self).__init__( |
100
|
|
|
args=args, |
101
|
|
|
config=config, |
102
|
|
|
items_history_list=items_history_list, |
103
|
|
|
stats_init_value=[], |
104
|
|
|
fields_description=fields_description, |
105
|
|
|
) |
106
|
|
|
|
107
|
|
|
# We want to display the stat in the curse interface |
108
|
|
|
self.display_curse = True |
109
|
|
|
|
110
|
|
|
# Manage the maximum number of CPU to display (related to enhancement request #2734) |
111
|
|
|
if config: |
112
|
|
|
self.max_cpu_display = config.get_int_value('percpu', 'max_cpu_display', 4) |
113
|
|
|
else: |
114
|
|
|
self.max_cpu_display = 4 |
115
|
|
|
|
116
|
|
|
def get_key(self): |
117
|
|
|
"""Return the key of the list.""" |
118
|
|
|
return 'cpu_number' |
119
|
|
|
|
120
|
|
|
@GlancesPluginModel._check_decorator |
121
|
|
|
@GlancesPluginModel._log_result_decorator |
122
|
|
|
def update(self): |
123
|
|
|
"""Update per-CPU stats using the input method.""" |
124
|
|
|
# Init new stats |
125
|
|
|
stats = self.get_init_value() |
126
|
|
|
|
127
|
|
|
# Grab per-CPU stats using psutil's cpu_percent(percpu=True) and |
128
|
|
|
# cpu_times_percent(percpu=True) methods |
129
|
|
|
if self.input_method == 'local': |
130
|
|
|
stats = cpu_percent.get(percpu=True) |
131
|
|
|
else: |
132
|
|
|
# Update stats using SNMP |
133
|
|
|
pass |
134
|
|
|
|
135
|
|
|
# Update the stats |
136
|
|
|
self.stats = stats |
137
|
|
|
|
138
|
|
|
return self.stats |
139
|
|
|
|
140
|
|
|
def msg_curse(self, args=None, max_width=None): |
141
|
|
|
"""Return the dict to display in the curse interface.""" |
142
|
|
|
# Init the return message |
143
|
|
|
ret = [] |
144
|
|
|
|
145
|
|
|
# Only process if stats exist... |
146
|
|
|
if not self.stats or not self.args.percpu or self.is_disabled(): |
147
|
|
|
return ret |
148
|
|
|
|
149
|
|
|
# Define the default header |
150
|
|
|
header = ['user', 'system', 'idle', 'iowait', 'steal'] |
151
|
|
|
|
152
|
|
|
# Build the string message |
153
|
|
|
if self.is_disabled('quicklook'): |
154
|
|
|
msg = '{:5}'.format('CPU') |
155
|
|
|
ret.append(self.curse_add_line(msg, "TITLE")) |
156
|
|
|
header.insert(0, 'total') |
157
|
|
|
|
158
|
|
|
# Per CPU stats displayed per line |
159
|
|
|
for stat in header: |
160
|
|
|
if stat not in self.stats[0]: |
161
|
|
|
continue |
162
|
|
|
msg = '{:>7}'.format(stat) |
163
|
|
|
ret.append(self.curse_add_line(msg)) |
164
|
|
|
|
165
|
|
|
# Manage the maximum number of CPU to display (related to enhancement request #2734) |
166
|
|
|
if len(self.stats) > self.max_cpu_display: |
167
|
|
|
# If the number of CPU is > max_cpu_display then sort and display top 'n' |
168
|
|
|
percpu_list = sorted(self.stats, key=lambda x: x['total'], reverse=True) |
169
|
|
|
else: |
170
|
|
|
percpu_list = self.stats |
171
|
|
|
|
172
|
|
|
# Per CPU stats displayed per column |
173
|
|
|
for cpu in percpu_list[0 : self.max_cpu_display]: |
174
|
|
|
ret.append(self.curse_new_line()) |
175
|
|
|
if self.is_disabled('quicklook'): |
176
|
|
|
try: |
177
|
|
|
cpu_id = cpu[cpu['key']] |
178
|
|
|
if cpu_id < 10: |
179
|
|
|
msg = 'CPU{:1} '.format(cpu_id) |
180
|
|
|
else: |
181
|
|
|
msg = '{:4} '.format(cpu_id) |
182
|
|
|
except TypeError: |
183
|
|
|
# TypeError: string indices must be integers (issue #1027) |
184
|
|
|
msg = '{:4} '.format('?') |
185
|
|
|
ret.append(self.curse_add_line(msg)) |
186
|
|
|
for stat in header: |
187
|
|
|
if stat not in self.stats[0]: |
188
|
|
|
continue |
189
|
|
|
try: |
190
|
|
|
msg = '{:6.1f}%'.format(cpu[stat]) |
191
|
|
|
except TypeError: |
192
|
|
|
msg = '{:>6}%'.format('?') |
193
|
|
|
ret.append(self.curse_add_line(msg, self.get_alert(cpu[stat], header=stat))) |
194
|
|
|
|
195
|
|
|
# Add a new line with sum of all others CPU |
196
|
|
|
if len(self.stats) > self.max_cpu_display: |
197
|
|
|
ret.append(self.curse_new_line()) |
198
|
|
|
if self.is_disabled('quicklook'): |
199
|
|
|
ret.append(self.curse_add_line('CPU* ')) |
200
|
|
|
for stat in header: |
201
|
|
|
if stat not in self.stats[0]: |
202
|
|
|
continue |
203
|
|
|
cpu_stat = sum([i[stat] for i in percpu_list[0 : self.max_cpu_display]]) / len( |
204
|
|
|
[i[stat] for i in percpu_list[0 : self.max_cpu_display]] |
205
|
|
|
) |
206
|
|
|
try: |
207
|
|
|
msg = '{:6.1f}%'.format(cpu_stat) |
208
|
|
|
except TypeError: |
209
|
|
|
msg = '{:>6}%'.format('?') |
210
|
|
|
ret.append(self.curse_add_line(msg, self.get_alert(cpu_stat, header=stat))) |
211
|
|
|
|
212
|
|
|
return ret |
213
|
|
|
|