Files
bf-infra/docs/tp6-call-run.sh
Christoph Emsenhuber 3aac2eeb8a TP6 Durchlauf 2 messbar machen: Sampler, gefuehrte Konsole, Treiber
Durchlauf 1 ist gemessen (Ergebnis folgt separat im Runbook). Fuer den Durchlauf
mit laufendem Videocall brauchte es Werkzeug, das es noch nicht gab:

- tp6-monitor-core.sh haengt EINE persistente SSH-Sitzung an den Core und laesst
  dort eine Console-Schleife laufen, die im 2-s-Takt CPU, WAN-Rate und die
  Registration-Table-Werte eines WLAN-Clients als CSV ausgibt. Nur Lesezugriffe.
  Unter CAPsMAN fuehrt der Manager die Tabelle, deshalb wird der Core abgefragt
  und nicht ap1/ap2. Zwei RouterOS-Fallen sind im Code kommentiert: die Console
  parst eine per SSH uebergebene Mehrzeilen-Eingabe zeilenweise, und in der
  Einzeiler-Form braucht jeder do={}-Block ein abschliessendes Semikolon.

- tp6-marker/server.py ist die Konsole fuer die Person am Telefon: Phasenkarte
  mit Klartext-Anweisung, Countdown, Fortschritt, Marker-Buttons und Not-Aus,
  bedienbar vom iPad. Die Buttons sind server-seitig gerenderte Formulare und
  die Anleitung aktualisiert sich notfalls per meta-refresh -- auf dem iPad ist
  das JavaScript ausgefallen, uebrig blieb nur der eine statische Button.

- tp6-call-run.sh fuehrt Durchlauf 2 mit denselben Messparametern wie Durchlauf 1
  (3 Flows x 430 KB/s, 24 Pings, 30 s Settle) und ergaenzt Ruhephasen davor und
  danach, Phasenmarken, eine PID-Datei fuer den Not-Aus und einen Heartbeat.
  Der Heartbeat ist kein Detail: ohne ihn geht der Start-Klick still ins Leere,
  der Call laeuft ohne Last, und "keine Probleme" liest sich wie ein Ergebnis,
  obwohl es eine Nicht-Messung ist -- genau so passiert, 5,5 Minuten lang.

- tp6-auswertung.py schneidet die Sampler-CSV an den Phasenmarken auf und meldet
  neben min/avg/max auch die Ereignisse, die im Mittelwert untergehen: Roaming,
  Re-Assoc und Verschwinden des Clients.

- tp6-bufferbloat-messung.sh schickt jetzt User-Agent und Referer mit. Ohne die
  beantwortet speed.cloudflare.com /__down mit 403 und einem Byte: die Flows
  sind sofort tot, die Ping-Reihe sieht sauber aus, und die Download-Seite gilt
  faelschlich als bufferbloat-frei. Aufgefallen ist es nur, weil der Sampler am
  Core 164 kbit statt 50 Mbit zeigte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:51:11 +02:00

157 lines
7.1 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# TP6 — Durchlauf 2: dieselbe Saettigung wie tp6-bufferbloat-messung.sh, aber mit laufendem
# Videocall, mit Phasenmarken auf derselben Zeitachse wie Sampler + Konsole — und mit einer
# Anleitung, die waehrend des Calls auf der Konsole steht.
#
# Messparameter BEWUSST identisch mit Durchlauf 1 (3 Flows x 430 KB/s, 24 Pings, 30 s Settle).
#
# 🔴 Der Treiber schreibt phase.json im 5-Sekunden-Takt. Die Konsole erkennt daran, dass
# ueberhaupt gemessen wird — ohne das geht ein Start-Klick still ins Leere, der Call
# laeuft ohne Last, und "keine Probleme" liest sich wie ein Ergebnis, obwohl es eine
# Nicht-Messung ist. (Am 2026-08-20 genau so passiert: 5,5 Minuten Call, nichts gemessen.)
#
# Misst nur. Aendert nichts am Netz.
set -uo pipefail
RUN_DIR="${TP6_RUN_DIR:-/private/tmp/claude-501/-Users-christophemsenhuber-dev-bf-network-r6/0d86ebd1-035f-4513-a231-5cca8111ecfd/scratchpad/tp6-run2}"
mkdir -p "$RUN_DIR"
MARKERS="$RUN_DIR/markers.csv"
PIDFILE="$RUN_DIR/flows.pid"
STATE="$RUN_DIR/phase.json"
GO_FLAG="$RUN_DIR/go.flag"
ABORT_FLAG="$RUN_DIR/abort.flag"
TARGET="1.1.1.1"
PINGS=24
SETTLE=30
UPLOAD_RATE_KB=430
C0=90
C1=60
STEPS=8
UPLOAD_FILE="$(mktemp -t tp6-upload).bin"
CF_UA="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
CF_REF="https://speed.cloudflare.com/"
[[ -f "$MARKERS" ]] || echo "ts_epoch,ts_local,marker" > "$MARKERS"
MODE="wartet"; PHASE="—"; TITLE=""; INST=""; STEP=0; ENDS=0
# Zustand fuer die Konsole. Wird zusaetzlich vom Heartbeat im 5-s-Takt neu geschrieben,
# damit die Konsole einen toten Treiber erkennt.
put_state() {
# 🔴 Atomar schreiben (tmp + mv): die Konsole liest die Datei im Sekundentakt und
# bekaeme sonst halb geschriebenes JSON zu sehen.
python3 - "$STATE" "$MODE" "$PHASE" "$TITLE" "$INST" "$STEP" "$STEPS" "$ENDS" <<'PY'
import json, os, sys
p, mode, phase, title, inst, step, steps, ends = sys.argv[1:9]
tmp = p + ".tmp"
with open(tmp, "w") as fh:
json.dump({"mode": mode, "phase": phase, "title": title, "instruction": inst,
"step": int(step), "steps": int(steps), "ends": int(ends)}, fh)
os.replace(tmp, p)
PY
}
mark() { printf '%s,%s,%s\n' "$(date +%s)" "$(date +%H:%M:%S)" "$1" >> "$MARKERS"; }
phase() { # phase <name> <dauer_s> <titel> <anweisung>
PHASE="$1"; local dur="$2"; TITLE="$3"; INST="$4"
STEP=$((STEP + 1)); ENDS=$(( $(date +%s) + dur )); MODE="messung"
put_state; mark "PHASE_$PHASE"
printf '\n\033[1;36m== %s ==\033[0m %s\n' "$PHASE" "$(date +%H:%M:%S)"
}
aborted() { [[ -f "$ABORT_FLAG" ]]; }
cleanup_flows() {
if [[ -f "$PIDFILE" ]]; then
while read -r p; do [[ "$p" =~ ^[0-9]+$ ]] && kill "$p" 2>/dev/null; done < "$PIDFILE"
: > "$PIDFILE"
fi
}
# 🔴 Der Heartbeat aktualisiert NUR die mtime. Er darf den Zustand nicht schreiben: eine
# Subshell sieht die Variablen vom Fork-Zeitpunkt und wuerde die Anzeige dauerhaft auf
# den Anfangszustand zuruecksetzen (und sich mit dem Hauptprozess ueberschreiben).
( while true; do touch "$STATE" 2>/dev/null; sleep 5; done ) & HEARTBEAT=$!
trap 'kill $HEARTBEAT 2>/dev/null; cleanup_flows; rm -f "$UPLOAD_FILE"' EXIT
# --- warten auf den Start-Klick -------------------------------------------------------
rm -f "$GO_FLAG" "$ABORT_FLAG"; : > "$PIDFILE"
MODE="wartet"; PHASE="BEREIT"; STEP=0; ENDS=0
TITLE="Warte auf den Start-Klick"
INST="Sobald der Call steht: <b>📞 Call verbunden</b> drücken. Vorher nichts weiter tun — Telefon in die Halterung, keine Downloads."
put_state
echo "Warte auf den Start-Klick …"
while [[ ! -f "$GO_FLAG" ]]; do sleep 0.5; done
mkfile 200M "$UPLOAD_FILE" 2>/dev/null || dd if=/dev/zero of="$UPLOAD_FILE" bs=1m count=200 2>/dev/null
# --- C0: Call ohne Last ---------------------------------------------------------------
phase "C0" $((C0 + 25)) "Baseline — Call ohne Last" \
"Einfach reden. <b>Nichts</b> tun: Telefon nicht bewegen, nichts herunterladen, Kamera anlassen. Das ist der Vergleichsmaßstab für alles Folgende."
ping -c "$PINGS" "$TARGET" > "$RUN_DIR/ping_C0.txt" 2>&1
sleep "$C0"
# --- U: Upload-Saettigung -------------------------------------------------------------
phase "U-settle" "$SETTLE" "Upload-Last baut sich auf" \
"Ab jetzt ist Stocken <b>erwartbar</b> — und zwar eher bei <b>Geli</b>: sie hört/sieht dich schlechter. Wenn sie etwas meldet: 🟠 drücken. Nicht eingreifen."
for _ in 1 2 3; do
curl -s -o /dev/null -A "$CF_UA" -e "$CF_REF" --limit-rate "${UPLOAD_RATE_KB}K" \
--data-binary @"$UPLOAD_FILE" "https://speed.cloudflare.com/__up" &
echo $! >> "$PIDFILE"
done
sleep "$SETTLE"
if aborted; then
mark "U_ABGEBROCHEN_waehrend_settle"
else
phase "U-ping" $((PINGS + 2)) "Upload-Last + Messung läuft" \
"Die eigentliche Messung. Weiterreden, Probleme klicken (🟠 Geli / 🔴 du / 🎬 Versatz). Nur wenn der Call abzubrechen droht: <b>⛔</b>."
ping -c "$PINGS" "$TARGET" > "$RUN_DIR/ping_U.txt" 2>&1
aborted && mark "U_ABGEBROCHEN_waehrend_ping"
fi
cleanup_flows
phase "U-recovery" 30 "Last ist weg — erholt sich der Call?" \
"Genau hinhören: wird es <b>sofort</b> wieder gut? Dann 🟢 drücken. Das ist der Beweis, dass die Last die Ursache war."
sleep 30
rm -f "$ABORT_FLAG"
# --- D: Download-Saettigung -----------------------------------------------------------
phase "D-settle" "$SETTLE" "Download-Last baut sich auf" \
"Jetzt andersherum: eher <b>du</b> hörst/siehst schlechter. Bei Problemen 🔴 drücken."
: > "$PIDFILE"
for _ in 1 2 3; do
curl -s -o /dev/null -A "$CF_UA" -e "$CF_REF" "https://speed.cloudflare.com/__down?bytes=2000000000" &
echo $! >> "$PIDFILE"
done
sleep "$SETTLE"
if aborted; then
mark "D_ABGEBROCHEN_waehrend_settle"
else
phase "D-ping" $((PINGS + 2)) "Download-Last + Messung läuft" \
"Letzte Lastphase. Weiterreden, Probleme klicken. Not-Aus nur, wenn es kippt."
ping -c "$PINGS" "$TARGET" > "$RUN_DIR/ping_D.txt" 2>&1
aborted && mark "D_ABGEBROCHEN_waehrend_ping"
fi
cleanup_flows
rm -f "$ABORT_FLAG"
# --- C1: Ruhiger Abschluss ------------------------------------------------------------
phase "C1" $((C1 + 25)) "Abschluss — Call ohne Last" \
"Last ist weg. Noch eine Minute ruhig reden, damit der Vergleich zum Anfang sauber ist. Danach ist die Messung durch."
ping -c "$PINGS" "$TARGET" > "$RUN_DIR/ping_C1.txt" 2>&1
sleep "$C1"
# --- Langzeit-Modus: Aufzeichnung laeuft weiter, Messung ist fertig --------------------
MODE="langzeit"; PHASE="LANGZEIT"; STEP=$STEPS; ENDS=0
TITLE="Messung abgeschlossen — Aufzeichnung läuft weiter"
INST="Die TP6-Zahlen sind im Kasten. Ab jetzt wird nur noch dein WLAN-Link mitgeschrieben, beliebig lange.<br><br><b>Roaming-Test, wenn du magst:</b> mit laufendem Call <b>🚶</b> drücken, langsam in den entferntesten Raum gehen, dort <b>📍</b> drücken und ~60 s stehen bleiben, dann zurück (wieder 🚶 / 📍). Alles, was dabei stockt oder asynchron wird, bitte klicken — dann sehe ich, ob genau dann der AP gewechselt hat.<br><br>Wenn Schluss ist: <b>📴</b>."
put_state; mark "PHASE_LANGZEIT"
echo
echo "Messung fertig. Langzeit-Aufzeichnung laeuft — Treiber laeuft weiter (Ctrl-C beendet ihn)."
while true; do sleep 5; done