mirror of
https://github.com/AdnanHodzic/auto-cpufreq.git
synced 2025-08-25 11:38:28 +02:00
* 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
302 lines
11 KiB
Python
302 lines
11 KiB
Python
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)
|