Skip to content

Commit d4dee52

Browse files
fix(ci): seed ubuntu baselines and fix ruff/mypy benchmark regressions
1 parent 93cb445 commit d4dee52

6 files changed

Lines changed: 58 additions & 38 deletions

File tree

benchmarks/baselines.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
{
2-
"_note": "Gated means from local reference run with 1.5x slack (Windows dev host). Excluded from gate: test_parse_session_small, test_search_full_corpus (CI noise). Memory benchmarks use peak_bytes; latency uses stats.mean. Refresh from ubuntu-latest CI artifact after first green job.",
3-
"updated": "2026-06-25T00:00:00Z",
4-
"machine": "Windows",
2+
"_note": "Gated means from ubuntu-latest CI benchmark-results.json (PR memory-path benchmarks). Excluded from gate: test_parse_session_small, test_search_full_corpus (CI noise). Memory benchmarks use extra_info.peak_bytes (bytes); latency uses stats.mean (seconds).",
3+
"updated": "2026-06-25T12:00:00Z",
4+
"machine": "Linux",
55
"groups": {
66
"parse": {
7-
"test_parse_session_medium": 0.0021930547314696017,
8-
"test_parse_session_large": 0.022708179059622507,
9-
"test_parse_large_peak_memory": 3175761.0
7+
"test_parse_session_medium": 0.003037,
8+
"test_parse_session_large": 0.030266,
9+
"test_parse_large_peak_memory": 2032028.0
1010
},
1111
"export": {
12-
"test_bulk_export_session_count[sessions-10]": 0.003750986296823886,
13-
"test_bulk_export_session_count[sessions-50]": 0.018420866638835933,
14-
"test_bulk_export_session_count[sessions-100]": 0.03871899230244498,
15-
"test_bulk_export_zip_peak_memory[sessions-10]": 525699.0,
16-
"test_bulk_export_zip_peak_memory[sessions-50]": 752907.0,
17-
"test_bulk_export_zip_peak_memory[sessions-100]": 1029018.0
12+
"test_bulk_export_session_count[sessions-10]": 0.004253,
13+
"test_bulk_export_session_count[sessions-50]": 0.021039,
14+
"test_bulk_export_session_count[sessions-100]": 0.041709,
15+
"test_bulk_export_zip_peak_memory[sessions-10]": 350546.0,
16+
"test_bulk_export_zip_peak_memory[sessions-50]": 503331.0,
17+
"test_bulk_export_zip_peak_memory[sessions-100]": 686874.0
1818
},
1919
"search": {
20-
"test_search_full_corpus": 0.0033342776477665584
20+
"test_search_full_corpus": 0.001091
2121
}
2222
}
2323
}

scripts/check_benchmark_regression.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,24 @@ class BenchmarkDataError(ValueError):
2222
"""Raised when benchmark JSON input is malformed or missing required fields."""
2323

2424

25+
def entry_uses_peak_bytes(entry: dict[str, object]) -> bool:
26+
"""True when the gated metric for *entry* is extra_info.peak_bytes."""
27+
extra = entry.get("extra_info")
28+
return isinstance(extra, dict) and "peak_bytes" in extra
29+
30+
31+
def metric_is_bytes(name: str, entry: dict[str, object] | None = None) -> bool:
32+
"""Shared heuristic for metric kind (bytes vs seconds) in gate and display."""
33+
if entry is not None and entry_uses_peak_bytes(entry):
34+
return True
35+
return "peak_memory" in name
36+
37+
2538
def benchmark_entry_mean(entry: dict[str, object]) -> float:
2639
"""Return gated metric: peak_bytes from extra_info when present, else stats.mean."""
27-
extra = entry.get("extra_info")
28-
if isinstance(extra, dict) and "peak_bytes" in extra:
40+
if entry_uses_peak_bytes(entry):
41+
extra = entry["extra_info"]
42+
assert isinstance(extra, dict)
2943
return float(extra["peak_bytes"])
3044
try:
3145
stats = entry["stats"]
@@ -36,11 +50,9 @@ def benchmark_entry_mean(entry: dict[str, object]) -> float:
3650
) from exc
3751

3852

39-
def is_memory_metric(name: str) -> bool:
40-
return "peak_memory" in name
41-
42-
43-
def load_results(results_path: str | Path) -> dict[str, float]:
53+
def load_results(
54+
results_path: str | Path,
55+
) -> tuple[dict[str, float], dict[str, dict[str, object]]]:
4456
path = Path(results_path)
4557
try:
4658
data = json.loads(path.read_text(encoding="utf-8"))
@@ -56,6 +68,7 @@ def load_results(results_path: str | Path) -> dict[str, float]:
5668
raise BenchmarkDataError(f"{path} 'benchmarks' must be an array")
5769

