#!/usr/bin/env python3 """Render a cross-port UBER perf table in Markdown. Reads each port's joeylog.txt (the file UBER appends ops/sec lines to) and emits a Markdown table comparing all ports against a reference. The reference column shows absolute ops/sec; every other port shows a ratio of (port / reference) so a glance tells you whether each target is at, above, or below the reference. Usage: tools/uber-perf-table [--ref iigs] [--out PATH] [--repo PATH] Defaults pull the 4 standard log paths under the repo's build/ tree; pass --repo if running from elsewhere. The reference port (default IIgs, which DESIGN.md calls the perf floor) defines the ratio denominator. Ports whose log file is missing are listed in the table header as "(no log)" and their columns are filled with "-". Example, run from the repo root: tools/uber-perf-table > PERF.md To compare against a different reference (e.g. profile DOS as the floor for a DOS-only project): tools/uber-perf-table --ref dos """ import argparse import os import re import sys LINE_RE = re.compile( r"UBER:\s+(?P[^:]+):\s+\d+\s+iters\s+/\s+\d+\s+frames\s+=\s+(?P\d+)\s+ops/sec" ) # Per-port log path relative to the repo root, in the column order the # table renders. Reference port appears first. PORTS = [ ("iigs", "IIgs", "build/iigs/bin/joeylog.txt"), ("amiga", "Amiga", "build/amiga/bin/joeylog.txt"), ("atarist", "Atari ST", "build/atarist/bin/joeylog.txt"), ("dos", "DOS", "build/dos/bin/JOEYLOG.TXT"), ] def parse_log(path): """Return {op: ops/sec} from a UBER log. Last entry per op wins.""" perf = {} with open(path) as f: for line in f: m = LINE_RE.search(line) if m: perf[m.group("op").strip()] = int(m.group("ops")) return perf def format_ratio(port_ops, ref_ops): if port_ops is None or ref_ops is None or ref_ops == 0: return "-" ratio = port_ops / ref_ops return f"{ratio:.2f}x" def render_table(port_data, ops_in_order, ref_key): headers = ["Op"] for key, label, _ in PORTS: if not port_data[key]["loaded"]: label = f"{label} (no log)" if key == ref_key: headers.append(f"{label} (ops/sec)") else: headers.append(f"{label} (vs {ref_key.upper()})") rows = [headers, ["---"] * len(headers)] for op in ops_in_order: row = [op] ref_ops = port_data[ref_key]["perf"].get(op) for key, _, _ in PORTS: ops = port_data[key]["perf"].get(op) if key == ref_key: row.append(str(ops) if ops is not None else "-") else: row.append(format_ratio(ops, ref_ops)) rows.append(row) return "\n".join("| " + " | ".join(r) + " |" for r in rows) def main(argv): parser = argparse.ArgumentParser( description="Render a cross-port UBER perf table in Markdown." ) parser.add_argument("--ref", default="iigs", choices=[p[0] for p in PORTS], help="Reference port (default: iigs)") parser.add_argument("--out", default=None, help="Write table to this file instead of stdout") parser.add_argument("--repo", default=None, help="Repo root (default: parent of this script)") args = parser.parse_args(argv[1:]) if args.repo is None: args.repo = os.path.dirname(os.path.dirname(os.path.abspath(argv[0]))) port_data = {} union_ops = [] seen = set() for key, _, rel in PORTS: path = os.path.join(args.repo, rel) try: perf = parse_log(path) loaded = True except OSError: perf = {} loaded = False port_data[key] = {"perf": perf, "loaded": loaded, "path": path} for op in perf: if op not in seen: seen.add(op) union_ops.append(op) if not port_data[args.ref]["loaded"]: sys.stderr.write( f"error: reference log missing: {port_data[args.ref]['path']}\n" f" run UBER on {args.ref} first, then retry.\n" ) return 2 if not union_ops: sys.stderr.write("error: no UBER ops parsed from any log\n") return 2 # Use the reference port's op order so the table matches UBER's # natural sequence (surfaceClear first, joeyAudioIsPlayingMod last). ref_ops = list(port_data[args.ref]["perf"].keys()) extras = [op for op in union_ops if op not in port_data[args.ref]["perf"]] ordered = ref_ops + extras table = render_table(port_data, ordered, args.ref) if args.out: with open(args.out, "w") as f: f.write(table + "\n") else: sys.stdout.write(table + "\n") return 0 if __name__ == "__main__": sys.exit(main(sys.argv))