fwt_software/tools/camera-bench/summarize_encode.py

132 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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"))