#!/usr/bin/env python3 """summarize_encode.py - Markdown tables from a run_encode_tests.sh result folder. python3 summarize_encode.py [ ...] Each folder holds matrix_real/synth.jsonl (with matrix12_*.jsonl, if the 12-bit part was rerun, replacing the 12-bit rows), throughput.jsonl, pileup*.jsonl, idle_*.jsonl and heat_*.jsonl. """ import json import os import sys def rows(path): if not os.path.exists(path): return [] return [json.loads(l) for l in open(path) if l.strip()] def matrix(d, which): m = rows(f"{d}/matrix_{which}.jsonl") m12 = rows(f"{d}/matrix12_{which}.jsonl") if m12: # the rerun replaces the 12-bit rows m = [r for r in m if r["kind"] not in ("raw12", "colour12")] + m12 return m def effort_threads(m, kind): out = ["| effort | 1 thread | 2 threads | 3 threads | 4 threads | file (4 thr.) |", "|---|---|---|---|---|---|"] for e in range(1, 6): r = {x["threads"]: x for x in m if x["kind"] == kind and x["effort"] == e and x["distance"] == 0.8 and "psnr_db" not in x} if not r: continue cells = [f"{r[t]['ms']:.0f} ms" if t in r else "–" for t in (1, 2, 3, 4)] size = f"{r[4]['bytes'] / 1e6:.1f} MB" if 4 in r else "–" out.append(f"| {e} | " + " | ".join(cells) + f" | {size} |") return "\n".join(out) def lossless(m, kind): out = ["| effort | 1 thread | 2 threads | 3 threads | 4 threads | file |", "|---|---|---|---|---|---|"] for e in (1, 2, 3): r = {x["threads"]: x for x in m if x["kind"] == kind and x["effort"] == e} if not r: continue cells = [f"{r[t]['ms']:.0f} ms" if t in r else "–" for t in (1, 2, 3, 4)] out.append(f"| {e} | " + " | ".join(cells) + f" | {r[max(r)]['bytes'] / 1e6:.1f} MB |") return "\n".join(out) def sweep(m): out = ["| input | distance | time | file | PSNR |", "|---|---|---|---|---|"] for x in m: if "psnr_db" in x: out.append(f"| {x['kind']} | {x['distance']} | {x['ms']:.0f} ms | " f"{x['bytes'] / 1e6:.2f} MB | {x['psnr_db']:.1f} dB |") return "\n".join(out) def summaries(path): return [r for r in rows(path) if r.get("summary") == "pipeline"] def pipeline_table(rs): out = ["| input | workers × threads | one frame every | frames/s | capture→saved p50 / max | " "backlog at end | CPU-s/frame | temp mean / max | clock | power |", "|---|---|---|---|---|---|---|---|---|---|"] for r in rs: period = f"{r['period_ms'] / 1000:.1f} s" if r["period_ms"] else "back to back" out.append( f"| {r['kind']} | {r['workers']} × {r['threads']} | {period} | {r['frames_per_s']:.2f} | " f"{r['latency_ms_p50'] / 1000:.2f} / {r['latency_ms_max'] / 1000:.2f} s | " f"{r['backlog_at_end']} | {r['cpu_s_per_frame']:.2f} | " f"{r['temp_c_mean']:.0f} / {r['temp_c_max']:.0f} °C | {r['mhz_mean'] / 1000:.2f} GHz | " f"{r['watts_mean']:.1f} W |") return "\n".join(out) def per_minute(path): rs = [r for r in rows(path) if "t" in r and "summary" not in r] out = ["| minute | temp mean (max) | clock mean | CPU busy | power | frames |", "|---|---|---|---|---|---|"] prev = 0 for m in range(0, len(rs) // 60): seg = rs[m * 60:(m + 1) * 60] avg = lambda k: sum(r[k] for r in seg) / len(seg) done = seg[-1].get("done", 0) out.append(f"| {m + 1} | {avg('temp_c'):.0f} ({max(r['temp_c'] for r in seg):.0f}) °C | " f"{avg('mhz') / 1000:.2f} GHz | {avg('cpu_pct'):.0f}% | {avg('watts'):.1f} W | " f"{done - prev} |") prev = done return "\n".join(out) for d in sys.argv[1:]: print(f"\n# {d}\n") idle = [r for r in rows(f"{d}/idle_start.jsonl") if r.get("summary")] if idle: i = idle[0] print(f"Idle: {i['temp_c_mean']:.0f} °C, {i['mhz_mean'] / 1000:.2f} GHz, " f"{i['cpu_pct_mean']:.0f}% busy, {i['watts_mean']:.1f} W\n") for which in ("synth", "real"): m = matrix(d, which) if not m: continue print(f"\n## matrix {which}\n") for kind in ("rgb8", "bilinear", "ea"): prep = [x["prep_ms"] for x in m if x["kind"] == kind] print(f"\n### {kind} (conversion {min(prep):.0f}–{max(prep):.0f} ms)\n") print(effort_threads(m, kind)) print("\n### distance sweep (effort 4, 4 threads)\n") print(sweep(m)) for kind in ("raw8", "raw12"): print(f"\n### {kind} lossless\n") print(lossless(m, kind)) for x in m: if x["kind"] == "colour12": print(f"\ncolour12 effort {x['effort']}, 4 threads: {x['ms']:.0f} ms " f"(conversion {x['prep_ms']:.0f}), {x['bytes'] / 1e6:.2f} MB") print("\n## throughput and pile-up\n") print(pipeline_table(summaries(f"{d}/throughput.jsonl") + summaries(f"{d}/pileup.jsonl") + summaries(f"{d}/pileup_real.jsonl"))) for h in ("heat_max", "heat_paced"): print(f"\n## {h}\n") print(pipeline_table(summaries(f"{d}/{h}.jsonl"))) print() print(per_minute(f"{d}/{h}.jsonl"))