Background thread that samples CPU, RAM, and temperature every `interval` seconds. Thread is daemon so it never blocks process exit.
| 22 | |
| 23 | |
| 24 | class SystemMonitor: |
| 25 | """ |
| 26 | Background thread that samples CPU, RAM, and temperature every `interval` |
| 27 | seconds. Thread is daemon so it never blocks process exit. |
| 28 | """ |
| 29 | |
| 30 | def __init__(self, interval: float = 2.0): |
| 31 | self._interval = interval |
| 32 | self._lock = threading.Lock() |
| 33 | self._cpu: float = 0.0 |
| 34 | self._ram_used: int = 0 |
| 35 | self._ram_total: int = 0 |
| 36 | self._temp: Optional[float] = None |
| 37 | self._battery_pct: Optional[int] = None |
| 38 | self._battery_charging: bool = False |
| 39 | self._running = False |
| 40 | self._thread: Optional[threading.Thread] = None |
| 41 | self._battery_tick: int = 0 # read battery every N loops (slow cmd) |
| 42 | |
| 43 | # For /proc/stat delta CPU calculation |
| 44 | self._prev_idle: int = 0 |
| 45 | self._prev_total: int = 0 |
| 46 | self._seed_cpu_proc() # seed /proc/stat fallback |
| 47 | |
| 48 | # ── public API ──────────────────────────────────────────────────────────── |
| 49 | |
| 50 | def start(self) -> None: |
| 51 | if self._thread and self._thread.is_alive(): |
| 52 | return |
| 53 | self._running = True |
| 54 | # Do one immediate read so the first render() call has real data |
| 55 | self._loop_once() |
| 56 | self._thread = threading.Thread(target=self._loop, daemon=True, name="sysmon") |
| 57 | self._thread.start() |
| 58 | |
| 59 | def stop(self) -> None: |
| 60 | self._running = False |
| 61 | |
| 62 | @property |
| 63 | def snapshot(self) -> dict: |
| 64 | with self._lock: |
| 65 | return { |
| 66 | "cpu": self._cpu, |
| 67 | "ram_used": self._ram_used, |
| 68 | "ram_total": self._ram_total, |
| 69 | "temp": self._temp, |
| 70 | "battery_pct": self._battery_pct, |
| 71 | "battery_charging": self._battery_charging, |
| 72 | } |
| 73 | |
| 74 | def render(self) -> Text: |
| 75 | """Return a Rich Text line suitable for printing as a stats bar.""" |
| 76 | s = self.snapshot |
| 77 | cpu = s["cpu"] |
| 78 | ru = s["ram_used"] / 1024 ** 3 |
| 79 | rt = s["ram_total"] / 1024 ** 3 |
| 80 | temp = s["temp"] |
| 81 |