import glob import psutil from app.sample import Sample _temp_path: str | None = None _temp_checked = False def _read(path: str) -> str | None: """Read a sysfs file, returning its stripped contents. Args: path: path under /sys. Returns: The file contents, or None if it cannot be read. """ try: with open(path) as f: return f.read().strip() except OSError: return None def _find_temp_path() -> str | None: """Find the sysfs file reporting CPU temperature, in millidegrees. Prefers hwmon sensors named k10temp (AMD), coretemp (Intel), or cpu_thermal (ARM), taking the first temp*_input of the first matching hwmon; falls back to the acpitz thermal zone. The result is cached by temp() for the process lifetime. Returns: The sysfs file to read, or None if no suitable sensor exists. """ for hwmon in sorted(glob.glob("/sys/class/hwmon/hwmon*")): name = (_read(f"{hwmon}/name") or "").lower() if name in ("k10temp", "coretemp", "cpu_thermal"): for t in sorted(glob.glob(f"{hwmon}/temp*_input")): return t return None for zone in sorted(glob.glob("/sys/class/thermal/thermal_zone*")): if (_read(f"{zone}/type") or "").lower() == "acpitz": return f"{zone}/temp" return None def temp() -> float | None: """Read the CPU temperature in degrees Celsius. The sensor path is resolved once via _find_temp_path. Sysfs reports millidegrees; the value is converted and rounded to 0.1 °C. Returns: Temperature in °C, or None if no sensor or unreadable value. """ global _temp_path, _temp_checked if not _temp_checked: _temp_checked = True _temp_path = _find_temp_path() if _temp_path is None: return None v = _read(_temp_path) if not v: return None try: n = float(v) except ValueError: return None return round(n / 1000.0, 1) def prime() -> None: """Prime psutil's CPU percent counter so the next call has a real delta. psutil.cpu_percent(None) returns 0.0 on its first call; sampler_loop invokes this before the first sample for that reason. """ _ = psutil.cpu_percent(None) def core_count() -> int: """Number of logical CPU cores (at least 1).""" return psutil.cpu_count(logical=True) or 1 def fill(s: Sample) -> None: """Fill the cpu, cpu_temp, and load-average fields of a Sample. Args: s: sample to fill. """ s.cpu = psutil.cpu_percent(None) s.cpu_temp = temp() l1, l5, l15 = psutil.getloadavg() s.load1 = l1 s.load5 = l5 s.load15 = l15