#!/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 [--markers ] """ 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()