Skip to content

Commit cc9054d

Browse files
committed
Merge branch 'develop'
2 parents f8aa98b + ca41d09 commit cc9054d

File tree

2 files changed

+99
-61
lines changed

2 files changed

+99
-61
lines changed

glances/cpu_percent.py

+76-49
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,44 @@
88

99
"""CPU percent stats shared between CPU and Quicklook plugins."""
1010

11+
from typing import List, Optional, TypedDict
12+
1113
import psutil
1214

1315
from glances.logger import logger
1416
from glances.timer import Timer
1517

18+
__all__ = ["cpu_percent"]
19+
20+
21+
class CpuInfo(TypedDict):
22+
cpu_name: str
23+
cpu_hz: Optional[float]
24+
cpu_hz_current: Optional[float]
25+
26+
27+
class PerCpuPercentInfo(TypedDict):
28+
key: str
29+
cpu_number: int
30+
total: float
31+
user: float
32+
system: float
33+
idle: float
34+
nice: Optional[float]
35+
iowait: Optional[float]
36+
irq: Optional[float]
37+
softirq: Optional[float]
38+
steal: Optional[float]
39+
guest: Optional[float]
40+
guest_nice: Optional[float]
41+
dpc: Optional[float]
42+
interrupt: Optional[float]
43+
1644

1745
class CpuPercent:
1846
"""Get and store the CPU percent."""
1947

20-
def __init__(self, cached_timer_cpu=2):
48+
def __init__(self, cached_timer_cpu: int = 2):
2149
# cached_timer_cpu is the minimum time interval between stats updates
2250
# since last update is passed (will retrieve old cached info instead)
2351
self.cached_timer_cpu = cached_timer_cpu
@@ -27,21 +55,21 @@ def __init__(self, cached_timer_cpu=2):
2755

2856
# Get CPU name
2957
self.timer_cpu_info = Timer(0)
30-
self.cpu_info = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None}
58+
self.cpu_info: CpuInfo = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None}
3159

3260
# Warning from PsUtil documentation
3361
# The first time this function is called with interval = 0.0 or None
3462
# it will return a meaningless 0.0 value which you are supposed to ignore.
3563
self.timer_cpu = Timer(0)
36-
self.cpu_percent = self.get_cpu()
64+
self.cpu_percent = self._compute_cpu()
3765
self.timer_percpu = Timer(0)
38-
self.percpu_percent = self.get_percpu()
66+
self.percpu_percent = self._compute_percpu()
3967

4068
def get_key(self):
4169
"""Return the key of the per CPU list."""
4270
return 'cpu_number'
4371

44-
def get_info(self):
72+
def get_info(self) -> CpuInfo:
4573
"""Get additional information about the CPU"""
4674
# Never update more than 1 time per cached_timer_cpu_info
4775
if self.timer_cpu_info.finished() and hasattr(psutil, 'cpu_freq'):
@@ -63,70 +91,69 @@ def get_info(self):
6391
self.timer_cpu_info.reset(duration=self.cached_timer_cpu_info)
6492
return self.cpu_info
6593

66-
def __get_cpu_name(self):
94+
@staticmethod
95+
def __get_cpu_name() -> str:
6796
# Get the CPU name once from the /proc/cpuinfo file
6897
# Read the first line with the "model name" ("Model" for Raspberry Pi)
69-
ret = None
7098
try:
71-
cpuinfo_file = open('/proc/cpuinfo').readlines()
99+
cpuinfo_lines = open('/proc/cpuinfo').readlines()
72100
except (FileNotFoundError, PermissionError):
73-
pass
74-
else:
75-
for line in cpuinfo_file:
76-
if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'):
77-
ret = line.split(':')[1].strip()
78-
break
79-
return ret if ret else 'CPU'
80-
81-
def get_cpu(self):
101+
logger.debug("No permission to read '/proc/cpuinfo'")
102+
return 'CPU'
103+
104+
for line in cpuinfo_lines:
105+
if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'):
106+
return line.split(':')[1].strip()
107+
108+
return 'CPU'
109+
110+
def get_cpu(self) -> float:
82111
"""Update and/or return the CPU using the psutil library."""
83112
# Never update more than 1 time per cached_timer_cpu
84113
if self.timer_cpu.finished():
85114
# Reset timer for cache
86115
self.timer_cpu.reset(duration=self.cached_timer_cpu)
87116
# Update the stats
88-
self.cpu_percent = psutil.cpu_percent(interval=0.0)
117+
self.cpu_percent = self._compute_cpu()
89118
return self.cpu_percent
90119

