Commit d81b9a9f by PLN (Algolia)

perf: the cap is thermal-pilot's rung-1 floor, and gig-up now reads knobs not wishes

Proven with the rig's own hand at 01:12, after being wrong about it twice.
thermal-mode-apply performance wrote cap=100 and EPP=balance_performance; six
seconds later thermal-pilot logged 'drift: live knobs no longer match rung 1
-- reasserted' and put back 65 and balance_power. thermald was already STOPPED
at the time and the cap still moved, so thermald is innocent and I owe it a
restart -- the box has had no thermal daemon since 00:40 on my bad call.

It is not a leftover, it is the design: rung_knobs 1 is '0
balance_power' with RUNG1_PCT defaulting to 65, and profile_params performance
is '1 2 2600' -- floor rung 1, ceiling rung 2, noise brake at 2600 rpm. The
fan idles near 2800, so the brake is always armed: six climbs to rung 2
tonight, six brakes back within five seconds. Under this profile 65% is the
floor and no hand-written value survives it, which means the mangled paste
cost nothing.

Suspected upstream cause, and it may close for a cable: the helper reported
PL1=25W PL2=50W on a '45W USB-C PD source' while thermal-mode.conf records
STOCK PL1=45W/PL2=90W. Half the power budget means hotter for the same work,
which is what keeps the fan over the brake. Test the Dell barrel charger
first.

ensure_perf used to print 'thermal-mode performance holding' sourced from
MODE= in the conf file, while the gate twelve lines below in the same log
FAILED on max_perf_pct=65. Both were correct -- the file records the last
intent, not the live posture. It now asserts the gear, waits out the pilot's
reassert, reads the four knobs, and either says VERIFIED or names the owner
and the reason. No sudo line to paste and no memory to rely on, which is what
was asked for.

