diff --git a/starpilot/common/cpu_throttle.py b/starpilot/common/cpu_throttle.py index 1d4b4cc29..3bdee5d55 100644 --- a/starpilot/common/cpu_throttle.py +++ b/starpilot/common/cpu_throttle.py @@ -1,6 +1,8 @@ from __future__ import annotations +from functools import lru_cache import math +from pathlib import Path import time @@ -11,9 +13,34 @@ DEVICE_BUSY_HOT_CORE_COUNT = 4 _throttle_state: dict[str, dict[str, float]] = {} +@lru_cache(maxsize=1) +def _online_cpu_count() -> int | None: + try: + spec = Path("/sys/devices/system/cpu/online").read_text(encoding="utf-8").strip() + count = 0 + for group in spec.split(","): + bounds = group.split("-", maxsplit=1) + first = int(bounds[0]) + last = int(bounds[-1]) + if last < first: + return None + count += last - first + 1 + return count or None + except (OSError, ValueError): + return None + + +def _online_cpu_usage(cpu_usage) -> list[float]: + usage = [float(value) for value in cpu_usage] + online_count = _online_cpu_count() + if online_count is not None and online_count < len(usage): + return usage[:online_count] + return usage + + def device_cpu_throttle_factor(cpu_usage, name="vision"): """Return a process-local, low-pass-filtered CPU throttle factor.""" - usage = list(cpu_usage) + usage = _online_cpu_usage(cpu_usage) if not usage: return 1.0 @@ -34,7 +61,7 @@ def device_cpu_throttle_factor(cpu_usage, name="vision"): state["factor"] = min(max(target_factor * alpha + state["factor"] * (1.0 - alpha), 1.0), 4.0) if state["factor"] >= state["last_logged_factor"] + 0.6: - print(f"[{name}] CPU throttle factor={state['factor']:.1f}x (avg={average:.0f}%, hot={hot_cores})") + print(f"[{name}] CPU throttle factor={state['factor']:.1f}x (avg={average:.0f}%, hot={hot_cores}/{len(usage)})") state["last_logged_factor"] = state["factor"] elif state["factor"] <= 1.05 and state["last_logged_factor"] > 1.05: print(f"[{name}] CPU recovered") diff --git a/starpilot/common/tests/test_cpu_throttle.py b/starpilot/common/tests/test_cpu_throttle.py new file mode 100644 index 000000000..5a1d7bd58 --- /dev/null +++ b/starpilot/common/tests/test_cpu_throttle.py @@ -0,0 +1,26 @@ +import pytest + +from openpilot.starpilot.common import cpu_throttle + + +def test_offline_cpu_placeholders_do_not_hide_sustained_load(monkeypatch): + monkeypatch.setattr(cpu_throttle, "_online_cpu_count", lambda: 4) + + usage = cpu_throttle._online_cpu_usage([80, 80, 80, 80, 0, 0, 0, 0]) + average = sum(usage) / len(usage) + + assert usage == [80.0, 80.0, 80.0, 80.0] + assert cpu_throttle._compute_throttle_factor(average, 0) == pytest.approx(1.75) + + +@pytest.mark.parametrize(("online_spec", "expected"), ( + ("0-3", 4), + ("0-3,6-7", 6), +)) +def test_online_cpu_count_parser(monkeypatch, tmp_path, online_spec, expected): + online_path = tmp_path / "online" + online_path.write_text(online_spec, encoding="utf-8") + monkeypatch.setattr(cpu_throttle, "Path", lambda _: online_path) + cpu_throttle._online_cpu_count.cache_clear() + + assert cpu_throttle._online_cpu_count() == expected