Initial import from bf-network-r6.opencode — opencode migration
This commit is contained in:
149
docs/tp6-auswertung.py
Normal file
149
docs/tp6-auswertung.py
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user