Skip to content

Commit 4ff7e80

Browse files
authored
Merge pull request #199 from runcycles/ci/benchmark-gate-noisy-p99
ci(bench): make p99 latency metrics non-gating in the release gate
2 parents abf5126 + 64b66a3 commit 4ff7e80

2 files changed

Lines changed: 49 additions & 16 deletions

File tree

AUDIT.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@
55

66
---
77

8+
### 2026-06-18 — Benchmark release gate: p99 metrics non-gating (no version bump)
9+
10+
The release gate (`scripts/check-regression.py`) failed the v0.1.25.34 release on `commit_p99` (+94% vs baseline) while every p50 and throughput metric was within tolerance.
11+
12+
**Why.** p99 tail latency on a 200-iteration micro-benchmark over shared GitHub runners swings ~2× run-to-run (`commit_p99` measured 6.5 → 8.2 → 12.6 across three runs) — GC pauses and runner contention dominate the tail, far beyond the 25% threshold. No single-sample baseline can stabilize that, and same-machine `.21``.34` showed only +8% on `commit_p99`, so it's noise, not a code regression.
13+
14+
**Change.** `HEADLINE_METRICS` gains a third element, `gating`. The p99 metrics (`reserve_p99_ms`, `commit_p99_ms`) are now **non-gating**: still measured and shown in the summary table (labelled `noisy (non-gating)` when they exceed the threshold) but no longer failing the build. The stable signals — p50 latency (reserve/commit/release/event) and `concurrent_throughput_32t` — remain hard gates. Applies to both the release gate and the nightly trend check.
15+
16+
**Verified.** A p99-only breach now passes (exit 0); a real p50 regression (+100% `commit_p50`) still fails (exit 1); bootstrap and trend modes unaffected. CI-tooling only — no production/spec/wire change, no version bump.
17+
818
### 2026-06-18 — v0.1.25.34: surface committed metadata on `getReservation`
919

1020
Commit-time metadata was write-only on the server — accepted, persisted, never returned. Fixes runcycles/cycles-server#197.

scripts/check-regression.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,20 @@
3232
from typing import Dict, List, Optional
3333

3434

35-
# Headline metrics the gate cares about. Direction: True = lower-is-better
36-
# (latency). False = higher-is-better (throughput).
35+
# Headline metrics. 2nd element = direction (True = lower-is-better/latency,
36+
# False = higher-is-better/throughput). 3rd element = GATING: whether a breach
37+
# fails the build. p99 tail latency on a 200-iteration micro-benchmark over
38+
# shared CI runners swings ~2x run-to-run (GC pauses / runner contention), far
39+
# beyond any sane threshold, so p99 is tracked + reported but NON-GATING; the
40+
# stable signals — p50 latency and concurrent throughput — are the hard gates.
3741
HEADLINE_METRICS = [
38-
("reserve_p50_ms", True),
39-
("reserve_p99_ms", True),
40-
("commit_p50_ms", True),
41-
("commit_p99_ms", True),
42-
("release_p50_ms", True),
43-
("event_p50_ms", True),
44-
("concurrent_throughput_32t", False),
42+
("reserve_p50_ms", True, True),
43+
("reserve_p99_ms", True, False),
44+
("commit_p50_ms", True, True),
45+
("commit_p99_ms", True, False),
46+
("release_p50_ms", True, True),
47+
("event_p50_ms", True, True),
48+
("concurrent_throughput_32t", False, True),
4549
]
4650

4751

@@ -68,7 +72,7 @@ def rolling_median(history: List[dict], window: int) -> Dict[str, float]:
6872
"""Take the last `window` history entries, median each metric."""
6973
recent = history[-window:] if window > 0 else history
7074
result: Dict[str, float] = {}
71-
for metric, _ in HEADLINE_METRICS:
75+
for metric, *_ in HEADLINE_METRICS:
7276
values = [
7377
e[metric] for e in recent
7478
if metric in e and isinstance(e[metric], (int, float))
@@ -94,7 +98,7 @@ def compare(
9498
) -> List[dict]:
9599
"""Return a list of {metric, current, baseline, change, regressed}."""
96100
results = []
97-
for metric, lower_is_better in HEADLINE_METRICS:
101+
for metric, lower_is_better, gating in HEADLINE_METRICS:
98102
c = current.get(metric)
99103
b = baseline.get(metric)
100104
if c is None or b is None:
@@ -104,16 +108,23 @@ def compare(
104108
"baseline": b,
105109
"change_pct": None,
106110
"regressed": False,
111+
"breached": False,
112+
"gating": gating,
107113
"note": "missing",
108114
})
109115
continue
110116
change = pct_change(c, b, lower_is_better)
117+
breached = change > threshold
111118
results.append({
112119
"metric": metric,
113120
"current": c,
114121
"baseline": b,
115122
"change_pct": round(change * 100, 1),
116-
"regressed": change > threshold,
123+
# Only a GATING metric's breach fails the build; a non-gating
124+
# (p99) breach is reported but does not regress the gate.
125+
"regressed": breached and gating,
126+
"breached": breached,
127+
"gating": gating,
117128
"note": None,
118129
})
119130
return results
@@ -149,9 +160,21 @@ def format_summary(
149160
else:
150161
sign = "+" if r["change_pct"] >= 0 else ""
151162
delta = f"{sign}{r['change_pct']}%"
152-
status = "REGRESSED" if r["regressed"] else "OK"
163+
if r["regressed"]:
164+
status = "REGRESSED"
165+
elif r.get("breached"):
166+
# exceeded threshold but the metric is non-gating (p99 noise)
167+
status = "noisy (non-gating)"
168+
else:
169+
status = "OK"
153170
lines.append(f"| `{r['metric']}` | {b} | {c} | {delta} | {status} |")
154171
lines.append("")
172+
lines.append(
173+
"_p99 latency is non-gating (informational): tail latency on a "
174+
"micro-benchmark over shared CI runners is too noisy to gate; p50 "
175+
"and throughput are the gating signals._"
176+
)
177+
lines.append("")
155178
lines.append(
156179
f"**Overall: {'REGRESSION DETECTED' if any_regressed else 'OK'}**"
157180
)
@@ -169,7 +192,7 @@ def run_release(args) -> int:
169192
# the gate. Accept and let the caller overwrite baseline.json with
170193
# `current`.
171194
if not baseline_raw or not any(
172-
m in baseline_raw for m, _ in HEADLINE_METRICS
195+
m in baseline_raw for m, *_ in HEADLINE_METRICS
173196
):
174197
results = [
175198
{
@@ -180,7 +203,7 @@ def run_release(args) -> int:
180203
"regressed": False,
181204
"note": "bootstrap",
182205
}
183-
for m, _ in HEADLINE_METRICS
206+
for m, *_ in HEADLINE_METRICS
184207
]
185208
print(format_summary(
186209
"release-gate", results, args.threshold,
@@ -190,7 +213,7 @@ def run_release(args) -> int:
190213

191214
baseline = {
192215
m: baseline_raw[m]
193-
for m, _ in HEADLINE_METRICS
216+
for m, *_ in HEADLINE_METRICS
194217
if m in baseline_raw
195218
}
196219
results = compare(current, baseline, args.threshold)

0 commit comments

Comments
 (0)