tools/fix-ardour-rt.sh closes the Ardour item as a script rather than
instructions: dry-run default, --apply, --revert, idempotent, backs up, and it
patches BOTH writers because perf-audio:565/573 re-arms what parvagues-protect
stops. Verified against the live root files (all four patterns match exactly
once) and applied to copies first: both still parse, the prio>0 guard is in,
and scsynth/sclang/pipewire priorities are untouched.
parent 2ecba716
...@@ -763,3 +763,116 @@ records. Bound once at 10:38 and deaf for fourteen hours. ...@@ -763,3 +763,116 @@ records. Bound once at 10:38 and deaf for fourteen hours.
**Verify by the records, never by the header**: `"midi": true` has been **Verify by the records, never by the header**: `"midi": true` has been
lying for ten sessions because `available()` only asks whether aseqdump is lying for ten sessions because `available()` only asks whether aseqdump is
installed. installed.
## Thu 24, 01:20 — the cap is thermal-pilot's rung-1 FLOOR, proven by the rig's own hand
I told PLN to stop `thermald` and lift the cap by hand. **Both halves of that were
wrong, and the correction matters more than the advice.**
**thermald is innocent.** It is `--adaptive` and its
`/etc/thermald/thermal-cpu-cdev-order.xml` does list `intel_pstate`, which is why
it looked guilty. But it was **stopped** at 00:40 and `max_perf_pct` stayed at 65
for the next forty minutes. A writer that is not running is not the writer.
**Restart it** (`sudo systemctl start thermald`) — the box has been sitting with
no thermal daemon since 00:40 for no reason, and that one is on me.
**The writer is `thermal-pilot.service`**, the rig's own ladder, and the proof is
its own log. At 01:12, using the sanctioned NOPASSWD helper:
```
BEFORE cap=65 epp=balance_power
sudo -n thermal-mode-apply performance -> cap=100 epp=balance_performance
t+6s thermal-pilot: drift: live knobs no longer match rung 1 -- reasserted
AFTER cap=65 epp=balance_power
```
**Six seconds.** So the mangled paste cost nothing: `echo 100 | sudo tee` would
have been undone before the prompt came back.
**And it is not a leftover to clear — it is the design.** From the sources:
- `thermal-pilot`: `rung_knobs 1` = `"0 $RUNG1_PCT balance_power"`, and
`RUNG1_PCT=${RUNG1_PCT:-65}`. **65 is rung 1's definition.**
- `thermal-pilot`: `profile_params performance` = `"1 2 ${PERF_FAN_SOFT:-2600}"` —
floor **rung 1**, ceiling rung 2, noise brake **2600 rpm**. Its own header says
it plainly: *"performance: floor of capped turbo, braked at 2600 rpm"*.
- The fan idles near **2800 rpm**, so the brake is always armed. Tonight the pilot
climbed `1 -> 2` **six times** and was braked back within 5 s every single time:
`rung 2 -> 1 (noise brake: fan 2802 rpm > 2600 for 30s)`.
Under `MODE=performance`, **65 % is the best this box will hold while the fan sits
above 2600 rpm.** No hand-written value survives it.
### The upstream cause is probably the CHARGER, and TODO already said so
`thermal-mode-apply performance` reported `PL1=25W PL2=50W … budget: 45W USB-C PD
source`, while `/etc/thermal-mode.conf` records `STOCK_intel_rapl_0_PL1=45000000`
(45 W) and `PL2=90000000`. So the box is on the **USB-C travel charger**, running
at roughly half its stock power budget. A halved PL1 means the same work is done
slower and hotter, the fan climbs past 2600, the noise brake fires, and the pilot
pins rung 1. *"Bring the proper Dell charger"* was already on the 5pm list as a
measurement-hygiene item; it is now a suspected **cause** of the cap.
**Test it first thing, because it may cost nothing:** plug the Dell barrel
charger, `sudo -n /usr/local/sbin/thermal-mode-apply performance`, wait 30 s, read
`max_perf_pct`. If PL1 reads 45 W and the fan settles under 2600, the cap holds by
itself and the whole item closes with a cable.
### If the charger is not enough, it is a choice — and it is PLN's, not mine
I have been wrong about this writer twice tonight. So: the options, with the
trade, and no pick.
- **(a) Raise the brake.** `PERF_FAN_SOFT` is an env default in `thermal-pilot`,
so a root drop-in on `thermal-pilot.service` with
`Environment=PERF_FAN_SOFT=3400` lets rung 2 hold. Cost: a louder laptop. At a
gig with a PA, fan noise is free — this is the cheapest real fix.
- **(b) Raise the floor.** A drop-in with `Environment=RUNG1_PCT=100` makes rung 1
harmless. Cost: the ladder loses its middle step everywhere, including on
battery at a café. Blunter than (a).
- **(c) Stop the pilot for the set.** Its exit trap writes rung 0, which is
`"1 100 power"` — **cap 100 but TURBO OFF**, which is not what a set wants.
Read the table before reaching for this one; it is not "uncapped".
- **(d) Leave it at 65 %.** Measured tonight: with Ardour's GUI threads demoted,
xruns went to **0 in 14 minutes** *at 65 %*. The cap may simply not be the
binding constraint once the RT bug is gone. Cheapest of all, if the rehearsal
agrees.
`gig-up.sh` no longer has an opinion here, but it now tells the truth (see below).
### Landed tonight, no root needed
- [x] **`gig-up.sh ensure_perf` reads the KNOBS, never the mode file.** It used to
print *"thermal-mode performance holding"* from `MODE=` in
`/etc/thermal-mode.conf` while the gate, twelve lines later in the same log,
FAILED on `max_perf_pct=65`. Both were right: the file records the last
intent, not the live posture. It now asserts the gear, sleeps 8 s so the
pilot has reasserted, reads `max_perf_pct` / EPP / `no_turbo` / PL1, and
either says **VERIFIED** or names the owner — *"owner = thermal-pilot
(profile 'performance'), and it reasserts within ~6s"* — plus the PL1-vs-45W
charger hint. **No sudo line to paste, and no memory to rely on.**
- [x] **`tools/fix-ardour-rt.sh`** — the #1 blocker, as a script instead of
instructions. Dry-run by default, `--apply` to write, `--revert` to undo,
idempotent, backs both files up, refuses any file whose expected text it
cannot find, and `bash -n`s the result before restarting anything. It patches
**both** writers (`parvagues-protect`'s `ardour:80` -> `ardour:0` plus the
`prio > 0` guard that a 0 requires, and `perf-audio`'s two Ardour `chrt`
calls at ~565/573), restarts `parvagues-protect`, restarts `thermald`, and
prints the acceptance test. Ardour keeps its OOM shield, its ionice and its
memlock; scsynth/90, sclang/85 and pipewire/95 are untouched.
**Verified before you run it:** all four patch patterns match the live root
files exactly once, and the patch was applied to *copies* — both files still
`bash -n` clean, the guard is in place, and nothing else moved.
### Thursday morning, in order
1. `sudo systemctl start thermald` — my mistake, 30 seconds.
2. `sudo tools/fix-ardour-rt.sh` (read the diff) then `--apply`. Then the
acceptance test it prints: **launch Ardour from the DESKTOP ICON**, because a
terminal or Bridge launch inherits `unlimited` and proves nothing.
3. **`sudo systemctl start parvagues-protect`** — it is OFF right now, on purpose,
which means scsynth currently has **no OOM shield and no FIFO/90**. Step 2 is
what makes it safe to turn back on.
4. Plug the **Dell barrel charger**, re-assert the gear, read the cap. Decide (a)
or (d) above with the rehearsal's ears.
5. `systemctl --user start midiviz` if you want the lens.
...@@ -107,17 +107,60 @@ ensure_perf(){ ...@@ -107,17 +107,60 @@ ensure_perf(){
# The gig posture is thermal-mode `performance`: turbo on, governor POWERSAVE + # The gig posture is thermal-mode `performance`: turbo on, governor POWERSAVE +
# EPP balance_performance, RT audio. NOT governor=performance: that pins EPP and # EPP balance_performance, RT audio. NOT governor=performance: that pins EPP and
# runs every idle wakeup at ~4-5 GHz (package 96 C at 5 % load, 2026-09-23). # runs every idle wakeup at ~4-5 GHz (package 96 C at 5 % load, 2026-09-23).
local mode noturbo # READ THE KNOBS, NEVER THE MODE FILE.
mode=$(sed -n 's/^MODE=//p' /etc/thermal-mode.conf 2>/dev/null) #
# 2026-09-24, 00:00: this function printed "thermal-mode performance holding —
# turbo headroom for MIDI bursts" while the gate, twelve lines later in the
# same log, read `max_perf_pct=65` and FAILED on it. Both were correct.
# `/etc/thermal-mode.conf` says `MODE=performance` because the mode WAS
# applied; it records the last intent, not the live posture, and no file can
# know what wrote the sysfs node afterwards. So a green line sourced from that
# file is a green line about a wish.
#
# And something does write it afterwards, every time. Measured with the rig's
# own hand at 01:12: `thermal-mode-apply performance` set cap 100 / EPP
# balance_performance, and SIX SECONDS later `thermal-pilot` logged "drift:
# live knobs no longer match rung 1 -- reasserted" and put back 65 /
# balance_power. Not thermald (stopped at the time, and the cap still moved) —
# the rig's OWN ladder. `thermal-pilot`'s `performance` profile is
# "floor 1, ceiling 2, brake 2600rpm", rung 1 IS `RUNG1_PCT=65`, and the fan
# idles near 2800, so the noise brake yanks every climb back within seconds.
# Under this profile 65% is not a leftover to clear: it is the floor, by
# design, and no amount of `echo 100 | sudo tee` survives it.
#
# Therefore: assert the mode (idempotent, and the only sanctioned writer),
# then LOOK, then say what is true and name who owns it. A launcher that
# cannot fix something must at least never claim it did.
local pct epp noturbo pl1 pilot rung
info "perf: asserting the gig gear, then verifying the live knobs…"
PYTHONPATH="$DIR/tools/bridge" python3 -c \
"import perf,sys; ok,m=perf.set_mode('performance'); print(m); sys.exit(0 if ok else 1)" \
>/dev/null 2>&1 \
|| warn "perf: could not assert the gear — pick Performance in the Thermal panel yourself."
# The pilot reasserts within ~6s, so settle before reading or we grade the
# write instead of the posture.
sleep 8
pct=$(cat /sys/devices/system/cpu/intel_pstate/max_perf_pct 2>/dev/null || echo '?')
epp=$(cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference 2>/dev/null || echo '?')
noturbo=$(cat /sys/devices/system/cpu/intel_pstate/no_turbo 2>/dev/null || echo 1) noturbo=$(cat /sys/devices/system/cpu/intel_pstate/no_turbo 2>/dev/null || echo 1)
if [ "$mode" = performance ] && [ "$noturbo" = 0 ]; then pl1=$(awk '{printf "%dW", $1/1000000}' /sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw 2>/dev/null || echo '?')
ok "perf: thermal-mode performance holding — turbo headroom for MIDI bursts."; return pilot=$(systemctl is-active thermal-pilot.service 2>/dev/null)
fi rung=$(cat /run/thermal-pilot.policy 2>/dev/null)
info "perf: thermal-mode is '${mode:-unset}' → switching to performance for the set…"
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 [ "$pct" = 100 ] && [ "$noturbo" = 0 ]; then
ok "perf: performance set (thermal-mode owns it; resume/charger reassert included). Back to quiet after: Thermal panel → Silent." ok "perf: VERIFIED — clock uncapped (100%), turbo on, EPP $epp, PL1 $pl1."
else
warn "perf: CAP IS LIVE — max_perf_pct=${pct}%, EPP=$epp, turbo=$([ "$noturbo" = 0 ] && echo on || echo OFF), PL1 $pl1."
if [ "$pilot" = active ]; then
warn "perf: owner = thermal-pilot (profile '${rung:-?}'), and it reasserts within ~6s."
warn "perf: its 'performance' profile FLOORS at rung 1 = 65% whenever the fan"
warn "perf: passes 2600 rpm, which it does at idle. Writing the node by hand"
warn "perf: cannot win this; the profile has to change, or the box has to run"
warn "perf: cooler. PL1 $pl1 vs 45W stock usually means the USB-C travel"
warn "perf: charger — the Dell barrel raises the budget and lowers the fan."
else else
warn "perf: auto-set failed — pick Performance in the Thermal panel yourself." warn "perf: thermal-pilot is not running, so something ELSE holds the cap. Investigate before the set."
fi
fi fi
} }
......
#!/usr/bin/env bash
# fix-ardour-rt — stop promoting Ardour's GUI thread to a realtime policy.
#
# WHAT THIS IS FOR
# ----------------
# 2026-09-24, 23:59: a desktop-icon launch brought Ardour up and the shell
# reported it `Killed` ten seconds later, while gig-up printed "gig-up done —
# Ardour + Pulsar are coming up" three lines further down. Two seconds before
# the death, parvagues-protect had logged:
#
# protected: ardour[540676] oom:100->-1000 sched:SCHED_OTHER/0->FIFO/80
#
# Ardour's AUDIO thread already gets realtime the right way, from the backend:
# `AudioEngine 1` runs SCHED_FIFO|SCHED_RESET_ON_FORK at 83, ABOVE the GUI,
# granted by PipeWire, needing nothing from us. So promoting the GUI thread buys
# no protection for anything that makes sound, and it costs two things:
#
# 1. THE KILL. RLIMIT_RTTIME is INHERITED, and it differs by who launched you:
# gnome-shell runs at 200000us, a systemd user unit at unlimited (measured
# both). The desktop icon is a gnome-shell descendant, so Ardour from the
# icon carries a 200ms realtime budget — and a GUI thread loading a 355KB
# session with plugins blows that easily. The kernel then sends SIGXCPU and,
# soft == hard, SIGKILL, WITH NO KERNEL LOG LINE. That is why the journal
# looked clean. The Ardour opened by hand that night had `unlimited` and
# lived.
# 2. XRUNS. `sched_rt_runtime_us` is at the default 950000/1000000, so once the
# realtime tasks on a runqueue pass 95% of a period the kernel throttles
# EVERY RT task on it for the remaining 50ms — 2.3 quanta at 1024/48k. A GUI
# thread eating 40-85% of a core at FIFO/80 is how AudioEngine at 83 gets
# starved by something BELOW it. gig-log had already recorded the evidence
# and nobody had read it by name: xrun_by.ardour was 5172 (Sep 10), 4852
# (Sep 18), 1114 (Sep 20), 987 (Sep 22).
#
# TWO WRITERS, NOT ONE
# --------------------
# Fixing parvagues-protect alone is not enough, and that is the trap. perf-audio
# does the same `chrt -f -p 80 $ARDOUR` at line ~565, plus ~573 for every child
# at 79, and perf-watch reasserts it by itself on any drift — the Bridge journal
# caught it doing exactly that at 09:20:15 on Sep 23. An acceptance test that
# only exercises the protect edit passes at home and fails at the venue.
#
# WHAT IT CHANGES
# ---------------
# /usr/local/bin/parvagues-protect
# - protect_pid gains one guard: prio 0 = OOM shield only, no chrt. Without
# it, a prio of 0 makes `chrt -f -p 0` fail (FIFO wants 1-99) and log
# "sched:FAILED" every 2 seconds forever.
# - the ardour TARGET's priority 80 -> 0. Ardour KEEPS its oom_score_adj
# -1000 and its unlimited memlock. It only loses the promotion it never
# needed.
# /usr/local/sbin/perf-audio
# - the two Ardour `chrt -f` calls become no-ops, with the reason inline.
# ionice, oom_adj and memlock for Ardour are left exactly as they were.
#
# Everything else on both files is untouched. scsynth (90) and sclang (85) keep
# their promotions: they ARE the sound.
#
# HOW TO RUN IT
# sudo tools/fix-ardour-rt.sh # dry run: prints the diff, changes nothing
# sudo tools/fix-ardour-rt.sh --apply # writes, backs up, restarts, verifies
# sudo tools/fix-ardour-rt.sh --revert # puts back the newest backup of each
#
# It is idempotent: a second --apply reports "already applied" and exits 0. It
# refuses to touch a file whose expected text it cannot find, and says what it
# found instead, because a sed that silently matches nothing is how you get a
# root file you believe you edited.
set -uo pipefail
PROTECT=/usr/local/bin/parvagues-protect
PERFAUD=/usr/local/sbin/perf-audio
STAMP=$(date +%Y%m%d-%H%M%S)
MODE=dry
C_OK=$'\e[32m'; C_WARN=$'\e[33m'; C_ERR=$'\e[31m'; C_DIM=$'\e[2m'; C_RST=$'\e[0m'
ok(){ printf '%s✓ %s%s\n' "$C_OK" "$*" "$C_RST"; }
warn(){ printf '%s! %s%s\n' "$C_WARN" "$*" "$C_RST"; }
err(){ printf '%s✗ %s%s\n' "$C_ERR" "$*" "$C_RST"; }
dim(){ printf '%s%s%s\n' "$C_DIM" "$*" "$C_RST"; }
while [ $# -gt 0 ]; do
case "$1" in
--apply) MODE=apply ;;
--revert) MODE=revert ;;
--dry-run) MODE=dry ;;
-h|--help) sed -n '1,80p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) err "unknown option: $1"; exit 2 ;;
esac
shift
done
[ "$(id -u)" = 0 ] || { err "must run as root (the two files are root-owned): sudo $0 $*"; exit 1; }
# ---------------------------------------------------------------- the edits ----
# Each edit is (file, what must be there, what it becomes). Both halves are
# EXACT text, so a file that has drifted fails loudly instead of half-applying.
PROTECT_OLD_TARGET=' "ardour:80:ardour|ardour[0-9]+|ArdourGUI|ardour-[0-9.]+"'
PROTECT_NEW_TARGET=' # 0 = OOM shield only, NO realtime promotion. Ardour'"'"'s AudioEngine thread
# already gets FIFO/83 from the backend; promoting the GUI thread to FIFO/80
# killed Ardour outright via RLIMIT_RTTIME on desktop-icon launches (200ms,
# inherited from gnome-shell) and starved the audio thread via RT bandwidth
# throttling. See tools/fix-ardour-rt.sh in the Tidal repo for the full
# measurement. 2026-09-24.
"ardour:0:ardour|ardour[0-9]+|ArdourGUI|ardour-[0-9.]+"'
PROTECT_OLD_GUARD=' sched_of "$pid" || return 0
if [ "$S_POL" != "SCHED_FIFO" ] || [ "${S_PRIO:-0}" -lt "$prio" ]; then
if chrt -f -p "$prio" "$pid" 2>/dev/null; then
out+=" sched:${S_POL:-?}/${S_PRIO:-?}->FIFO/${prio}"
else
out+=" sched:FAILED"
fi
fi'
PROTECT_NEW_GUARD=' # A prio of 0 means "shield it from the OOM killer, but never change its
# scheduling policy". Needed as a real branch and not just a 0 in the table:
# `chrt -f -p 0` is invalid (FIFO wants 1-99), so without this the sweep would
# log "sched:FAILED" every 2 seconds, forever, about a thing it meant to skip.
if [ "${prio:-0}" -gt 0 ] && sched_of "$pid"; then
if [ "$S_POL" != "SCHED_FIFO" ] || [ "${S_PRIO:-0}" -lt "$prio" ]; then
if chrt -f -p "$prio" "$pid" 2>/dev/null; then
out+=" sched:${S_POL:-?}/${S_PRIO:-?}->FIFO/${prio}"
else
out+=" sched:FAILED"
fi
fi
fi'
PERF_OLD_MAIN=' chrt -f -p 80 $ARDOUR'
PERF_NEW_MAIN=' # NOT chrt. Ardour'"'"'s AudioEngine thread already runs FIFO/83 from the
# backend; putting its GUI thread on a realtime policy killed Ardour on
# desktop-icon launches (RLIMIT_RTTIME 200ms, inherited) and caused xruns via
# RT bandwidth throttling. 2026-09-24. (Was: chrt -f -p 80 $ARDOUR)
:'
PERF_OLD_CHILD=' chrt -f -p 79 $child'
PERF_NEW_CHILD=' # NOT chrt, same reason as the parent above. 2026-09-24.
: # (was: chrt -f -p 79 $child)'
# ------------------------------------------------------------------ helpers ----
applied_already() {
grep -qF '"ardour:0:' "$PROTECT" 2>/dev/null \
&& ! grep -qE '^\s*chrt -f -p 80 \$ARDOUR\s*$' "$PERFAUD" 2>/dev/null
}
# patch_file FILE then pairs of OLD NEW on stdin-free args
patch_one() {
local file="$1" old="$2" new="$3" label="$4"
if ! grep -qF -- "$old" "$file"; then
if grep -qF -- "${new##*$'\n'}" "$file"; then
dim " $label: already applied"
return 0
fi
err " $label: expected text NOT FOUND in $file — refusing to edit."
err " the file has drifted from what this script was written against."
return 1
fi
if [ "$MODE" = dry ]; then
printf ' %s: would replace\n' "$label"
printf '%s' "$old" | sed 's/^/ - /'
echo
printf '%s' "$new" | sed 's/^/ + /'
echo
return 0
fi
python3 - "$file" "$old" "$new" <<'PY'
import sys, pathlib
f, old, new = pathlib.Path(sys.argv[1]), sys.argv[2], sys.argv[3]
t = f.read_text()
if t.count(old) != 1:
sys.exit(f"expected exactly one occurrence, found {t.count(old)}")
f.write_text(t.replace(old, new, 1))
PY
}
# --------------------------------------------------------------------- main ----
if [ "$MODE" = revert ]; then
rc=0
for f in "$PROTECT" "$PERFAUD"; do
b=$(ls -1t "$f".pre-ardour-rt-* 2>/dev/null | head -1)
if [ -n "$b" ]; then
cp -a "$b" "$f" && ok "reverted $f from $(basename "$b")"
else
warn "no backup found for $f"; rc=1
fi
done
systemctl restart parvagues-protect 2>/dev/null && ok "parvagues-protect restarted"
exit "$rc"
fi
for f in "$PROTECT" "$PERFAUD"; do
[ -f "$f" ] || { err "missing: $f"; exit 1; }
done
if applied_already; then
ok "already applied — nothing to do."
grep -n '"ardour:' "$PROTECT" | sed 's/^/ /'
exit 0
fi
echo
printf '%s\n' "── ${MODE^^}: stop promoting Ardour's GUI thread ──"
echo
if [ "$MODE" = apply ]; then
cp -a "$PROTECT" "$PROTECT.pre-ardour-rt-$STAMP" && ok "backed up $PROTECT -> .pre-ardour-rt-$STAMP"
cp -a "$PERFAUD" "$PERFAUD.pre-ardour-rt-$STAMP" && ok "backed up $PERFAUD -> .pre-ardour-rt-$STAMP"
echo
fi
echo " $PROTECT"
patch_one "$PROTECT" "$PROTECT_OLD_GUARD" "$PROTECT_NEW_GUARD" "prio-0 guard" || exit 1
patch_one "$PROTECT" "$PROTECT_OLD_TARGET" "$PROTECT_NEW_TARGET" "ardour 80 -> 0" || exit 1
echo
echo " $PERFAUD"
patch_one "$PERFAUD" "$PERF_OLD_MAIN" "$PERF_NEW_MAIN" "ardour chrt 80" || exit 1
patch_one "$PERFAUD" "$PERF_OLD_CHILD" "$PERF_NEW_CHILD" "ardour children chrt 79" || exit 1
echo
if [ "$MODE" = dry ]; then
warn "DRY RUN — nothing was written. Re-run with --apply when the diff looks right."
exit 0
fi
# A root file that no longer parses is worse than the bug it was fixing.
for f in "$PROTECT" "$PERFAUD"; do
if bash -n "$f" 2>/dev/null; then
ok "syntax OK: $f"
else
err "SYNTAX ERROR in $f — restoring the backup."
cp -a "$f.pre-ardour-rt-$STAMP" "$f"
exit 1
fi
done
echo
systemctl restart parvagues-protect 2>/dev/null \
&& ok "parvagues-protect restarted" \
|| warn "could not restart parvagues-protect — do it by hand."
# thermald was stopped on 2026-09-24 on a diagnosis that turned out WRONG: the
# CPU cap was thermal-pilot reasserting rung 1, proven at 01:12 with thermald
# already inactive. There is no reason for the box to sit with no thermal
# daemon, so put it back.
if ! systemctl is-active --quiet thermald; then
systemctl start thermald 2>/dev/null \
&& ok "thermald restarted (it was stopped on a wrong diagnosis — it never held the cap)" \
|| warn "could not start thermald."
fi
echo
printf '%s\n' "── VERIFY ──"
grep -n '"ardour:' "$PROTECT" | sed 's/^/ /'
apid=$(pgrep -x ArdourGUI 2>/dev/null | head -1)
if [ -n "$apid" ]; then
warn "Ardour is running from BEFORE the fix, so its threads keep whatever they were given."
warn "The real acceptance test needs a fresh launch FROM THE DESKTOP ICON — that is the"
warn "only path that inherits gnome-shell's 200ms RLIMIT_RTTIME and reproduces the kill."
else
ok "Ardour not running. Launch it from the DESKTOP ICON (not a terminal, not the"
ok "Bridge — those inherit 'unlimited' and prove nothing), then check:"
fi
cat <<'AC'
Acceptance test, in order:
1. launch Ardour from the ParVagues desktop icon (or the Ardour icon)
2. it is still alive 60s later
3. no thread of it is on a realtime policy except the backend's own:
for t in /proc/$(pgrep -x ArdourGUI)/task/*; do \
chrt -p $(basename $t) 2>/dev/null | grep -q FIFO && \
echo "$(cat $t/comm) $(chrt -p $(basename $t) | tail -1)"; done
EXPECT exactly: AudioEngine 1 ... priority 83 (and nothing at 80)
4. play for 5 minutes, then confirm xrun_by.ardour is flat:
python3 tools/gig-log.py report --since 5m # or read the newest .jsonl
AC
ok "done."
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