Commit cfda4841 by PLN (Algolia)

perf: one owner for the CPU knobs — the Bridge drives thermal-mode, the watcher is gone

The Bridge's perf.py shelled out to its own root script (perf-audio) and ran a
watcher that reasserted its last choice every 30 s, on resume and on every
charger edge. gig-up armed it with 'standard' = --optimize = governor
performance, which pins EPP and runs every idle wakeup at 4-5 GHz. On
2026-09-23 it undid a Silent click from the GNOME panel within 31 s and held
the package at 96-100 C at ~5 % load all morning.

- MODES now name thermal-mode-apply arguments (silent, performance); set_mode
  runs the exact-args sudoers form; detect_mode reads /etc/thermal-mode.conf.
- run_watcher/reconcile/desired-file removed: thermal-mode-reassert.service
  already re-applies the persisted mode on resume and charger events.
- gig-up asserts thermal-mode performance (turbo on, powersave governor, EPP
  balance_performance, RT audio) instead of demanding governor=performance.
- rig-doctor and gig-preflight point at thermal-mode-apply, not perf-audio.
parent e534a059
...@@ -103,19 +103,21 @@ pulsar_up(){ pgrep -x pulsar >/dev/null 2>&1; } ...@@ -103,19 +103,21 @@ pulsar_up(){ pgrep -x pulsar >/dev/null 2>&1; }
# to Cool in the tray when you're done. RT priority is already fine (rtprio 95). # to Cool in the tray when you're done. RT priority is already fine (rtprio 95).
# Skip with GIG_PERF=off. --- # Skip with GIG_PERF=off. ---
ensure_perf(){ ensure_perf(){
[ "${GIG_PERF:-standard}" = off ] && { info "perf: auto-assert disabled (GIG_PERF=off)."; return; } [ "${GIG_PERF:-performance}" = off ] && { info "perf: auto-assert disabled (GIG_PERF=off)."; return; }
local maxp gov # The gig posture is thermal-mode `performance`: turbo on, governor POWERSAVE +
maxp=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct 2>/dev/null || echo 100) # EPP balance_performance, RT audio. NOT governor=performance: that pins EPP and
gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null) # runs every idle wakeup at ~4-5 GHz (package 96 C at 5 % load, 2026-09-23).
if [ "${maxp:-100}" -ge 100 ] 2>/dev/null && [ "$gov" = performance ]; then local mode noturbo
ok "perf: CPU uncapped + performance — headroom for MIDI bursts."; return mode=$(sed -n 's/^MODE=//p' /etc/thermal-mode.conf 2>/dev/null)
noturbo=$(cat /sys/devices/system/cpu/intel_pstate/no_turbo 2>/dev/null || echo 1)
if [ "$mode" = performance ] && [ "$noturbo" = 0 ]; then
ok "perf: thermal-mode performance holding — turbo headroom for MIDI bursts."; return
fi fi
info "perf: CPU capped at ${maxp}% ('$gov') → asserting Standard for the set…" info "perf: thermal-mode is '${mode:-unset}' → switching to performance for the set…"
# set_mode writes the desired-state file too, so the watcher won't revert it. if PYTHONPATH="$DIR/tools/bridge" python3 -c "import perf,sys; ok,m=perf.set_mode('performance'); print(m); sys.exit(0 if ok else 1)" 2>/dev/null; then
if PYTHONPATH="$DIR/tools/bridge" python3 -c "import perf,sys; ok,m=perf.set_mode('standard'); print(m); sys.exit(0 if ok else 1)" 2>/dev/null; then ok "perf: performance set (thermal-mode owns it; resume/charger reassert included). Back to quiet after: Thermal panel → Silent."
ok "perf: Standard set + held by the watcher (fans may rise under load — fine on stage; tray → Cool to revert)."
else else
warn "perf: auto-set failed — switch to STANDARD in the perf tray/Bridge yourself." warn "perf: auto-set failed — pick Performance in the Thermal panel yourself."
fi fi
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
python3 bridge.py serve [--host --port] # run the dashboard (default 127.0.0.1:8773) python3 bridge.py serve [--host --port] # run the dashboard (default 127.0.0.1:8773)
python3 bridge.py status # print a perf/thermal snapshot python3 bridge.py status # print a perf/thermal snapshot
python3 bridge.py perf [cool|standard|extreme|normal] # read or switch mode python3 bridge.py perf [silent|performance] # read or switch mode (thermal-mode)
Runs under system python3 (stdlib only). See README to install the systemd Runs under system python3 (stdlib only). See README to install the systemd
--user service for always-on autostart. --user service for always-on autostart.
...@@ -36,11 +36,8 @@ def cmd_perf(a): ...@@ -36,11 +36,8 @@ def cmd_perf(a):
def cmd_serve(a): def cmd_serve(a):
# Keep the chosen perf regime asserted across AC/battery flips & resume # No perf watcher: thermal-mode-reassert.service re-applies the persisted
# (the platform silently resets pstate caps on those events). # mode on resume and charger events, and a second reasserter here is a fight.
import threading
threading.Thread(target=P.run_watcher, daemon=True,
name="perf-watcher").start()
import server import server
server.run(host=a.host, port=a.port) server.run(host=a.host, port=a.port)
......
"""perf — rootless thermal/perf-mode reader + sudo-backed mode switch. """perf — rootless thermal reader + the Bridge's mode buttons.
Ported from perf-tray.py (the PyQt tray) with the Qt dropped, so the same proven Reading thermals is rootless (hwmon + cpufreq sysfs). *Changing* mode goes
logic backs the web Bridge. Reading thermals/mode is rootless (hwmon + cpufreq through the SRE thermal tooling (`thermal-mode-apply`, via gearbox.HELPER), the
sysfs); only *changing* mode needs root, done via `sudo -n` against a ROOT-OWNED, same single owner of the pstate/RAPL/fan knobs that the GNOME panel and
sudoers-whitelisted copy of perf.sh at /usr/local/sbin/perf-audio (see perf-tray drive. This module used to shell out to its own root script
perf-audio.sudoers) — never the user-editable repo copy. (perf-audio) and ran a watcher that reasserted its choice every 30 s; with two
owners of the same knobs, whichever wrote last won, and on 2026-09-23 the
watcher undid a Silent click within 31 s and held the governor at `performance`
(EPP pinned, ~4 GHz at 5 % load, package 96 C) through a whole morning.
Resume / AC-plug reassert is thermal-mode-reassert.service's job now.
DRY note: perf-tray.py still has its own copy of Thermals/detect_mode; a later DRY note: perf-tray.py still has its own copy of Thermals/detect_mode; a later
pass can have it import this module. Kept separate now to avoid touching the pass can have it import this module. Kept separate now to avoid touching the
...@@ -17,18 +21,16 @@ import os ...@@ -17,18 +21,16 @@ import os
import subprocess import subprocess
import time import time
# Root-owned, sudoers-whitelisted deployment of perf.sh (same as perf-tray). import gearbox as GB
SCRIPT = os.environ.get("PERF_TRAY_SCRIPT", "/usr/local/sbin/perf-audio")
# mode key -> (label, perf.sh flag). Order = coolest → hottest → normal; the # mode key -> (label, thermal-mode-apply argument). Order = quiet -> hard; the
# tray menu and Bridge toolbar both iterate this, so a new mode lights up in # Bridge toolbar iterates this. Every argument is an exact-args NOPASSWD rule in
# both faces automatically. # /etc/sudoers.d/thermal-mode, so nothing beyond a key from this table ever
# reaches sudo. `performance` is the gig posture: turbo on, but with the
# powersave governor + EPP balance_performance (idle stays calm) and RT audio.
MODES = { MODES = {
"silent": ("Silent", "--silent"), "silent": ("Silent", "silent"),
"cool": ("Cool", "--cool"), "performance": ("Performance", "performance"),
"standard": ("Standard", "--optimize"),
"extreme": ("Extreme", "--extreme"),
"normal": ("Normal", "--stop"),
} }
# temp thresholds (°C) → state name; the UI maps state→color (matches tray). # temp thresholds (°C) → state name; the UI maps state→color (matches tray).
...@@ -252,137 +254,33 @@ class Thermals: ...@@ -252,137 +254,33 @@ class Thermals:
def detect_mode(): def detect_mode():
gov = _read("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor") """The mode thermal-mode persisted (its conf is the one source of truth),
epp = _read("/sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference") or "other" when it is something this toolbar does not offer. The live
maxp = _read_int("/sys/devices/system/cpu/intel_pstate/max_perf_pct", 100) sysfs readings in snapshot() say whether it actually holds."""
minp = _read_int("/sys/devices/system/cpu/intel_pstate/min_perf_pct", 0) m = GB.read_conf().get("MODE")
if gov == "performance" and minp >= 100: return m if m in MODES else "other"
return "extreme"
if gov == "performance":
return "standard"
# Silent and cool are both powersave + capped clock; the EPP tells them apart
# (silent biases all the way to "power", cool keeps "balance_performance").
if gov == "powersave" and epp in ("power", "balance_power") and (maxp or 100) < 100:
return "silent"
if epp == "balance_performance" and (maxp or 100) < 100:
return "cool"
return "normal"
def script_available(): def script_available():
"""True if the sudoers-whitelisted root perf.sh is deployed.""" """True if the root thermal-mode helper is installed."""
return os.path.exists(SCRIPT) return GB.installed()
def set_mode(mode): def set_mode(mode):
"""Switch perf mode via `sudo -n`. Returns (ok, message).""" """Switch mode via `sudo -n thermal-mode-apply <mode>`. Returns (ok, message)."""
if mode not in MODES: if mode not in MODES:
return False, f"unknown mode {mode!r}; have {sorted(MODES)}" return False, f"unknown mode {mode!r}; have {sorted(MODES)}"
if not script_available(): if not script_available():
return False, (f"{SCRIPT} not deployed — install the root-owned perf.sh + " return False, f"{GB.HELPER} not installed (see SRE/thermal/install.sh)"
"sudoers rule (see README 'Deploy mode-switching')")
flag = MODES[mode][1]
try: try:
r = subprocess.run(["sudo", "-n", SCRIPT, flag], r = subprocess.run(["sudo", "-n", GB.HELPER, MODES[mode][1]],
capture_output=True, text=True, timeout=30) capture_output=True, text=True, timeout=30)
except (OSError, subprocess.SubprocessError) as e: except (OSError, subprocess.SubprocessError) as e:
return False, str(e) return False, str(e)
out = (r.stdout or "").strip() or (r.stderr or "").strip()
if r.returncode != 0: if r.returncode != 0:
return False, (r.stderr or r.stdout or f"exit {r.returncode}").strip() return False, out or f"exit {r.returncode}"
write_desired(mode) # record intent so the watcher can reassert it (below) return True, out or "ok"
return True, (r.stdout or "ok").strip()
# --- power-aware desired-state maintenance -----------------------------------
# One chosen regime, reasserted whenever the platform silently resets the pstate
# caps underneath us. The trigger that started this: plugging in on AC lifts the
# battery turbo limit, so a "cool" set is quietly undone and temps jump ~25°C.
# Also fires on resume-from-suspend. The Bridge daemon runs run_watcher().
#
# "desired" is the mode the user actually chose (written by set_mode on both the
# tray and web faces). desired absent or "normal" == maintenance off, so we never
# fight a deliberate switch back to platform defaults.
DESIRED_PATH = os.environ.get(
"PERF_DESIRED_FILE", os.path.expanduser("~/.cache/parvagues/perf-desired"))
def read_desired():
"""The mode to keep asserted, or None if maintenance is off."""
m = _read(DESIRED_PATH)
return m if m in MODES else None
def write_desired(mode):
"""Persist the chosen regime (called on every successful mode switch)."""
if mode not in MODES:
return
try:
os.makedirs(os.path.dirname(DESIRED_PATH) or ".", exist_ok=True)
with open(DESIRED_PATH, "w") as f:
f.write(mode)
except OSError:
pass
def on_ac():
"""True on AC, False on battery, None if no mains adapter is exposed."""
for p in sorted(glob.glob("/sys/class/power_supply/A*/online")):
v = _read_int(p)
if v is not None:
return bool(v)
return None
def reconcile(force=False):
"""Reassert the desired mode if live state has drifted from it. Returns
(acted, message). desired absent/"normal" == maintenance off."""
desired = read_desired()
if not desired or desired == "normal":
return False, "maintenance off"
if not force and detect_mode() == desired:
return False, f"{desired} holding"
ok, msg = set_mode(desired)
return ok, (f"reasserted {desired} ({msg})" if ok else f"reassert FAILED ({msg})")
def run_watcher(interval=5.0, drift_every=6, log=None):
"""Daemon loop: keep the desired perf regime across AC/battery flips and
resume-from-suspend. Reasserts on a power-source edge and on resume (both
force), plus a periodic drift check as the safety net. Blocks; run in a
thread. `log` is a callable(str); defaults to stdout."""
emit = log or (lambda s: print(f"[perf-watch] {s}", flush=True))
if read_desired() is None: # seed from whatever is live right now
cur = detect_mode()
if cur in MODES and cur != "normal":
write_desired(cur)
# BOOTTIME (not MONOTONIC) counts time spent suspended, so a resume shows up
# as a big gap here — MONOTONIC freezes across suspend and would miss it,
# leaving only the slow periodic drift check to catch a post-resume reset.
_bt = getattr(time, "CLOCK_BOOTTIME", None)
uptime = (lambda: time.clock_gettime(_bt)) if _bt is not None else time.monotonic
emit(f"watching (every {interval}s) on_ac={on_ac()} desired={read_desired()}")
last_ac = on_ac()
last_clock = uptime()
i = 0
while True:
time.sleep(interval)
i += 1
now = uptime()
resumed = (now - last_clock) > interval * 3 # gap ≫ sleep ⇒ we were suspended
last_clock = now
ac = on_ac()
edge = ac is not None and last_ac is not None and ac != last_ac
if edge or resumed:
reason = ("power→" + ("AC" if ac else "battery")) if edge else "resume"
time.sleep(2) # let the platform settle
_, msg = reconcile(force=True)
emit(f"{reason}: {msg}")
elif i % drift_every == 0:
acted, msg = reconcile(force=False)
if acted:
emit(f"drift: {msg}")
last_ac = ac if ac is not None else last_ac
def snapshot(therm: "Thermals | None" = None) -> dict: def snapshot(therm: "Thermals | None" = None) -> dict:
......
...@@ -19,24 +19,41 @@ def test_temp_state_thresholds(): ...@@ -19,24 +19,41 @@ def test_temp_state_thresholds():
def test_modes_shape(): def test_modes_shape():
assert set(P.MODES) == {"silent", "cool", "standard", "extreme", "normal"} # Every argument must be an exact-args sudoers form of thermal-mode-apply.
assert P.MODES["extreme"][1] == "--extreme" assert set(P.MODES) == {"silent", "performance"}
assert P.MODES["normal"][1] == "--stop" assert all(arg in {"silent", "performance"} for _, arg in P.MODES.values())
assert P.MODES["silent"][1] == "--silent"
def test_detect_mode_reads_the_thermal_mode_conf(monkeypatch, tmp_path):
def test_detect_mode_silent_vs_cool(monkeypatch): conf = tmp_path / "thermal-mode.conf"
# silent and cool share powersave + a capped clock; EPP disambiguates. monkeypatch.setattr(P.GB, "CONF", conf)
def fake_reads(gov, epp, maxp): conf.write_text("MODE=silent\nTRIM=auto\n")
monkeypatch.setattr(P, "_read", lambda p, d=None:
{"scaling_governor": gov,
"energy_performance_preference": epp}.get(p.split("/")[-1], d))
monkeypatch.setattr(P, "_read_int", lambda p, d=None:
maxp if p.endswith("max_perf_pct") else (0 if p.endswith("min_perf_pct") else d))
fake_reads("powersave", "power", 55)
assert P.detect_mode() == "silent" assert P.detect_mode() == "silent"
fake_reads("powersave", "balance_performance", 85) conf.write_text("MODE=performance\n")
assert P.detect_mode() == "cool" assert P.detect_mode() == "performance"
conf.write_text("MODE=cool\n") # a mode the toolbar does not offer
assert P.detect_mode() == "other"
conf.unlink()
assert P.detect_mode() == "other"
def test_set_mode_argv_is_the_whitelisted_form(monkeypatch):
import types
seen = {}
def fake_run(argv, **kw):
seen["argv"] = argv
return types.SimpleNamespace(returncode=0, stdout="applied\n", stderr="")
monkeypatch.setattr(P, "script_available", lambda: True)
monkeypatch.setattr(P.subprocess, "run", fake_run)
assert P.set_mode("performance") == (True, "applied")
assert seen["argv"] == ["sudo", "-n", P.GB.HELPER, "performance"]
def test_no_watcher_left():
# Two reasserters of one set of knobs is a fight (2026-09-23).
assert not hasattr(P, "run_watcher") and not hasattr(P, "reconcile")
def test_gpu_manager_mode_parse_and_cache(monkeypatch): def test_gpu_manager_mode_parse_and_cache(monkeypatch):
...@@ -70,8 +87,8 @@ def test_set_mode_rejects_unknown(): ...@@ -70,8 +87,8 @@ def test_set_mode_rejects_unknown():
def test_set_mode_guards_missing_script(monkeypatch): def test_set_mode_guards_missing_script(monkeypatch):
monkeypatch.setattr(P, "script_available", lambda: False) monkeypatch.setattr(P, "script_available", lambda: False)
ok, msg = P.set_mode("cool") ok, msg = P.set_mode("silent")
assert ok is False and "not deployed" in msg assert ok is False and "not installed" in msg
def test_snapshot_shape(): def test_snapshot_shape():
...@@ -80,10 +97,10 @@ def test_snapshot_shape(): ...@@ -80,10 +97,10 @@ def test_snapshot_shape():
"freq_max_mhz", "throttle_delta", "script_available", "modes", "freq_max_mhz", "throttle_delta", "script_available", "modes",
"dgpu", "gpu_manager", "thermald"): "dgpu", "gpu_manager", "thermald"):
assert k in s assert k in s
assert s["mode"] in P.MODES assert s["mode"] in set(P.MODES) | {"other"}
assert isinstance(s["cores_c"], list) assert isinstance(s["cores_c"], list)
assert isinstance(s["script_available"], bool) assert isinstance(s["script_available"], bool)
def test_detect_mode_returns_known(): def test_detect_mode_returns_known():
assert P.detect_mode() in P.MODES assert P.detect_mode() in set(P.MODES) | {"other"}
...@@ -218,7 +218,7 @@ def check_audio_rt() -> None: ...@@ -218,7 +218,7 @@ def check_audio_rt() -> None:
f"{policy} prio {prio.group(1) if prio else '?'}") f"{policy} prio {prio.group(1) if prio else '?'}")
else: else:
add(f"{proc} realtime", WARN, f"policy={policy} (not realtime)", add(f"{proc} realtime", WARN, f"policy={policy} (not realtime)",
"sudo /usr/local/sbin/perf-audio --optimize") "sudo -n /usr/local/sbin/thermal-mode-apply performance # RT audio chain")
def check_superdirt_fresh() -> None: def check_superdirt_fresh() -> None:
...@@ -367,8 +367,7 @@ def check_editor() -> None: ...@@ -367,8 +367,7 @@ def check_editor() -> None:
worst = max(nices) worst = max(nices)
if worst > 0: if worst > 0:
add("editor priority", WARN, f"nice={worst} (deprioritized)", add("editor priority", WARN, f"nice={worst} (deprioritized)",
"sudo install -m 755 ~/Work/Sound/Tidal/perf.sh /usr/local/sbin/perf-audio" "sudo renice -n 0 -p $(pgrep -d' ' -f pulsar)")
" # then: bridge.py perf standard")
else: else:
add("editor priority", OK, f"nice={worst}") add("editor priority", OK, f"nice={worst}")
......
...@@ -1181,27 +1181,22 @@ def check_audio_interface() -> None: ...@@ -1181,27 +1181,22 @@ def check_audio_interface() -> None:
# INSTALL SURFACE (not in git — the real portability blockers) # INSTALL SURFACE (not in git — the real portability blockers)
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
def check_perf_audio() -> None: def check_perf_audio() -> None:
bin_state = path_state(Path("/usr/local/sbin/perf-audio")) # The mode switch is the SRE thermal tooling now (thermal-mode-apply +
sudo_state = path_state(Path("/etc/sudoers.d/perf-audio")) # its exact-args sudoers); perf-audio is retired (SRE TODO.d #52).
install_cmd = ( bin_state = path_state(Path("/usr/local/sbin/thermal-mode-apply"))
"sudo install -m 755 -o root -g root ~/Work/Sound/Tidal/perf.sh " sudo_state = path_state(Path("/etc/sudoers.d/thermal-mode"))
"/usr/local/sbin/perf-audio && " install_cmd = "sudo ~/Work/Perso/SRE/thermal/install.sh"
"sudo install -m 440 -o root -g root ~/Work/Sound/Tidal/perf-audio.sudoers "
"/etc/sudoers.d/perf-audio && sudo visudo -cf /etc/sudoers.d/perf-audio"
)
if bin_state == "present" and sudo_state == "present": if bin_state == "present" and sudo_state == "present":
add("INSTALL SURFACE", "perf-audio + sudoers", PASS, add("INSTALL SURFACE", "thermal-mode + sudoers", PASS,
"/usr/local/sbin/perf-audio and /etc/sudoers.d/perf-audio both present") "/usr/local/sbin/thermal-mode-apply and /etc/sudoers.d/thermal-mode both present")
elif sudo_state == "unknown": elif sudo_state == "unknown" and bin_state == "present":
add("INSTALL SURFACE", "perf-audio + sudoers", WARN, add("INSTALL SURFACE", "thermal-mode + sudoers", PASS,
f"bin={bin_state}, sudoers=permission denied to check as this user " "helper present; /etc/sudoers.d is 750 root:root so the rule itself "
f"(that's expected — /etc/sudoers.d is 750 root:root)", "is checked by: sudo -n -l | grep thermal-mode-apply")
f"verify as root: sudo test -e /etc/sudoers.d/perf-audio && echo ok. "
f"If missing: {install_cmd}")
else: else:
add("INSTALL SURFACE", "perf-audio + sudoers", FAIL, add("INSTALL SURFACE", "thermal-mode + sudoers", FAIL,
f"bin={bin_state}, sudoers={sudo_state} — perf tray can't switch " f"bin={bin_state}, sudoers={sudo_state} — the Bridge and tray can't "
f"modes without a password prompt", install_cmd) f"switch modes without a password prompt", install_cmd)
# CAP_DAC_OVERRIDE is capability number 1, so bit 1 of the capability bitmask. # CAP_DAC_OVERRIDE is capability number 1, so bit 1 of the capability bitmask.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment