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>
157 lines
7.1 KiB
Bash
Executable File
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
|