#!/bin/bash # Sampler for the digest-vs-transcription contention measurement (the "named # test" in STATE.md). Writes one CSV row every 2 s. # # It samples the SAME TWO SIGNALS `controller/digestYield.ts` reads, through the # endpoints that expose them — `GET /api/workers` (built from the same # `summary()` call, so `busy` and `device` are exactly what the yield sees) and # `GET /api/jobs/active` — beside the card's own view. That is what lets a run be # declared VOID rather than quietly believed: if `gpu_worker_busy` was 0 # throughout, the cell measured nothing. # # It also samples the two things that make such a run LIE: # - swap_used_kb / mem_avail_kb / psi_mem_full_avg10 — a run that swaps # measures the swap. This is not hypothetical; see # aborted-run-2026-07-30.csv, where swap went 9.6 -> 18.4 GB of 19 and the # editor died mid-request. # - temp_c / power_w / sclk_hz — an RX 6600 throttles over 2-3 GPU-hours, and # a throttled second cell would look like a regression in the first. # # Usage: ./sample-contention.sh out.csv (Ctrl-C to stop; assumes card1) OUT="$1"; CARD=/sys/class/drm/card1/device; HW=$CARD/hwmon/hwmon2 echo "ts,gpu_busy_pct,vram_used,temp_c,power_w,sclk_hz,mem_avail_kb,swap_used_kb,psi_mem_full_avg10,loadavg1,ollama_models,gpu_worker_busy,cpu_workers_busy,active_jobs,digest_jobs" > "$OUT" while true; do ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) gpu=$(cat $CARD/gpu_busy_percent 2>/dev/null||echo -1) vram=$(cat $CARD/mem_info_vram_used 2>/dev/null||echo -1) temp=$(( $(cat $HW/temp1_input 2>/dev/null||echo 0) / 1000 )) pw=$(( $(cat $HW/power1_average 2>/dev/null||echo 0) / 1000000 )) sclk=$(cat $HW/freq1_input 2>/dev/null||echo 0) memav=$(awk '/MemAvailable/{print $2}' /proc/meminfo) swt=$(awk '/SwapTotal/{print $2}' /proc/meminfo); swf=$(awk '/SwapFree/{print $2}' /proc/meminfo) swu=$((swt-swf)) psi=$(awk -F'[= ]' '/^full/{print $3}' /proc/pressure/memory) la=$(awk '{print $1}' /proc/loadavg) om=$(curl -s --max-time 2 localhost:11434/api/ps | jq -r '[.models[]?.name]|join(";")' 2>/dev/null) w=$(curl -s --max-time 8 localhost:3001/api/workers) gb=$(echo "$w" | jq -r '[.workers[]?|select(.kind=="local" and ((.device // "")|ascii_downcase)!="cpu")|select(.busy)]|length' 2>/dev/null||echo -1) cb=$(echo "$w" | jq -r '[.workers[]?|select(.kind=="local" and ((.device // "")|ascii_downcase)=="cpu")|select(.busy)]|length' 2>/dev/null||echo -1) aj=$(curl -s --max-time 8 localhost:3001/api/jobs/active) n=$(echo "$aj" | jq -r '[.jobs[]?|select(.status=="running")]|length' 2>/dev/null||echo -1) dj=$(echo "$aj" | jq -r '[.jobs[]?|select(.status=="running" and (.kind|test("digest")))]|length' 2>/dev/null||echo -1) echo "$ts,$gpu,$vram,$temp,$pw,$sclk,$memav,$swu,$psi,$la,$om,$gb,$cb,$n,$dj" >> "$OUT" sleep 2 done