5870
results: dict[str, float] = {}
71+
entries_by_name: dict[str, dict[str, object]] = {}
5972
for index, entry in enumerate(benchmarks):
6073
if not isinstance(entry, dict):
6174
raise BenchmarkDataError(f"{path} benchmarks[{index}] must be an object")
@@ -70,7 +83,8 @@ def load_results(results_path: str | Path) -> dict[str, float]:
7083
if name in results:
7184
raise BenchmarkDataError(f"{path} duplicate benchmark name {name!r}")
7285
results[name] = mean
73-
return results
86+
entries_by_name[name] = entry
87+
return results, entries_by_name
7488

7589

7690
def load_baseline_means(baselines_path: str | Path) -> dict[str, float]:
@@ -114,7 +128,7 @@ def check_regression(
114128
threshold: float = THRESHOLD,
115129
) -> int:
116130
"""Return 0 when within threshold; 1 when any gated benchmark regresses."""
117-
flat = load_results(results_path)
131+
flat, entries_by_name = load_results(results_path)
118132
baseline_means = load_baseline_means(baselines_path)
119133

120134
failures: list[str] = []
@@ -130,7 +144,8 @@ def check_regression(
130144
continue
131145
ratio = cur / base
132146
tag = "FAIL" if ratio > threshold else "ok"
133-
if is_memory_metric(name):
147+
entry = entries_by_name.get(name)
148+
if metric_is_bytes(name, entry):
134149
print(f"[{tag}] {name}: {cur:.0f} bytes vs {base:.0f} bytes ({ratio:.2f}x)")
135150
else:
136151
print(f"[{tag}] {name}: {cur:.6f}s vs {base:.6f}s ({ratio:.2f}x)")

scripts/reduce_baselines.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,9 @@
99
from pathlib import Path
1010

1111
try:
12-
from scripts.check_benchmark_regression import (
13-
EXCLUDED_FROM_GATE,
14-
BenchmarkDataError,
15-
benchmark_entry_mean,
16-
)
12+
from scripts.check_benchmark_regression import BenchmarkDataError, benchmark_entry_mean
1713
except ModuleNotFoundError:
18-
from check_benchmark_regression import (
19-
EXCLUDED_FROM_GATE,
20-
BenchmarkDataError,
21-
benchmark_entry_mean,
22-
)
14+
from check_benchmark_regression import BenchmarkDataError, benchmark_entry_mean
2315

2416
GATED_GROUPS = ("parse", "export", "search")
2517

@@ -72,9 +64,10 @@ def reduce_baselines(
7264
machine = machine_info.get("system") if isinstance(machine_info, dict) else None
7365
output: dict[str, object] = {
7466
"_note": (
75-
"Gated means from ubuntu-latest CI (post-cache). "
67+
"Gated means from ubuntu-latest CI benchmark-results.json. "
7668
"Excluded from gate: test_parse_session_small, test_search_full_corpus (CI noise). "
77-
"Memory benchmarks use extra_info.peak_bytes (bytes); latency uses stats.mean (seconds)."
69+
"Memory benchmarks use extra_info.peak_bytes (bytes); "
70+
"latency uses stats.mean (seconds)."
7871
),
7972
"updated": datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
8073
"machine": machine,

tests/benchmarks/test_export_bench.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import pytest
1010

1111
from tests.benchmarks.conftest import TracemallocPeak
12-
from utils.export_engine import NoopSink, ZipSink, run_bulk_export
12+
from utils.export_engine import BulkExportResult, NoopSink, ZipSink, run_bulk_export
1313

1414

1515
def _bench_projects(export_corpus: Path) -> list[dict[str, str]]:
@@ -62,7 +62,7 @@ def test_bulk_export_zip_peak_memory(
6262
peaks: list[int] = []
6363

6464
def _run() -> None:
65-
def _export() -> object:
65+
def _export() -> BulkExportResult:
6666
buf = io.BytesIO()
6767
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf:
6868
sink = ZipSink(zf)

tests/benchmarks/test_parse_memory.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,5 @@ def _run() -> None:
4646

4747
benchmark(_run)
4848
assert peaks, "benchmark produced no peak memory samples"
49+
# Gate uses extra_info.peak_bytes, not stats.mean (tracemalloc-inflated wall time).
4950
benchmark.extra_info["peak_bytes"] = int(sum(peaks) / len(peaks))

tests/test_check_benchmark_regression.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,18 @@ def test_load_results_prefers_peak_bytes_extra_info(tmp_path) -> None:
192192
],
193193
)
194194

195-
assert load_results(path)["test_parse_large_peak_memory"] == 12_345_678.0
195+
assert load_results(path)[0]["test_parse_large_peak_memory"] == 12_345_678.0
196+
197+
198+
def test_metric_is_bytes_uses_extra_info_without_name_hint() -> None:
199+
from scripts.check_benchmark_regression import metric_is_bytes
200+
201+
entry = {
202+
"name": "test_custom_memory",
203+
"stats": {"mean": 0.05},
204+
"extra_info": {"peak_bytes": 1_000_000},
205+
}
206+
assert metric_is_bytes("test_custom_memory", entry)
196207

197208

198209
def test_memory_metric_regression_uses_bytes(tmp_path, capsys: pytest.CaptureFixture[str]) -> None:

0 commit comments

Comments
 (0)