Enhance CLI System Monitoring with a TUI (#810)

* create a cli for better visual monitor, live and stats

- created a system info module
- created system monitor module
- fixed avg_all_core_temp not defined

* fixed snap package error and added missing implementations
This commit is contained in:
LAITH343 2025-03-01 08:43:55 +03:00 committed by GitHub
parent e6bbd2c833
commit 081dbda79c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 715 additions and 55 deletions

3
.gitignore vendored
View File

@ -141,3 +141,6 @@ dmypy.json
# nix build
/result
# vs code
.vscode/

View File

@ -13,7 +13,9 @@ from auto_cpufreq.battery_scripts.battery import *
from auto_cpufreq.config.config import config as conf, find_config_file
from auto_cpufreq.core import *
from auto_cpufreq.globals import GITHUB, IS_INSTALLED_WITH_AUR, IS_INSTALLED_WITH_SNAP
from auto_cpufreq.modules.system_monitor import ViewType, SystemMonitor
from auto_cpufreq.power_helper import *
from threading import Thread
@click.command()
@click.option("--monitor", is_flag=True, help="Monitor and see suggestions for CPU optimizations")
@ -55,11 +57,8 @@ def main(monitor, live, daemon, install, update, remove, force, config, stats, g
set_override(force) # Calling set override, only if force has some values
if monitor:
config_info_dialog()
root_check()
print('\nNote: You can quit monitor mode by pressing "ctrl+c"')
battery_setup()
battery_get_thresholds()
conf.notifier.start()
if IS_INSTALLED_WITH_SNAP:
gnome_power_detect_snap()
@ -67,26 +66,19 @@ def main(monitor, live, daemon, install, update, remove, force, config, stats, g
else:
gnome_power_detect()
tlp_service_detect()
while True:
if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):
try:
time.sleep(1)
running_daemon_check()
footer()
gov_check()
cpufreqctl()
distro_info()
sysinfo()
mon_autofreq()
countdown(2)
except KeyboardInterrupt: break
conf.notifier.stop()
input("press Enter to continue or Ctrl + c to exit...")
except KeyboardInterrupt:
conf.notifier.stop()
sys.exit(0)
monitor = SystemMonitor(suggestion=True, type=ViewType.MONITOR)
monitor.run(on_quit=conf.notifier.stop)
elif live:
root_check()
config_info_dialog()
print('\nNote: You can quit live mode by pressing "ctrl+c"')
time.sleep(1)
battery_setup()
battery_get_thresholds()
conf.notifier.start()
if IS_INSTALLED_WITH_SNAP:
gnome_power_detect_snap()
@ -96,22 +88,40 @@ def main(monitor, live, daemon, install, update, remove, force, config, stats, g
gnome_power_stop_live()
tuned_stop_live()
tlp_service_detect()
while True:
if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):
try:
running_daemon_check()
footer()
gov_check()
cpufreqctl()
distro_info()
sysinfo()
set_autofreq()
countdown(2)
input("press Enter to continue or Ctrl + c to exit...")
except KeyboardInterrupt:
gnome_power_start_live()
tuned_start_live()
print()
break
conf.notifier.stop()
conf.notifier.stop()
sys.exit(0)
cpufreqctl()
def live_daemon():
# Redirect stdout to suppress prints
class NullWriter:
def write(self, _): pass
def flush(self): pass
try:
sys.stdout = NullWriter()
while True:
time.sleep(1)
set_autofreq()
except:
pass
def live_daemon_off():
gnome_power_start_live()
tuned_start_live()
cpufreqctl_restore()
conf.notifier.stop()
thread = Thread(target=live_daemon, daemon=True)
thread.start()
monitor = SystemMonitor(type=ViewType.LIVE)
monitor.run(on_quit=live_daemon_off)
elif daemon:
config_info_dialog()
root_check()
@ -205,15 +215,22 @@ def main(monitor, live, daemon, install, update, remove, force, config, stats, g
elif stats:
not_running_daemon_check()
config_info_dialog()
print('\nNote: You can quit stats mode by pressing "ctrl+c"')
if IS_INSTALLED_WITH_SNAP:
gnome_power_detect_snap()
tlp_service_detect_snap()
else:
gnome_power_detect()
tlp_service_detect()
battery_get_thresholds()
read_stats()
if IS_INSTALLED_WITH_SNAP or tlp_stat_exists or (systemctl_exists and not bool(gnome_power_status)):
try:
input("press Enter to continue or Ctrl + c to exit...")
except KeyboardInterrupt:
conf.notifier.stop()
sys.exit(0)
monitor = SystemMonitor(type=ViewType.STATS)
monitor.run()
elif get_state:
not_running_daemon_check()
override = get_override()

View File

@ -435,7 +435,9 @@ def get_load():
print("\nTotal CPU usage:", cpuload, "%")
print("Total system load: {:.2f}".format(load1m))
print("Average temp. of all cores: {:.2f} °C \n".format(avg_all_core_temp))
from auto_cpufreq.modules.system_info import SystemInfo
print("Average temp. of all cores: {:.2f} °C \n".format(SystemInfo.avg_temp()))
return cpuload, load1m
@ -574,7 +576,9 @@ def set_powersave():
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
else: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
from auto_cpufreq.modules.system_info import SystemInfo
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
footer()
@ -591,7 +595,9 @@ def mon_powersave():
if cpuload >= 20: print("suggesting to set turbo boost: on") # high cpu usage trigger
else: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
from auto_cpufreq.modules.system_info import SystemInfo
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
@ -672,32 +678,37 @@ def set_performance():
print("Configuration file disables turbo boost")
set_turbo(False)
else:
from auto_cpufreq.modules.system_info import SystemInfo
if (
psutil.cpu_percent(percpu=False, interval=0.01) >= 20.0
or max(psutil.cpu_percent(percpu=True, interval=0.01)) >= 75
):
print("High CPU load", end=""), display_system_load_avg()
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
elif avg_all_core_temp >= 70: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
elif SystemInfo.avg_temp() >= 70: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
else: set_turbo(True)
elif load1m >= performance_load_threshold:
print("High system load", end=""), display_system_load_avg()
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
elif avg_all_core_temp >= 65: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
elif SystemInfo.avg_temp() >= 65: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
else: set_turbo(True)
else:
print("Load optimal", end=""), display_system_load_avg()
if cpuload >= 20: set_turbo(True) # high cpu usage trigger
else: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
set_turbo(False)
footer()
def mon_performance():
from auto_cpufreq.modules.system_info import SystemInfo
cpuload, load1m = get_load()
if (
@ -705,12 +716,14 @@ def mon_performance():
or max(psutil.cpu_percent(percpu=True, interval=0.01)) >= 75
):
print("High CPU load", end=""), display_system_load_avg()
if cpuload >= 20: # high cpu usage trigger
print("suggesting to set turbo boost: on")
get_turbo()
# set turbo state based on average of all core temperatures
elif cpuload <= 25 and avg_all_core_temp >= 70:
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
elif cpuload <= 25 and SystemInfo.avg_temp() >= 70:
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
else:
@ -721,8 +734,8 @@ def mon_performance():
if cpuload >= 20: # high cpu usage trigger
print("suggesting to set turbo boost: on")
get_turbo()
elif cpuload <= 25 and avg_all_core_temp >= 65: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
elif cpuload <= 25 and SystemInfo.avg_temp() >= 65: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
else:
@ -733,8 +746,8 @@ def mon_performance():
if cpuload >= 20: # high cpu usage trigger
print("suggesting to set turbo boost: on")
get_turbo()
elif cpuload <= 25 and avg_all_core_temp >= 60: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {avg_all_core_temp}°C")
elif cpuload <= 25 and SystemInfo.avg_temp() >= 60: # set turbo state based on average of all core temperatures
print(f"Optimal total CPU usage: {cpuload}%, high average core temp: {SystemInfo.avg_temp()}°C")
print("suggesting to set turbo boost: off")
get_turbo()
else:
@ -895,11 +908,6 @@ def sysinfo():
if offline_cpus: print(f"\nDisabled CPUs: {','.join(offline_cpus)}")
# get average temperature of all cores
avg_cores_temp = sum(temp_per_cpu)
global avg_all_core_temp
avg_all_core_temp = float(avg_cores_temp / online_cpu_count)
# print current fan speed
current_fans = list(psutil.sensors_fans())
for current_fan in current_fans: print("\nCPU fan speed:", psutil.sensors_fans()[current_fan][0].current, "RPM")

View File

@ -0,0 +1,330 @@
from dataclasses import dataclass
import os
from pathlib import Path
import platform
from subprocess import getoutput
from typing import Tuple, List
import psutil
import distro
from pathlib import Path
from auto_cpufreq.config.config import config
from auto_cpufreq.core import get_power_supply_ignore_list
from auto_cpufreq.globals import (
AVAILABLE_GOVERNORS_SORTED,
IS_INSTALLED_WITH_SNAP,
POWER_SUPPLY_DIR,
)
@dataclass
class CoreInfo:
id: int
usage: float
temperature: float
frequency: float
@dataclass
class BatteryInfo:
is_charging: bool | None
is_ac_plugged: bool | None
charging_start_threshold: int | None
charging_stop_threshold: int | None
battery_level: int | None
power_consumption: float | None
def __repr__(self) -> str:
if self.is_charging:
return "charging"
elif not self.is_ac_plugged:
return f"discharging {('(' + '{:.2f}'.format(self.power_consumption) + ' W)') if self.power_consumption != None else ''}"
return "Not Charging"
@dataclass
class SystemReport:
distro_name: str
distro_ver: str
arch: str
processor_model: str
total_core: int | None
kernel_version: str
current_gov: str | None
current_epp: str | None
current_epb: str | None
cpu_driver: str
cpu_fan_speed: int | None
cpu_usage: float
cpu_max_freq: float | None
cpu_min_freq: float | None
load: float
avg_load: Tuple[float, float, float] | None
cores_info: list[CoreInfo]
battery_info: BatteryInfo
is_turbo_on: Tuple[bool | None, bool | None]
class SystemInfo:
"""
Provides system information related to CPU, distribution, and performance metrics.
"""
def __init__(self):
self.distro_name: str = (
distro.name(pretty=True) if not IS_INSTALLED_WITH_SNAP else "UNKNOWN"
)
self.distro_version: str = (
distro.version() if not IS_INSTALLED_WITH_SNAP else "UNKNOWN"
)
self.architecture: str = platform.machine()
self.processor_model: str = (
getoutput("grep -E 'model name' /proc/cpuinfo -m 1").split(":")[-1].strip()
)
self.total_cores: int | None = psutil.cpu_count(logical=True)
self.cpu_driver: str = getoutput(
"cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver"
).strip()
self.kernel_version: str = platform.release()
@staticmethod
def cpu_min_freq() -> float | None:
freqs = psutil.cpu_freq(percpu=True)
return min((freq.min for freq in freqs), default=None)
@staticmethod
def cpu_max_freq() -> float | None:
freqs = psutil.cpu_freq(percpu=True)
return max((freq.max for freq in freqs), default=None)
@staticmethod
def get_cpu_info() -> List[CoreInfo]:
"""Returns detailed CPU information for each core."""
cpu_usage = psutil.cpu_percent(percpu=True)
cpu_freqs = psutil.cpu_freq(percpu=True)
try:
temps = psutil.sensors_temperatures()
core_temps = [temp.current for temp in temps.get("coretemp", [])]
except AttributeError:
core_temps = []
avg_temp = sum(core_temps) / len(core_temps) if core_temps else 0.0
return [
CoreInfo(
id=i,
usage=cpu_usage[i],
temperature=core_temps[i] if i < len(core_temps) else avg_temp,
frequency=cpu_freqs[i].current,
)
for i in range(len(cpu_usage))
]
@staticmethod
def cpu_fan_speed() -> int | None:
fans = psutil.sensors_fans()
return next((fan[0].current for fan in fans.values() if fan), None)
@staticmethod
def current_gov() -> str | None:
try:
with open(
"/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"
) as f:
return f.read().strip()
except:
return None
@staticmethod
def current_epp() -> str | None:
epp_path = "/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference"
if not Path(epp_path).exists():
return None
return config.get_config().get(
"battery", "energy_performance_preference", fallback="balance_power"
)
@staticmethod
def current_epb() -> str | None:
epb_path = "/sys/devices/system/cpu/intel_pstate"
if not Path(epb_path).exists():
return None
return config.get_config().get(
"battery", "energy_perf_bias", fallback="balance_power"
)
@staticmethod
def cpu_usage() -> float:
return psutil.cpu_percent(
interval=0.5
) # Reduced interval for better responsiveness
@staticmethod
def system_load() -> float:
return os.getloadavg()[0]
@staticmethod
def avg_load() -> Tuple[float, float, float]:
return os.getloadavg()
@staticmethod
def avg_temp() -> int:
temps: List[float] = [i.temperature for i in SystemInfo.get_cpu_info()]
return int(sum(temps) / len(temps))
@staticmethod
def turbo_on() -> Tuple[bool | None, bool | None]:
"""Get CPU turbo mode status.
Returns: Tuple[bool | None, bool | None]:
The first value indicates whether turbo mode is enabled, None if unknown
The second value indicates whether auto mode is enabled (amd_pstate only), None if unknown
"""
intel_pstate = Path("/sys/devices/system/cpu/intel_pstate/no_turbo")
cpu_freq = Path("/sys/devices/system/cpu/cpufreq/boost")
amd_pstate = Path("/sys/devices/system/cpu/amd_pstate/status")
if intel_pstate.exists():
control_file: Path = intel_pstate
inverse_logic = True
elif cpu_freq.exists():
control_file = cpu_freq
inverse_logic = False
elif amd_pstate.exists():
amd_status: str = amd_pstate.read_text().strip()
if amd_status == "active":
return None, True
return None, False
else:
return None, None
try:
current_value = int(control_file.read_text().strip())
return bool(current_value) ^ inverse_logic, False
except Exception as e:
return None, None
@staticmethod
def battery_info() -> BatteryInfo:
def read_file(path: str) -> str | None:
try:
with open(path, "r") as f:
return f.read().strip()
except FileNotFoundError:
return None
power_supplies: List[str] = sorted(os.listdir(POWER_SUPPLY_DIR))
if not power_supplies:
return BatteryInfo(
is_charging=None,
is_ac_plugged=True,
charging_start_threshold=None,
charging_stop_threshold=None,
battery_level=None,
power_consumption=None,
)
is_ac_plugged = None
is_charging = None
battery_level = None
power_consumption = None
charging_start_threshold = None
charging_stop_threshold = None
for supply in power_supplies:
if any(item in supply for item in get_power_supply_ignore_list()):
continue
supply_type: str | None = read_file(f"{POWER_SUPPLY_DIR}{supply}/type")
if supply_type == "Mains":
power_supply_online: str | None = read_file(
f"{POWER_SUPPLY_DIR}{supply}/online"
)
is_ac_plugged = power_supply_online == "1"
elif supply_type == "Battery":
battery_status: str | None = read_file(
f"{POWER_SUPPLY_DIR}{supply}/status"
)
battery_percentage: str | None = read_file(
f"{POWER_SUPPLY_DIR}{supply}/capacity"
)
energy_rate: str | None = read_file(
f"{POWER_SUPPLY_DIR}{supply}/power_now"
)
charge_start_threshold: str | None = read_file(
f"{POWER_SUPPLY_DIR}{supply}/charge_start_threshold"
)
charge_stop_threshold: str | None = read_file(
f"{POWER_SUPPLY_DIR}{supply}/charge_stop_threshold"
)
is_charging: bool | None = (
battery_status.lower() == "charging" if battery_status else None
)
battery_level: int | None = (
int(battery_percentage) if battery_percentage else None
)
power_consumption: float | None = (
float(energy_rate) / 1_000_000 if energy_rate else None
)
charging_start_threshold: int | None = (
int(charge_start_threshold) if charge_start_threshold else None
)
charging_stop_threshold: int | None = (
int(charge_stop_threshold) if charge_stop_threshold else None
)
return BatteryInfo(
is_charging=is_charging,
is_ac_plugged=is_ac_plugged,
charging_start_threshold=charging_start_threshold,
charging_stop_threshold=charging_stop_threshold,
battery_level=battery_level,
power_consumption=power_consumption,
)
@staticmethod
def turbo_on_suggestion() -> bool:
usage = SystemInfo.cpu_usage()
if usage >= 20.0:
return True
elif usage <= 25 and SystemInfo.avg_temp() >= 70:
return False
return False
@staticmethod
def governor_suggestion() -> str:
if SystemInfo.battery_info().is_ac_plugged:
return AVAILABLE_GOVERNORS_SORTED[0]
return AVAILABLE_GOVERNORS_SORTED[-1]
def generate_system_report(self) -> SystemReport:
return SystemReport(
distro_name=self.distro_name,
distro_ver=self.distro_version,
arch=self.architecture,
processor_model=self.processor_model,
total_core=self.total_cores,
cpu_driver=self.cpu_driver,
kernel_version=self.kernel_version,
current_gov=self.current_gov(),
current_epp=self.current_epp(),
current_epb=self.current_epb(),
cpu_fan_speed=self.cpu_fan_speed(),
cpu_usage=self.cpu_usage(),
cpu_max_freq=self.cpu_max_freq(),
cpu_min_freq=self.cpu_min_freq(),
load=self.system_load(),
avg_load=self.avg_load(),
cores_info=self.get_cpu_info(),
is_turbo_on=self.turbo_on(),
battery_info=self.battery_info(),
)
system_info = SystemInfo()

View File

@ -0,0 +1,301 @@
import sys
from typing import Callable
import urwid
import time
from .system_info import SystemReport, system_info
from auto_cpufreq.config.config import config
from enum import Enum
class ViewType(str, Enum):
STATS = "Stats"
MONITOR = "Monitor"
LIVE = "Live"
def __str__(self) -> str:
return self.value
class SystemMonitor:
def __init__(self, type: ViewType, suggestion: bool = False):
self.type: ViewType = type
self.title_header = urwid.Text(f"{type} Mode", align="center")
self.header = urwid.Columns(
[
self.title_header,
]
)
# Create separate content walkers for left and right columns
self.left_content = urwid.SimpleListWalker([])
self.right_content = urwid.SimpleListWalker([])
# Create listboxes for both columns
self.left_listbox = urwid.ListBox(self.left_content)
self.right_listbox = urwid.ListBox(self.right_content)
# Create a columns widget with a vertical line (using box drawing character)
self.columns = urwid.Columns(
[
("weight", 1, self.left_listbox),
(
"fixed",
1,
urwid.AttrMap(urwid.SolidFill(""), "divider"),
), # Vertical line # type: ignore
("weight", 1, self.right_listbox),
],
dividechars=0,
)
self.footer = urwid.AttrMap(
urwid.Text(
"Press Q or Ctrl+C to quit | Use ↑↓ or PageUp/PageDown to scroll",
align="center",
),
"footer",
)
self.frame = urwid.Frame(
body=self.columns,
header=urwid.AttrMap(self.header, "header"),
footer=self.footer,
)
palette = [
("header", "white", "dark blue"),
("footer", "white", "dark green"),
("body", "white", "default"),
("divider", "light gray", "default"), # Style for the vertical line
]
if suggestion:
palette.append(("suggestion", "yellow", "default"))
self.loop = urwid.MainLoop(
self.frame, palette=palette, unhandled_input=self.handle_input
)
self.last_focus_left = 0
self.last_focus_right = 0
self.on_quit: Callable[[], None] | None = None
self.suggestion = suggestion
def update(self, loop: urwid.MainLoop, user_data: dict) -> None:
# Store current focus positions
if len(self.left_content) > 0:
_, self.last_focus_left = self.left_listbox.get_focus()
if self.last_focus_left is None:
self.last_focus_left = 0
if len(self.right_content) > 0:
_, self.last_focus_right = self.right_listbox.get_focus()
if self.last_focus_right is None:
self.last_focus_right = 0
current_time = time.strftime("%H:%M:%S")
self.title_header.set_text(f"{self.type} Mode - {current_time}")
report: SystemReport = system_info.generate_system_report()
self.format_system_info(report)
# Restore focus positions
if len(self.left_content) > 0:
self.left_listbox.set_focus(min(self.last_focus_left, len(self.left_content) - 1)) # type: ignore
if len(self.right_content) > 0:
self.right_listbox.set_focus(min(self.last_focus_right, len(self.right_content) - 1)) # type: ignore
self.loop.set_alarm_in(2, self.update) # type: ignore
def handle_input(self, key):
if key in ("q", "Q"):
if self.on_quit:
self.on_quit()
raise urwid.ExitMainLoop()
def format_system_info(self, report: SystemReport):
self.left_content.clear()
self.right_content.clear()
# Helper function to create centered text
def aligned_text(text: str) -> urwid.Text:
return urwid.Text(text, align="left")
# Left Column - System Info and CPU Stats
self.left_content.extend(
[
urwid.AttrMap(aligned_text("System Information"), "header"),
aligned_text(""),
aligned_text(f"Linux distro: {report.distro_name} {report.distro_ver}"),
aligned_text(f"Linux kernel: {report.kernel_version}"),
aligned_text(f"Processor: {report.processor_model}"),
aligned_text(f"Cores: {report.total_core}"),
aligned_text(f"Architecture: {report.arch}"),
aligned_text(f"Driver: {report.cpu_driver}"),
aligned_text(""),
]
)
if config.has_config():
self.left_content.append(
aligned_text(f"Using settings defined in {config.path} file")
)
self.left_content.append(aligned_text(""))
# CPU Stats
self.left_content.extend(
[
urwid.AttrMap(aligned_text("Current CPU Stats"), "header"),
aligned_text(""),
aligned_text(f"CPU max frequency: {report.cpu_max_freq} MHz"),
aligned_text(f"CPU min frequency: {report.cpu_min_freq} MHz"),
aligned_text(""),
aligned_text("Core Usage Temperature Frequency"),
]
)
for core in report.cores_info:
self.left_content.append(
aligned_text(
f"CPU{core.id:<2} {core.usage:>4.1f}% {core.temperature:>6.0f} °C {core.frequency:>6.0f} MHz"
)
)
if report.cpu_fan_speed:
self.left_content.append(aligned_text(""))
self.left_content.append(
aligned_text(f"CPU fan speed: {report.cpu_fan_speed} RPM")
)
# Right Column - Battery, Frequency Scaling, and System Stats
if report.battery_info != None:
self.right_content.extend(
[
urwid.AttrMap(aligned_text("Battery Stats"), "header"),
aligned_text(""),
aligned_text(f"Battery status: {str(report.battery_info)}"),
aligned_text(
f"Battery precentage: {(str(report.battery_info.battery_level) + '%') if report.battery_info.battery_level != None else 'Unknown'}"
),
aligned_text(
f'AC plugged: {("Yes" if report.battery_info.is_ac_plugged else "No") if report.battery_info.is_ac_plugged != None else "Unknown"}'
),
aligned_text(
f'Charging start threshold: {report.battery_info.charging_start_threshold if report.battery_info.is_ac_plugged != None else "Unknown"}'
),
aligned_text(
f'Charging stop threshold: {report.battery_info.charging_stop_threshold if report.battery_info.is_ac_plugged != None else "Unknown"}'
),
aligned_text(""),
]
)
# CPU Frequency Scaling
self.right_content.extend(
[
urwid.AttrMap(aligned_text("CPU Frequency Scaling"), "header"),
aligned_text(""),
aligned_text(
f'Setting to use: "{report.current_gov if report.current_gov != None else "Unknown"}" governor'
),
]
)
if (
self.suggestion
and report.current_gov != None
and system_info.governor_suggestion() != report.current_gov
):
self.right_content.append(
urwid.AttrMap(
aligned_text(
f'Suggesting use of: "{system_info.governor_suggestion()}" governor'
),
"suggestion",
)
)
if report.current_epp:
self.right_content.append(
aligned_text(f"EPP setting: {report.current_epp}")
)
else:
self.right_content.append(
aligned_text("Not setting EPP (not supported by system)")
)
if report.current_epb:
self.right_content.append(
aligned_text(f'Setting to use: "{report.current_epb}" EPB')
)
self.right_content.append(aligned_text(""))
# System Statistics
self.right_content.extend(
[
urwid.AttrMap(aligned_text("System Statistics"), "header"),
aligned_text(""),
aligned_text(f"Total CPU usage: {report.cpu_usage:.1f} %"),
aligned_text(f"Total system load: {report.load:.2f}"),
]
)
if report.cores_info:
avg_temp = sum(core.temperature for core in report.cores_info) / len(
report.cores_info
)
self.right_content.append(
aligned_text(f"Average temp. of all cores: {avg_temp:.2f} °C")
)
if report.avg_load:
load_status = "Load optimal" if report.load < 1.0 else "Load high"
self.right_content.append(
aligned_text(
f"{load_status} (load average: {report.avg_load[0]:.2f}, {report.avg_load[1]:.2f}, {report.avg_load[2]:.2f})"
)
)
if report.cores_info:
usage_status = "Optimal" if report.cpu_usage < 70 else "High"
temp_status = "high" if avg_temp > 75 else "normal" # type: ignore
self.right_content.append(
aligned_text(
f"{usage_status} total CPU usage: {report.cpu_usage:.1f}%, {temp_status} average core temp: {avg_temp:.1f}°C" # type: ignore
)
)
turbo_status: str
if report.is_turbo_on[0] != None:
turbo_status = "On" if report.is_turbo_on[0] else "Off"
elif report.is_turbo_on[1] != None:
turbo_status = (
f"Auto mode {'enabled' if report.is_turbo_on[1] else 'disabled'}"
)
else:
turbo_status = "Unknown"
self.right_content.append(aligned_text(f"Setting turbo boost: {turbo_status}"))
if (
self.suggestion
and report.is_turbo_on[0] != None
and system_info.turbo_on_suggestion() != report.is_turbo_on[0]
):
self.right_content.append(
urwid.AttrMap(
aligned_text(
f'Suggesting to set turbo boost: {"on" if system_info.turbo_on_suggestion() else "off"}'
),
"suggestion",
)
)
def run(self, on_quit: Callable[[], None] | None = None):
try:
if on_quit:
self.on_quit = on_quit
self.loop.set_alarm_in(0, self.update) # type: ignore
self.loop.run()
except KeyboardInterrupt:
if on_quit:
on_quit()
sys.exit(0)

View File

@ -27,6 +27,7 @@ click = "^8.1.0"
distro = "^1.8.0"
requests = "^2.32.3"
PyGObject = "^3.46.0"
urwid = "^2.6.16"
pyinotify = {git = "https://github.com/shadeyg56/pyinotify-3.12"}
[tool.poetry.group.dev.dependencies]