109 lines
3.3 KiB
Python
109 lines
3.3 KiB
Python
#!/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()
|