Compare commits

...

5 Commits

Author SHA1 Message Date
Nicolas Hennion
bbbdcd3f8e
Merge 9219b458fb into 6af2340c5c 2024-07-02 02:54:02 +02:00
Bharath Vignesh J K
6af2340c5c chg: plugin(percpu) - show times based on host OS 2024-07-01 10:27:33 +05:30
RazCrimson
46abd4b4c9
Merge pull request #2860 from nicolargo/2859-glances-411-on-windows-attributeerror-cpupercent-object-has-no-attribute-cpu_percent
fix: cpu_percent - initialization timer bugs
2024-07-01 09:33:57 +05:30
Bharath Vignesh J K
2801add7e2 fix: cpu_percent - initialization timer bugs
fixes #2859
2024-07-01 09:33:05 +05:30
snyk-bot
9219b458fb
fix: dev-requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-FONTTOOLS-6133203
- https://snyk.io/vuln/SNYK-PYTHON-REQUESTS-6928867
2024-06-17 11:41:21 +00:00
3 changed files with 99 additions and 60 deletions

View File

@ -12,3 +12,4 @@ requirements-parser
ruff ruff
semgrep semgrep
setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
requests>=2.32.2 # not directly required, pinned by Snyk to avoid a vulnerability

View File

@ -8,16 +8,44 @@
"""CPU percent stats shared between CPU and Quicklook plugins.""" """CPU percent stats shared between CPU and Quicklook plugins."""
from typing import List, Optional, TypedDict
import psutil import psutil
from glances.logger import logger from glances.logger import logger
from glances.timer import Timer from glances.timer import Timer
__all__ = ["cpu_percent"]
class CpuInfo(TypedDict):
cpu_name: str
cpu_hz: Optional[float]
cpu_hz_current: Optional[float]
class PerCpuPercentInfo(TypedDict):
key: str
cpu_number: int
total: float
user: float
system: float
idle: float
nice: Optional[float]
iowait: Optional[float]
irq: Optional[float]
softirq: Optional[float]
steal: Optional[float]
guest: Optional[float]
guest_nice: Optional[float]
dpc: Optional[float]
interrupt: Optional[float]
class CpuPercent: class CpuPercent:
"""Get and store the CPU percent.""" """Get and store the CPU percent."""
def __init__(self, cached_timer_cpu=2): def __init__(self, cached_timer_cpu: int = 2):
# cached_timer_cpu is the minimum time interval between stats updates # cached_timer_cpu is the minimum time interval between stats updates
# since last update is passed (will retrieve old cached info instead) # since last update is passed (will retrieve old cached info instead)
self.cached_timer_cpu = cached_timer_cpu self.cached_timer_cpu = cached_timer_cpu
@ -27,21 +55,21 @@ class CpuPercent:
# Get CPU name # Get CPU name
self.timer_cpu_info = Timer(0) self.timer_cpu_info = Timer(0)
self.cpu_info = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None} self.cpu_info: CpuInfo = {'cpu_name': self.__get_cpu_name(), 'cpu_hz_current': None, 'cpu_hz': None}
# Warning from PsUtil documentation # Warning from PsUtil documentation
# The first time this function is called with interval = 0.0 or None # The first time this function is called with interval = 0.0 or None
# it will return a meaningless 0.0 value which you are supposed to ignore. # it will return a meaningless 0.0 value which you are supposed to ignore.
self.timer_cpu = Timer(0) self.timer_cpu = Timer(0)
self.cpu_percent = self.get_cpu() self.cpu_percent = self._compute_cpu()
self.timer_percpu = Timer(0) self.timer_percpu = Timer(0)
self.percpu_percent = self.get_percpu() self.percpu_percent = self._compute_percpu()
def get_key(self): def get_key(self):
"""Return the key of the per CPU list.""" """Return the key of the per CPU list."""
return 'cpu_number' return 'cpu_number'
def get_info(self): def get_info(self) -> CpuInfo:
"""Get additional information about the CPU""" """Get additional information about the CPU"""
# Never update more than 1 time per cached_timer_cpu_info # Never update more than 1 time per cached_timer_cpu_info
if self.timer_cpu_info.finished() and hasattr(psutil, 'cpu_freq'): if self.timer_cpu_info.finished() and hasattr(psutil, 'cpu_freq'):
@ -63,70 +91,69 @@ class CpuPercent:
self.timer_cpu_info.reset(duration=self.cached_timer_cpu_info) self.timer_cpu_info.reset(duration=self.cached_timer_cpu_info)
return self.cpu_info return self.cpu_info
def __get_cpu_name(self): @staticmethod
def __get_cpu_name() -> str:
# Get the CPU name once from the /proc/cpuinfo file # Get the CPU name once from the /proc/cpuinfo file
# Read the first line with the "model name" ("Model" for Raspberry Pi) # Read the first line with the "model name" ("Model" for Raspberry Pi)
ret = None
try: try:
cpuinfo_file = open('/proc/cpuinfo').readlines() cpuinfo_lines = open('/proc/cpuinfo').readlines()
except (FileNotFoundError, PermissionError): except (FileNotFoundError, PermissionError):
pass logger.debug("No permission to read '/proc/cpuinfo'")
else: return 'CPU'
for line in cpuinfo_file:
if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'):
ret = line.split(':')[1].strip()
break
return ret if ret else 'CPU'
def get_cpu(self): for line in cpuinfo_lines:
if line.startswith('model name') or line.startswith('Model') or line.startswith('cpu model'):
return line.split(':')[1].strip()
return 'CPU'
def get_cpu(self) -> float:
"""Update and/or return the CPU using the psutil library.""" """Update and/or return the CPU using the psutil library."""
# Never update more than 1 time per cached_timer_cpu # Never update more than 1 time per cached_timer_cpu
if self.timer_cpu.finished(): if self.timer_cpu.finished():
# Reset timer for cache # Reset timer for cache
self.timer_cpu.reset(duration=self.cached_timer_cpu) self.timer_cpu.reset(duration=self.cached_timer_cpu)
# Update the stats # Update the stats
self.cpu_percent = psutil.cpu_percent(interval=0.0) self.cpu_percent = self._compute_cpu()
return self.cpu_percent return self.cpu_percent
def get_percpu(self): @staticmethod
def _compute_cpu() -> float:
return psutil.cpu_percent(interval=0.0)
def get_percpu(self) -> List[PerCpuPercentInfo]:
"""Update and/or return the per CPU list using the psutil library.""" """Update and/or return the per CPU list using the psutil library."""
# Never update more than 1 time per cached_timer_cpu # Never update more than 1 time per cached_timer_cpu
if self.timer_percpu.finished(): if self.timer_percpu.finished():
# Reset timer for cache # Reset timer for cache
self.timer_percpu.reset(duration=self.cached_timer_cpu) self.timer_percpu.reset(duration=self.cached_timer_cpu)
# Get stats
percpu_percent = []
psutil_percpu = enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True))
for cpu_number, cputimes in psutil_percpu:
cpu = {
'key': self.get_key(),
'cpu_number': cpu_number,
'total': round(100 - cputimes.idle, 1),
'user': cputimes.user,
'system': cputimes.system,
'idle': cputimes.idle,
}
# The following stats are for API purposes only
if hasattr(cputimes, 'nice'):
cpu['nice'] = cputimes.nice
if hasattr(cputimes, 'iowait'):
cpu['iowait'] = cputimes.iowait
if hasattr(cputimes, 'irq'):
cpu['irq'] = cputimes.irq
if hasattr(cputimes, 'softirq'):
cpu['softirq'] = cputimes.softirq
if hasattr(cputimes, 'steal'):
cpu['steal'] = cputimes.steal
if hasattr(cputimes, 'guest'):
cpu['guest'] = cputimes.guest
if hasattr(cputimes, 'guest_nice'):
cpu['guest_nice'] = cputimes.guest_nice
# Append new CPU to the list
percpu_percent.append(cpu)
# Update stats # Update stats
self.percpu_percent = percpu_percent self.percpu_percent = self._compute_percpu()
return self.percpu_percent return self.percpu_percent
def _compute_percpu(self) -> List[PerCpuPercentInfo]:
psutil_percpu = enumerate(psutil.cpu_times_percent(interval=0.0, percpu=True))
return [
{
'key': self.get_key(),
'cpu_number': cpu_number,
'total': round(100 - cpu_times.idle, 1),
'user': cpu_times.user,
'system': cpu_times.system,
'idle': cpu_times.idle,
'nice': cpu_times.nice if hasattr(cpu_times, 'nice') else None,
'iowait': cpu_times.iowait if hasattr(cpu_times, 'iowait') else None,
'irq': cpu_times.irq if hasattr(cpu_times, 'irq') else None,
'softirq': cpu_times.softirq if hasattr(cpu_times, 'softirq') else None,
'steal': cpu_times.steal if hasattr(cpu_times, 'steal') else None,
'guest': cpu_times.guest if hasattr(cpu_times, 'guest') else None,
'guest_nice': cpu_times.steal if hasattr(cpu_times, 'guest_nice') else None,
'dpc': cpu_times.dpc if hasattr(cpu_times, 'dpc') else None,
'interrupt': cpu_times.interrupt if hasattr(cpu_times, 'interrupt') else None,
}
for cpu_number, cpu_times in psutil_percpu
]
# CpuPercent instance shared between plugins # CpuPercent instance shared between plugins
cpu_percent = CpuPercent() cpu_percent = CpuPercent()

