132 lines
5.2 KiB
Python
132 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
||
"""summarize_encode.py - Markdown tables from a run_encode_tests.sh result folder.
|
||
|
||
python3 summarize_encode.py <results_dir> [<results_dir> ...]
|
||
|
||
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"))
|