Files
bf-infra/docs/tp6-auswertung.py

150 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""
TP6 — Auswertung: Sampler-CSV + Marker-CSV -> Zahlen je Phase.
Ohne das ist die Sampler-CSV nur ein Haufen Zeilen. Schneidet die CSV an den
PHASE_*-Marken auf und rechnet je Phase min/avg/max fuer Core-CPU, WAN-Rate und —
sofern ein Client mitgesampelt wurde — Signal und Raten des WLAN-Clients.
Zusaetzlich werden die Ereignisse gemeldet, die im Mittelwert untergehen:
Roaming (Interface-Wechsel), Re-Assoc (Uptime-Ruecksprung), Verschwinden (absent)
und die Nutzer-Marker (ICH/GELI schlecht, Kamera, Not-Aus).
Aufruf:
python3 docs/tp6-auswertung.py --sampler <sampler.csv> [--markers <markers.csv>]
"""
import argparse
import csv
import sys
def num(v):
try:
return float(v)
except (TypeError, ValueError):
return None
def stats(vals):
vals = [v for v in vals if v is not None]
if not vals:
return None
return min(vals), sum(vals) / len(vals), max(vals)
def fmt_bps(v):
if v is None:
return "—"
return "%.1f Mbit" % (v / 1e6) if v >= 1e6 else "%.0f kbit" % (v / 1e3)
def fmt_rate(v):
return "—" if v is None else "%.0f Mbps" % (v / 1e6)
def hms(sec):
return "%02d:%02d" % (sec // 60, sec % 60)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--sampler", required=True)
ap.add_argument("--markers")
args = ap.parse_args()
rows = list(csv.DictReader(open(args.sampler)))
rows = [r for r in rows if (r.get("ts_epoch") or "").isdigit()]
if not rows:
sys.exit("Sampler-CSV enthaelt keine Datenzeilen.")
marks = []
if args.markers:
for m in csv.DictReader(open(args.markers)):
if (m.get("ts_epoch") or "").isdigit():
marks.append((int(m["ts_epoch"]), m["ts_local"], m["marker"]))
marks.sort()
# Phasenfenster aus den PHASE_*-Marken; ohne Marker die gesamte Aufzeichnung.
phases = [(ts, name[6:]) for ts, _, name in marks if name.startswith("PHASE_")]
t0, tEnd = int(rows[0]["ts_epoch"]), int(rows[-1]["ts_epoch"])
windows = []
if phases:
for i, (ts, name) in enumerate(phases):
end = phases[i + 1][0] if i + 1 < len(phases) else tEnd + 1
windows.append((name, ts, end))
else:
windows.append(("gesamte Aufzeichnung", t0, tEnd + 1))
has_sta = any((r.get("sta_signal") or "").strip() for r in rows)
print("Sampler: %d Zeilen, %s bis %s\n" % (len(rows), rows[0]["ts_local"], rows[-1]["ts_local"]))
for name, start, end in windows:
win = [r for r in rows if start <= int(r["ts_epoch"]) < end]
if not win:
continue
dur = int(win[-1]["ts_epoch"]) - int(win[0]["ts_epoch"])
print("── %s (%s, %d Samples, %s)" % (name, win[0]["ts_local"], len(win), hms(dur)))
cpu = stats([num(r["cpu"]) for r in win])
rx = stats([num(r["wan_rx_bps"]) for r in win])
tx = stats([num(r["wan_tx_bps"]) for r in win])
if cpu:
print(" Core-CPU min %.0f%% avg %.1f%% max %.0f%%" % cpu)
if rx:
print(" WAN rx avg %s max %s" % (fmt_bps(rx[1]), fmt_bps(rx[2])))
if tx:
print(" WAN tx avg %s max %s" % (fmt_bps(tx[1]), fmt_bps(tx[2])))
if has_sta:
absent = sum(1 for r in win if (r.get("sta_iface") or "") == "absent")
sig = stats([num(r["sta_signal"]) for r in win])
trate = stats([num(r["sta_tx_rate"]) for r in win])
rrate = stats([num(r["sta_rx_rate"]) for r in win])
stx = stats([num(r["sta_tx_bps"]) for r in win])
srx = stats([num(r["sta_rx_bps"]) for r in win])
ifaces = []
for r in win:
i = r.get("sta_iface") or ""
if i and (not ifaces or ifaces[-1] != i):
ifaces.append(i)
if sig:
print(" Client-Sig min %.0f avg %.1f max %.0f dBm" % sig)
if trate and rrate:
print(" Client-Rate tx min %s / avg %s rx min %s / avg %s"
% (fmt_rate(trate[0]), fmt_rate(trate[1]), fmt_rate(rrate[0]), fmt_rate(rrate[1])))
if stx and srx:
print(" Client-Last tx avg %s / max %s rx avg %s / max %s"
% (fmt_bps(stx[1]), fmt_bps(stx[2]), fmt_bps(srx[1]), fmt_bps(srx[2])))
print(" AP-Pfad %s%s" % (" -> ".join(ifaces) or "—",
" ⚠️ %d Samples ABSENT" % absent if absent else ""))
ereignisse = [(t, l, n) for t, l, n in marks if start <= t < end and not n.startswith("PHASE_")]
for _, lt, n in ereignisse:
print(" ⚑ %s %s" % (lt, n))
print()
# Re-Assoc erkennen: Uptime springt zurueck.
if has_sta:
reassoc = []
prev = None
for r in rows:
up = (r.get("sta_uptime") or "").strip()
if not up or up == "absent":
prev = None
continue
parts = [int(p) for p in up.split(":")] if up.count(":") == 2 else None
secs = parts[0] * 3600 + parts[1] * 60 + parts[2] if parts else None
if secs is not None and prev is not None and secs < prev:
reassoc.append((r["ts_local"], r.get("sta_iface", "")))
prev = secs
if reassoc:
print("⚠️ Re-Assoc / Neuverbindung des Clients:")
for lt, i in reassoc:
print(" %s auf %s" % (lt, i))
else:
print("Keine Re-Assoc des Clients im gesamten Fenster.")
if __name__ == "__main__":
main()