Files
tso-paper-eval/analysis/gen_values.py
T
2026-05-27 21:00:28 +02:00

100 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Generate values.tex from plugins under analysis/values/.
Each plugin exposes compute(derived) -> (keys, sources). Keys are merged into
one \\pgfkeys block; sources are recorded in the header for provenance.
"""
import argparse
import datetime as dt
import importlib.util
from pathlib import Path
from types import ModuleType
def load_plugins(plugin_dir: Path, skip: set[str]) -> list[tuple[str, ModuleType]]:
plugins: list[tuple[str, ModuleType]] = []
for p in sorted(plugin_dir.glob("*.py")):
if p.name.startswith("_") or p.stem in skip:
continue
spec = importlib.util.spec_from_file_location(f"values.{p.stem}", p)
if spec is None or spec.loader is None:
raise SystemExit(f"could not load plugin {p}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
if hasattr(mod, "compute"):
plugins.append((p.stem, mod))
return plugins
def file_mtime_iso(p: Path) -> str:
return dt.datetime.fromtimestamp(p.stat().st_mtime).isoformat(timespec="seconds")
# Map key-name suffix to siunitx unit.
UNIT_SUFFIXES = {
"-us": r"\micro\second",
"-ms": r"\milli\second",
"-gbps": r"\giga\bit\per\second",
"-pct": r"\percent",
"-kb": r"\kilo\byte",
}
NUM_SUFFIXES = ("-p", "-alpha", "-n", "-uncorrected")
NUM_PREFIXES = ("n-",)
def value_to_latex(key: str, value: str) -> str:
leaf = key.rsplit("/", 1)[-1]
for suffix, unit in UNIT_SUFFIXES.items():
if leaf.endswith(suffix):
return f"\\qty{{{value}}}{{{unit}}}"
if any(leaf.startswith(p) for p in NUM_PREFIXES) or any(leaf.endswith(s) for s in NUM_SUFFIXES):
return f"\\num{{{value}}}"
return value
def format_pgfkeys(all_keys: dict[str, str], provenance: list[str]) -> str:
lines: list[str] = []
lines.append("% Auto-generated by analysis/gen_values.py. Do not edit by hand.")
lines.append(f"% Generated: {dt.datetime.now().isoformat(timespec='seconds')}")
lines.extend(provenance)
for k, v in sorted(all_keys.items()):
lines.append(f"\\pgfkeyssetvalue{{/{k}}}{{{value_to_latex(k, v)}}}")
return "\n".join(lines) + "\n"
def main() -> None:
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument("--derived", required=True, type=Path, help="derived/ root with per-experiment subdirs")
p.add_argument("--out", required=True, type=Path, help="output values.tex path")
p.add_argument("--plugins", type=Path, default=Path(__file__).parent / "values",
help="plugin directory (default: analysis/values)")
p.add_argument("--skip", nargs="*", default=[],
help="plugin names to skip (stem only, e.g. --skip cpu rtt)")
args = p.parse_args()
plugins = load_plugins(args.plugins, set(args.skip))
if not plugins:
raise SystemExit(f"no plugins found in {args.plugins} (after --skip)")
all_keys: dict[str, str] = {}
provenance: list[str] = ["% Plugins:"]
for name, mod in plugins:
keys, sources = mod.compute(args.derived)
dup = set(keys) & set(all_keys)
if dup:
raise SystemExit(f"plugin {name} duplicates keys: {sorted(dup)}")
all_keys.update(keys)
provenance.append(f"% {name}: {len(keys)} keys")
for src in sources:
provenance.append(f"% {src} (mtime {file_mtime_iso(src)})")
print(f" {name}: {len(keys)} keys from {len(sources)} sources")
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(format_pgfkeys(all_keys, provenance))
print(f"wrote {args.out} ({len(all_keys)} keys total)")
if __name__ == "__main__":
main()