91-
def get_percpu(self):
120+
@staticmethod
121+
def _compute_cpu() -> float:
122+
return psutil.cpu_percent(interval=0.0)
123+
124+
def get_percpu(self) -> List[PerCpuPercentInfo]:
92125
"""Update and/or return the per CPU list using the psutil library."""
93126
# Never update more than 1 time per cached_timer_cpu
94127
if self.timer_percpu.finished():
95128
# Reset timer for cache
96129
self.timer_percpu.reset(duration=self.cached_timer_cpu)
97-
# Get stats
98-
percpu_percent = []
99-
psutil_percpu = enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True))
100-
for cpu_number, cputimes in psutil_percpu:
101-
cpu = {
102-
'key': self.get_key(),
103-
'cpu_number': cpu_number,
104-
'total': round(100 - cputimes.idle, 1),
105-
'user': cputimes.user,
106-
'system': cputimes.system,
107-
'idle': cputimes.idle,
108-
}
109-
# The following stats are for API purposes only
110-
if hasattr(cputimes, 'nice'):
111-
cpu['nice'] = cputimes.nice
112-
if hasattr(cputimes, 'iowait'):
113-
cpu['iowait'] = cputimes.iowait
114-
if hasattr(cputimes, 'irq'):
115-
cpu['irq'] = cputimes.irq
116-
if hasattr(cputimes, 'softirq'):
117-
cpu['softirq'] = cputimes.softirq
118-
if hasattr(cputimes, 'steal'):
119-
cpu['steal'] = cputimes.steal
120-
if hasattr(cputimes, 'guest'):
121-
cpu['guest'] = cputimes.guest
122-
if hasattr(cputimes, 'guest_nice'):
123-
cpu['guest_nice'] = cputimes.guest_nice
124-
# Append new CPU to the list
125-
percpu_percent.append(cpu)
126130
# Update stats
127-
self.percpu_percent = percpu_percent
131+
self.percpu_percent = self._compute_percpu()
128132
return self.percpu_percent
129133

134+
def _compute_percpu(self) -> List[PerCpuPercentInfo]:
135+
psutil_percpu = enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True))
136+
return [
137+
{
138+
'key': self.get_key(),
139+
'cpu_number': cpu_number,
140+
'total': round(100 - cpu_times.idle, 1),
141+
'user': cpu_times.user,
142+
'system': cpu_times.system,
143+
'idle': cpu_times.idle,
144+
'nice': cpu_times.nice if hasattr(cpu_times, 'nice') else None,
145+
'iowait': cpu_times.iowait if hasattr(cpu_times, 'iowait') else None,
146+
'irq': cpu_times.irq if hasattr(cpu_times, 'irq') else None,
147+
'softirq': cpu_times.softirq if hasattr(cpu_times, 'softirq') else None,
148+
'steal': cpu_times.steal if hasattr(cpu_times, 'steal') else None,
149+
'guest': cpu_times.guest if hasattr(cpu_times, 'guest') else None,
150+
'guest_nice': cpu_times.steal if hasattr(cpu_times, 'guest_nice') else None,
151+
'dpc': cpu_times.dpc if hasattr(cpu_times, 'dpc') else None,
152+
'interrupt': cpu_times.interrupt if hasattr(cpu_times, 'interrupt') else None,
153+
}
154+
for cpu_number, cpu_times in psutil_percpu
155+
]
156+
130157

131158
# CpuPercent instance shared between plugins
132159
cpu_percent = CpuPercent()

glances/plugins/percpu/__init__.py

+23-12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"""Per-CPU plugin."""
1010

1111
from glances.cpu_percent import cpu_percent
12+
from glances.globals import BSD, LINUX, MACOS, WINDOWS
1213
from glances.plugins.plugin.model import GlancesPluginModel
1314

1415
# Fields description
@@ -76,9 +77,16 @@
7677
'description': '*(Linux)*: percent of time spent handling software interrupts.',
7778
'unit': 'percent',
7879
},
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+
},
7988
}
8089

81-
8290
# Define the history items list
8391
items_history_list = [
8492
{'name': 'user', 'description': 'User CPU usage', 'y_unit': '%'},
@@ -141,8 +149,17 @@ def msg_curse(self, args=None, max_width=None):
141149
if not self.stats or not self.args.percpu or self.is_disabled():
142150
return ret
143151

144-
# Define the default header
145-
header = ['user', 'system', 'idle', 'iowait', 'steal']
152+
# Define the headers based on OS
153+
header = ['user', 'system']
154+
155+
if LINUX:
156+
header.extend(['iowait', 'idle', 'irq', 'nice', 'steal', 'guest'])
157+
elif MACOS:
158+
header.extend(['idle', 'nice'])
159+
elif BSD:
160+
header.extend(['idle', 'irq', 'nice'])
161+
elif WINDOWS:
162+
header.extend(['dpc', 'interrupt'])
146163

147164
# Build the string message
148165
if self.is_disabled('quicklook'):
@@ -152,8 +169,6 @@ def msg_curse(self, args=None, max_width=None):
152169

153170
# Per CPU stats displayed per line
154171
for stat in header:
155-
if stat not in self.stats[0]:
156-
continue
157172
msg = f'{stat:>7}'
158173
ret.append(self.curse_add_line(msg))
159174

@@ -179,8 +194,6 @@ def msg_curse(self, args=None, max_width=None):
179194
msg = '{:4} '.format('?')
180195
ret.append(self.curse_add_line(msg))
181196
for stat in header:
182-
if stat not in self.stats[0]:
183-
continue
184197
try:
185198
msg = f'{cpu[stat]:6.1f}%'
186199
except TypeError:
@@ -192,12 +205,10 @@ def msg_curse(self, args=None, max_width=None):
192205
ret.append(self.curse_new_line())
193206
if self.is_disabled('quicklook'):
194207
ret.append(self.curse_add_line('CPU* '))
208+
195209
for stat in header:
196-
if stat not in self.stats[0]:
197-
continue
198-
cpu_stat = sum([i[stat] for i in percpu_list[0 : self.max_cpu_display]]) / len(
199-
[i[stat] for i in percpu_list[0 : self.max_cpu_display]]
200-
)
210+
percpu_stats = [i[stat] for i in percpu_list[0 : self.max_cpu_display]]
211+
cpu_stat = sum(percpu_stats) / len(percpu_stats)
201212
try:
202213
msg = f'{cpu_stat:6.1f}%'
203214
except TypeError:

0 commit comments

Comments
 (0)