Files

109 lines
3.3 KiB
Python
Raw Permalink Normal View History

2026-07-09 05:08:16 +08:00
#!/usr/bin/env python3
"""
Generate the Speed Comparison markdown table from benchmarks/results.json.
Requires results from the full suite:
pytest benchmarks/test_speed.py --benchmark-only --benchmark-json=benchmarks/results.json -v
Reads results.json and prints a markdown table: all indicators × all libraries.
Unsupported (indicator, library) pairs show N/A. Supported pairs missing benchmark
data show ERR (indicating the benchmark run was incomplete or failed).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
# Ensure project root is on path when run as script
_root = Path(__file__).resolve().parent.parent
if _root not in (Path(p).resolve() for p in sys.path):
sys.path.insert(0, str(_root))
from benchmarks.wrapper_registry import (
INDICATOR_CATEGORIES,
is_supported,
)
from benchmarks.wrapper_registry import (
LIBRARY_NAMES as LIBS,
)
def _all_indicators() -> list[str]:
"""All indicators in category order (matches test_speed parametrization)."""
return [ind for cat in INDICATOR_CATEGORIES for ind in INDICATOR_CATEGORIES[cat]]
def main():
p = Path(__file__).parent / "results.json"
if not p.exists():
print(
"Run: pytest benchmarks/test_speed.py --benchmark-only --benchmark-json=benchmarks/results.json -v",
file=sys.stderr,
)
sys.exit(1)
raw = p.read_text().strip()
if not raw:
print(
"results.json is empty. Run the full benchmark suite first.",
file=sys.stderr,
)
sys.exit(1)
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f"Invalid JSON in results.json: {e}", file=sys.stderr)
sys.exit(1)
benchmarks = data.get("benchmarks", [])
# Collect test_speed[Category/Indicator/library] -> median µs
table: dict[str, dict[str, float]] = {}
for b in benchmarks:
name = b.get("name") or ""
if "test_speed[" not in name:
continue
params = b.get("params") or {}
ind = params.get("indicator")
lib = params.get("library")
if not ind or not lib or lib not in LIBS:
continue
median_sec = (b.get("stats") or {}).get("median")
if median_sec is None:
continue
if ind not in table:
table[ind] = {}
table[ind][lib] = median_sec * 1e6 # to µs
all_indicators = _all_indicators()
if not all_indicators:
print("No indicators from INDICATOR_CATEGORIES.", file=sys.stderr)
sys.exit(1)
# Header: Indicator | ferro_ta | talib | ...
lib_header = " | ".join(LIBS)
print(f"| Indicator | {lib_header} |")
print("|-----------|" + "|".join(["--------:" for _ in LIBS]) + "|")
for ind in all_indicators:
row = table.get(ind, {})
cells = []
for lib in LIBS:
if lib in row:
cells.append(str(round(row[lib])))
elif not is_supported(lib, ind):
cells.append("N/A")
else:
cells.append("ERR")
print(f"| {ind} | {' | '.join(cells)} |")
print()
print(
"(Median time in µs, lower is better. N/A = unsupported pair. "
"ERR = supported pair missing benchmark data. Source: results.json from full test_speed run.)"
)
if __name__ == "__main__":
main()