100 lines
3.7 KiB
Python
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()
|