Skip to content

Commit 46abd4b

Browse files
authored
Merge pull request nicolargo#2860 from nicolargo/2859-glances-411-on-windows-attributeerror-cpupercent-object-has-no-attribute-cpu_percent
fix: cpu_percent - initialization timer bugs
2 parents 9b853d8 + 2801add commit 46abd4b

File tree

2 files changed

+79
-60
lines changed

2 files changed

+79
-60
lines changed

glances/cpu_percent.py

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,42 @@
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+
1642

1743
class CpuPercent:
1844
"""Get and store the CPU percent."""
1945

20-
def __init__(self, cached_timer_cpu=2):
46+
def __init__(self, cached_timer_cpu: int = 2):
2147
# cached_timer_cpu is the minimum time interval between stats updates
2248
# since last update is passed (will retrieve old cached info instead)
2349
self.cached_timer_cpu = cached_timer_cpu
@@ -27,21 +53,21 @@ def __init__(self, cached_timer_cpu=2):
2753

2854
# Get CPU name
2955
self.timer_cpu_info = Timer(0)
30-
self.cpu_info = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None}
56+
self.cpu_info: CpuInfo = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None}
3157

3258
# Warning from PsUtil documentation
3359
# The first time this function is called with interval = 0.0 or None
3460
# it will return a meaningless 0.0 value which you are supposed to ignore.
3561
self.timer_cpu = Timer(0)
36-
self.cpu_percent = self.get_cpu()
62+
self.cpu_percent = self._compute_cpu()
3763
self.timer_percpu = Timer(0)
38-
self.percpu_percent = self.get_percpu()
64+
self.percpu_percent = self._compute_percpu()
3965

4066
def get_key(self):
4167
"""Return the key of the per CPU list."""
4268
return 'cpu_number'
4369

44-
def get_info(self):
70+
def get_info(self) -> CpuInfo:
4571
"""Get additional information about the CPU"""
4672
# Never update more than 1 time per cached_timer_cpu_info
4773
if self.timer_cpu_info.finished() and hasattr(psutil, 'cpu_freq'):
@@ -63,70 +89,67 @@ def get_info(self):
6389
self.timer_cpu_info.reset(duration=self.cached_timer_cpu_info)
6490
return self.cpu_info
6591

66-
def __get_cpu_name(self):
92+
@staticmethod
93+
def __get_cpu_name() -> str:
6794
# Get the CPU name once from the /proc/cpuinfo file
6895
# Read the first line with the "model name" ("Model" for Raspberry Pi)
69-
ret = None
7096
try:
71-
cpuinfo_file = open('/proc/cpuinfo').readlines()
97+
cpuinfo_lines = open('/proc/cpuinfo').readlines()
7298
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):
99+
logger.debug("No permission to read '/proc/cpuinfo'")
100+
return 'CPU'
101+
102+
for line in cpuinfo_lines:
103+
if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'):
104+
return line.split(':')[1].strip()
105+
106+
return 'CPU'
107+
108+
def get_cpu(self) -> float:
82109
"""Update and/or return the CPU using the psutil library."""
83110
# Never update more than 1 time per cached_timer_cpu
84111
if self.timer_cpu.finished():
85112
# Reset timer for cache
86113
self.timer_cpu.reset(duration=self.cached_timer_cpu)
87114
# Update the stats
88-
self.cpu_percent = psutil.cpu_percent(interval=0.0)
115+
self.cpu_percent = self._compute_cpu()
89116
return self.cpu_percent
90117

91-
def get_percpu(self):
118+
@staticmethod
119+
def _compute_cpu() -> float:
120+
return psutil.cpu_percent(interval=0.0)
121+
122+
def get_percpu(self) -> List[PerCpuPercentInfo]:
92123
"""Update and/or return the per CPU list using the psutil library."""
93124
# Never update more than 1 time per cached_timer_cpu
94125
if self.timer_percpu.finished():
95126
# Reset timer for cache
96127
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)
126128
# Update stats
127-
self.percpu_percent = percpu_percent
129+
self.percpu_percent = self._compute_percpu()
128130
return self.percpu_percent
129131

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

131154
# CpuPercent instance shared between plugins
132155
cpu_percent = CpuPercent()

glances/plugins/percpu/__init__.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
},
7979
}
8080

81-
8281
# Define the history items list
8382
items_history_list = [
8483
{'name': 'user', 'description': 'User CPU usage', 'y_unit': '%'},
@@ -142,7 +141,10 @@ def msg_curse(self, args=None, max_width=None):
142141
return ret
143142

144143
# Define the default header
145-
header = ['user', 'system', 'idle', 'iowait', 'steal']
144+
all_headers = ['user', 'system', 'idle', 'iowait', 'steal']
145+
146+
# Determine applicable headers
147+
header = [h for h in all_headers if self.stats[0].get(h) is not None]
146148

147149
# Build the string message
148150
if self.is_disabled('quicklook'):
@@ -152,8 +154,6 @@ def msg_curse(self, args=None, max_width=None):
152154

153155
# Per CPU stats displayed per line
154156
for stat in header:
155-
if stat not in self.stats[0]:
156-
continue
157157
msg = f'{stat:>7}'
158158
ret.append(self.curse_add_line(msg))
159159

@@ -179,8 +179,6 @@ def msg_curse(self, args=None, max_width=None):
179179
msg = '{:4} '.format('?')
180180
ret.append(self.curse_add_line(msg))
181181
for stat in header:
182-
if stat not in self.stats[0]:
183-
continue
184182
try:
185183
msg = f'{cpu[stat]:6.1f}%'
186184
except TypeError:
@@ -192,12 +190,10 @@ def msg_curse(self, args=None, max_width=None):
192190
ret.append(self.curse_new_line())
193191
if self.is_disabled('quicklook'):
194192
ret.append(self.curse_add_line('CPU* '))
193+
195194
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-
)
195+
percpu_stats = [i[stat] for i in percpu_list[0 : self.max_cpu_display]]
196+
cpu_stat = sum(percpu_stats) / len(percpu_stats)
201197
try:
202198
msg = f'{cpu_stat:6.1f}%'
203199
except TypeError:

0 commit comments

Comments
 (0)