View File

@ -9,6 +9,7 @@
"""Per-CPU plugin.""" """Per-CPU plugin."""
from glances.cpu_percent import cpu_percent from glances.cpu_percent import cpu_percent
from glances.globals import BSD, LINUX, MACOS, WINDOWS
from glances.plugins.plugin.model import GlancesPluginModel from glances.plugins.plugin.model import GlancesPluginModel
# Fields description # Fields description
@ -76,9 +77,16 @@ guest operating systems under the control of the Linux kernel.',
'description': '*(Linux)*: percent of time spent handling software interrupts.', 'description': '*(Linux)*: percent of time spent handling software interrupts.',
'unit': 'percent', 'unit': 'percent',
}, },
'dpc': {
'description': '*(Windows)*: percent of time spent handling deferred procedure calls.',
'unit': 'percent',
},
'interrupt': {
'description': '*(Windows)*: percent of time spent handling software interrupts.',
'unit': 'percent',
},
} }
# Define the history items list # Define the history items list
items_history_list = [ items_history_list = [
{'name': 'user', 'description': 'User CPU usage', 'y_unit': '%'}, {'name': 'user', 'description': 'User CPU usage', 'y_unit': '%'},
@ -141,8 +149,17 @@ class PluginModel(GlancesPluginModel):
if not self.stats or not self.args.percpu or self.is_disabled(): if not self.stats or not self.args.percpu or self.is_disabled():
return ret return ret
# Define the default header # Define the headers based on OS
header = ['user', 'system', 'idle', 'iowait', 'steal'] header = ['user', 'system']
if LINUX:
header.extend(['iowait', 'idle', 'irq', 'nice', 'steal', 'guest'])
elif MACOS:
header.extend(['idle', 'nice'])
elif BSD:
header.extend(['idle', 'irq', 'nice'])
elif WINDOWS:
header.extend(['dpc', 'interrupt'])
# Build the string message # Build the string message
if self.is_disabled('quicklook'): if self.is_disabled('quicklook'):
@ -152,8 +169,6 @@ class PluginModel(GlancesPluginModel):
# Per CPU stats displayed per line # Per CPU stats displayed per line
for stat in header: for stat in header:
if stat not in self.stats[0]:
continue
msg = f'{stat:>7}' msg = f'{stat:>7}'
ret.append(self.curse_add_line(msg)) ret.append(self.curse_add_line(msg))
@ -179,8 +194,6 @@ class PluginModel(GlancesPluginModel):
msg = '{:4} '.format('?') msg = '{:4} '.format('?')
ret.append(self.curse_add_line(msg)) ret.append(self.curse_add_line(msg))
for stat in header: for stat in header:
if stat not in self.stats[0]:
continue
try: try:
msg = f'{cpu[stat]:6.1f}%' msg = f'{cpu[stat]:6.1f}%'
except TypeError: except TypeError:
@ -192,12 +205,10 @@ class PluginModel(GlancesPluginModel):
ret.append(self.curse_new_line()) ret.append(self.curse_new_line())
if self.is_disabled('quicklook'): if self.is_disabled('quicklook'):
ret.append(self.curse_add_line('CPU* ')) ret.append(self.curse_add_line('CPU* '))
for stat in header: for stat in header:
if stat not in self.stats[0]: percpu_stats = [i[stat] for i in percpu_list[0 : self.max_cpu_display]]
continue cpu_stat = sum(percpu_stats) / len(percpu_stats)
cpu_stat = sum([i[stat] for i in percpu_list[0 : self.max_cpu_display]]) / len(
[i[stat] for i in percpu_list[0 : self.max_cpu_display]]
)
try: try:
msg = f'{cpu_stat:6.1f}%' msg = f'{cpu_stat:6.1f}%'
except TypeError: except TypeError: