Files
2026-07-09 05:08:16 +08:00

166 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""
Validate benchmark-vs-TA-Lib results against guardrail thresholds.
This is intentionally conservative: it catches severe regressions and incomplete
benchmark outputs, without overfitting to one machine.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
def _parse_threshold_items(items: list[str]) -> dict[int, float]:
thresholds: dict[int, float] = {}
for item in items:
if "=" not in item:
raise ValueError(f"Invalid threshold '{item}', expected SIZE=VALUE")
size_s, value_s = item.split("=", 1)
thresholds[int(size_s)] = float(value_s)
return thresholds
def _percentile(values: list[float], q: float) -> float:
"""Return the q percentile using linear interpolation."""
if not values:
raise ValueError("Cannot compute percentile of empty sequence")
if q <= 0:
return min(values)
if q >= 100:
return max(values)
values = sorted(values)
rank = (len(values) - 1) * (q / 100.0)
lower = int(rank)
upper = min(lower + 1, len(values) - 1)
weight = rank - lower
return values[lower] * (1.0 - weight) + values[upper] * weight
def main() -> int:
parser = argparse.ArgumentParser(
description="Check TA-Lib benchmark JSON against regression thresholds."
)
parser.add_argument(
"--input",
default="benchmark_vs_talib.json",
help="Path to benchmark JSON produced by benchmarks/bench_vs_talib.py",
)
parser.add_argument(
"--min-rows",
type=int,
default=10,
help="Minimum benchmark rows required per size",
)
parser.add_argument(
"--median-floor",
action="append",
default=["10000=0.35", "100000=0.35"],
help="Required minimum median speedup per size, e.g. 100000=0.5 (repeatable)",
)
parser.add_argument(
"--min-speedup-floor",
action="append",
default=["10000=0.10", "100000=0.10"],
help="Hard minimum per-row speedup floor per size, e.g. 100000=0.1 (repeatable)",
)
parser.add_argument(
"--tail-percentile",
type=float,
default=10.0,
help="Tail percentile used for distribution-based slowdown checks (default: 10)",
)
parser.add_argument(
"--tail-speedup-floor",
action="append",
default=["10000=0.20", "100000=0.20"],
help="Required minimum tail percentile speedup per size, e.g. 100000=0.2 (repeatable)",
)
args = parser.parse_args()
path = Path(args.input)
if not path.exists():
print(f"ERROR: benchmark file not found: {path}")
return 1
data = json.loads(path.read_text(encoding="utf-8"))
if not data.get("talib_available", False):
print(
"ERROR: TA-Lib was not available; cannot enforce TA-Lib regression policy."
)
return 1
summary_by_size = {
int(entry.get("size")): entry
for entry in data.get("summary", {}).get("by_size", [])
if entry.get("size") is not None
}
results_by_size: dict[int, list[dict[str, object]]] = {}
for row in data.get("results", []):
if "speedup" not in row or row.get("size") is None:
continue
size = int(row["size"])
results_by_size.setdefault(size, []).append(row)
median_floor = _parse_threshold_items(args.median_floor)
min_speedup_floor = _parse_threshold_items(args.min_speedup_floor)
tail_speedup_floor = _parse_threshold_items(args.tail_speedup_floor)
required_sizes = sorted(
set(median_floor) | set(min_speedup_floor) | set(tail_speedup_floor)
)
failures: list[str] = []
for size in required_sizes:
entry = summary_by_size.get(size)
if entry is None:
failures.append(f"missing summary for size={size}")
continue
rows_for_size = results_by_size.get(size, [])
if not rows_for_size:
failures.append(f"missing detailed rows for size={size}")
continue
rows = int(entry.get("rows", 0))
med = float(entry.get("median_speedup", 0.0))
min_s = float(entry.get("min_speedup", 0.0))
speedups = [float(row["speedup"]) for row in rows_for_size]
tail_s = _percentile(speedups, args.tail_percentile)
print(
"size="
f"{size}: rows={rows}, median_speedup={med:.4f}, "
f"p{args.tail_percentile:g}_speedup={tail_s:.4f}, min_speedup={min_s:.4f}"
)
if rows < args.min_rows:
failures.append(f"size={size} rows {rows} < min_rows {args.min_rows}")
if med < median_floor.get(size, float("-inf")):
failures.append(
f"size={size} median_speedup {med:.4f} < floor {median_floor[size]:.4f}"
)
if tail_s < tail_speedup_floor.get(size, float("-inf")):
failures.append(
"size="
f"{size} p{args.tail_percentile:g}_speedup {tail_s:.4f} "
f"< floor {tail_speedup_floor[size]:.4f}"
)
if min_s < min_speedup_floor.get(size, float("-inf")):
failures.append(
f"size={size} min_speedup {min_s:.4f} < floor {min_speedup_floor[size]:.4f}"
)
if failures:
print("FAILED benchmark regression policy:")
for failure in failures:
print(f" - {failure}")
return 1
print("PASS benchmark regression policy.")
return 0
if __name__ == "__main__":
raise SystemExit(main())