Skip to content

Commit 3f05f57

Browse files
committed
refactor to adopt producer -> typed dataclass -> serialized by consumer schema from refactors
1 parent a513123 commit 3f05f57

8 files changed

Lines changed: 270 additions & 115 deletions

File tree

tools/perf_smoke_test/aggregate.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
update_baseline,
3030
update_baselines_git,
3131
)
32+
from contracts import BenchResult # noqa: E402
3233
from gate_config import BASELINE_PUSH_RETRIES, load_gate_config # noqa: E402
3334
from gate_types import FpsMeanThreshold, OracleVerdict # noqa: E402
3435
from gpu_identity import canonical_gpu_model # noqa: E402
@@ -58,11 +59,11 @@ def _parse_args():
5859
return parser.parse_args()
5960

6061

61-
def _find_bench_results(artifacts_dir: Path) -> list[tuple[Path, dict]]:
62+
def _find_bench_results(artifacts_dir: Path) -> list[tuple[Path, BenchResult]]:
6263
found = []
6364
for path in sorted(artifacts_dir.rglob("perf_smoke_test_result.json")):
6465
with path.open() as fh:
65-
found.append((path.parent, json.load(fh)))
66+
found.append((path.parent, BenchResult.from_dict(json.load(fh))))
6667
return found
6768

6869

@@ -74,20 +75,20 @@ def _short_sha(value: str | None) -> str:
7475
return value[:12] if value else "none"
7576

7677

77-
def _bench_gpu_model(bench_result: dict, fallback: str) -> str:
78-
launch_config = bench_result.get("launch_config") or {}
78+
def _bench_gpu_model(bench_result: BenchResult, fallback: str) -> str:
79+
launch_config = bench_result.launch_config or {}
7980
gpu_model = canonical_gpu_model(launch_config.get("gpu_model") or launch_config.get("gpu_model_raw"))
8081
return canonical_gpu_model(fallback) if gpu_model == "unknown_gpu" else gpu_model
8182

8283

83-
def _thresholds(bench_result: dict, gpu_model: str, backend: str) -> list[FpsMeanThreshold]:
84+
def _thresholds(bench_result: BenchResult, gpu_model: str, backend: str) -> list[FpsMeanThreshold]:
8485
"""Resolve configured FPS thresholds, preferring the run's launch_config artifact."""
85-
launch_config = bench_result.get("launch_config") or {}
86+
launch_config = bench_result.launch_config or {}
8687
raw = launch_config.get("fps_mean_thresholds")
8788
if raw is not None:
88-
return FpsMeanThreshold.from_list(raw, context=f"{bench_result.get('task_id')}/{backend}")
89+
return FpsMeanThreshold.from_list(raw, context=f"{bench_result.task_id}/{backend}")
8990
try:
90-
task = get_task(bench_result["task_id"], backend)
91+
task = get_task(bench_result.task_id, backend)
9192
return task.thresholds_for(gpu_model)
9293
except Exception:
9394
return []
@@ -109,10 +110,10 @@ def _build_summary_table(rows: list[tuple]) -> str:
109110
"|---|---|---|---:|---:|---:|---:|---:|---|---|---|---|---|---|",
110111
]
111112
for result, bench_result in rows:
112-
gpu_diag = bench_result.get("gpu_diag") or {}
113-
launch_config = bench_result.get("launch_config") or {}
113+
gpu_diag = bench_result.gpu_diag or {}
114+
launch_config = bench_result.launch_config or {}
114115
gpu_name = gpu_diag.get("gpu_name") or launch_config.get("gpu_model_raw") or launch_config.get("gpu_model", "")
115-
provenance = bench_result.get("provenance") or {}
116+
provenance = bench_result.provenance or {}
116117
software = provenance.get("software") or {}
117118
runtime = ", ".join(
118119
part
@@ -125,7 +126,7 @@ def _build_summary_table(rows: list[tuple]) -> str:
125126
)
126127
# result.note already carries the config_mismatch string on the
127128
# config-mismatch HARD_FAILURE path; dedupe so it is not shown twice.
128-
note_parts = list(dict.fromkeys(part for part in (result.note, bench_result.get("config_mismatch")) if part))
129+
note_parts = list(dict.fromkeys(part for part in (result.note, bench_result.config_mismatch) if part))
129130
note_parts.extend(_render_crossed(result.crossed_thresholds))
130131
lines.append(
131132
f"| {result.task_id} | {result.backend} | {result.verdict.value}"
@@ -189,11 +190,12 @@ def main() -> int:
189190
pending_git_updates: list[BaselineUpdateRecord] = []
190191

191192
for artifact_dir, bench_result in items:
192-
task_id = bench_result["task_id"]
193-
backend = bench_result.get("backend_key") or bench_result.get("backend")
193+
task_id = bench_result.task_id
194+
backend = bench_result.backend_key or bench_result.backend
194195
bench_gpu_model = _bench_gpu_model(bench_result, args.gpu_model)
196+
# The baseline_manager storage layer works with the serialized (dict) form.
195197
match_context = match_context_from_bench_result(
196-
bench_result,
198+
bench_result.to_dict(),
197199
gpu_model=bench_gpu_model,
198200
base_sha=args.base_sha,
199201
target_branch=args.target_branch,
@@ -248,7 +250,7 @@ def main() -> int:
248250
task_id=task_id,
249251
backend=backend,
250252
fps=oracle_result.measured_fps,
251-
bench_result=bench_result,
253+
bench_result=bench_result.to_dict(),
252254
target_branch=args.target_branch,
253255
source_branch=args.source_branch,
254256
trusted_source=args.trusted_source,

tools/perf_smoke_test/benchmark_result_adapter.py

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@
1414
:mod:`oracle` and :mod:`build_bench_result` consume, replacing the legacy
1515
phase-array parsing.
1616
17-
**Deliberately dropped vs. the legacy phase-array output** (the bundle stores
18-
only aggregates, so these have no source and were never gating):
19-
20-
- ``raw_fps_median`` / ``raw_fps_p5`` / ``raw_fps_p95`` (percentiles)
21-
- ``p99_over_median`` / ``outlier_count`` (raw step-time distribution)
22-
2317
``raw_fps_min`` is *recovered* from the slowest steady-state step
2418
(``num_envs / iteration_time_s.peak``); ``raw_fps_{mean,std,max}`` come straight
2519
from ``runtime.total_fps.{mean,std,peak}``. Warmup exclusion is applied at the
@@ -34,6 +28,8 @@
3428
from pathlib import Path
3529
from typing import Any
3630

31+
from contracts import RuntimeSample
32+
3733
# Map the schema's rendering-backend vocabulary to the gate's render-preset
3834
# tokens used in tasks.json / backend_identity. ``"none"`` (headless, no camera)
3935
# maps to ``None``.
@@ -239,19 +235,31 @@ def benchmark_info(bundle: dict) -> dict:
239235
return {k: v for k, v in info.items() if v is not None}
240236

241237

242-
def to_gate_fields(bundle: dict) -> dict:
243-
"""Project a runtime bundle into the flat fields the result builder writes.
238+
def project_runtime(bundle: dict) -> RuntimeSample | None:
239+
"""Project a runtime bundle into a typed :class:`~contracts.RuntimeSample`.
244240
245-
Returns a dict with ``raw_fps_*`` stats, ``startup_time_s``, ``gpu_diag``,
246-
and ``provenance``; keys are omitted when their source is absent.
241+
Returns ``None`` when ``bundle`` is not a valid schema-v1 runtime bundle, so the
242+
caller can degrade to a HARD_FAILURE (missing benchmark output).
247243
"""
248-
out: dict = {}
249-
out.update(fps_stats(bundle))
250-
startup = startup_seconds(bundle)
251-
if startup is not None:
252-
out["startup_time_s"] = startup
253-
diag = gpu_diag(bundle)
254-
if diag:
255-
out["gpu_diag"] = diag
256-
out["provenance"] = provenance(bundle)
257-
return out
244+
if not is_runtime_bundle(bundle):
245+
return None
246+
stats = fps_stats(bundle)
247+
info = benchmark_info(bundle)
248+
return RuntimeSample(
249+
fps_mean=stats.get("raw_fps_mean"),
250+
fps_std=stats.get("raw_fps_std"),
251+
fps_min=stats.get("raw_fps_min"),
252+
fps_max=stats.get("raw_fps_max"),
253+
startup_time_s=startup_seconds(bundle),
254+
task=info.get("task"),
255+
num_envs=info.get("num_envs"),
256+
seed=info.get("seed"),
257+
num_frames=info.get("num_frames"),
258+
warmup_frames=info.get("warmup_frames"),
259+
status=info.get("status"),
260+
physics_backend=info.get("physics_backend"),
261+
render_backend=info.get("render_backend"),
262+
presets=info.get("presets") or [],
263+
provenance=provenance(bundle),
264+
gpu_diag=gpu_diag(bundle),
265+
)

tools/perf_smoke_test/build_bench_result.py

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,8 @@
4242
normalize_physics_backend,
4343
normalize_render_backend,
4444
)
45-
from benchmark_result_adapter import ( # noqa: E402
46-
benchmark_info as bundle_benchmark_info,
47-
is_runtime_bundle,
48-
load_info,
49-
to_gate_fields,
50-
)
45+
from benchmark_result_adapter import load_info, project_runtime # noqa: E402
46+
from contracts import BenchResult # noqa: E402
5147
from gate_config import load_gate_config # noqa: E402
5248
from gate_types import FailurePhase # noqa: E402
5349
from gpu_identity import normalize_gpu_fields # noqa: E402
@@ -223,8 +219,8 @@ def main() -> int:
223219
timeout_s=args.timeout_s,
224220
)
225221

226-
# Read the runtime bundle (schema v1) and project it into gate fields.
227-
info_provenance: dict = {}
222+
# Read the runtime bundle (schema v1) and project it into a typed RuntimeSample.
223+
sample = None
228224
benchmark_info: dict = {}
229225
config_mismatch: str | None = None
230226
observed_backend = None
@@ -234,19 +230,19 @@ def main() -> int:
234230
if perf_smoke_test_info_present:
235231
info_path = artifact_dir / "perf_smoke_test_info.json"
236232
bundle = load_info(info_path)
237-
if bundle is not None and is_runtime_bundle(bundle):
238-
info_provenance = to_gate_fields(bundle)
239-
benchmark_info = bundle_benchmark_info(bundle)
233+
sample = project_runtime(bundle) if bundle is not None else None
234+
if sample is not None:
235+
benchmark_info = sample.benchmark_info()
240236
observed_backend = backend_identity_from_benchmark_info(benchmark_info)
241237
runtime_contract, runtime_contract_hash = build_runtime_contract(
242-
provenance=info_provenance.get("provenance"),
243-
gpu_diag=info_provenance.get("gpu_diag"),
238+
provenance=sample.provenance,
239+
gpu_diag=sample.gpu_diag,
244240
backend=expected_backend,
245241
policy=runtime_policy,
246242
)
247243
runtime_info = build_runtime_publish_info(
248-
provenance=info_provenance.get("provenance"),
249-
gpu_diag=info_provenance.get("gpu_diag"),
244+
provenance=sample.provenance,
245+
gpu_diag=sample.gpu_diag,
250246
policy=runtime_policy,
251247
)
252248
config_mismatch = _config_drift(benchmark_info, launch_config)
@@ -257,38 +253,38 @@ def main() -> int:
257253
if config_mismatch and failure_phase is None:
258254
failure_phase = FailurePhase.CONFIG_MISMATCH.value
259255

260-
bench_result = {
261-
"task_id": task_id,
262-
"backend": backend_key,
263-
"physics_backend": physics_backend,
264-
"render_backend": render_backend,
265-
"backend_key": backend_key,
266-
"preset": preset,
267-
"attempt": args.attempt,
268-
"was_retried": args.was_retried,
269-
"exit_code": args.exit_code,
270-
"failure_phase": failure_phase,
271-
"stdout_tail": log_text[-2000:] if len(log_text) > 2000 else log_text,
272-
"wall_time_s": args.wall_time_s,
273-
"startup_time_s": info_provenance.get("startup_time_s"),
274-
"perf_smoke_test_info_present": perf_smoke_test_info_present,
275-
"raw_fps_mean": info_provenance.get("raw_fps_mean"),
276-
"raw_fps_std": info_provenance.get("raw_fps_std"),
277-
"raw_fps_min": info_provenance.get("raw_fps_min"),
278-
"raw_fps_max": info_provenance.get("raw_fps_max"),
279-
"benchmark_info": benchmark_info,
280-
"observed_backend": observed_backend.to_dict() if observed_backend else None,
281-
"config_mismatch": config_mismatch,
282-
"runtime_contract": runtime_contract,
283-
"runtime_contract_hash": runtime_contract_hash,
284-
"runtime_info": runtime_info,
285-
"gpu_diag": info_provenance.get("gpu_diag"),
286-
"provenance": info_provenance.get("provenance"),
287-
"launch_config": launch_config,
288-
"launch_config_hash": launch_config.get("launch_config_hash"),
289-
"benchmark_contract_hash": launch_config.get("benchmark_contract_hash"),
290-
"baseline_epoch": launch_config.get("baseline_epoch", 1),
291-
"task_config_snapshot": {
256+
bench_result = BenchResult(
257+
task_id=task_id,
258+
backend=backend_key,
259+
physics_backend=physics_backend,
260+
render_backend=render_backend,
261+
backend_key=backend_key,
262+
preset=preset,
263+
attempt=args.attempt,
264+
was_retried=args.was_retried,
265+
exit_code=args.exit_code,
266+
failure_phase=failure_phase,
267+
stdout_tail=log_text[-2000:] if len(log_text) > 2000 else log_text,
268+
wall_time_s=args.wall_time_s,
269+
startup_time_s=sample.startup_time_s if sample else None,
270+
perf_smoke_test_info_present=perf_smoke_test_info_present,
271+
raw_fps_mean=sample.fps_mean if sample else None,
272+
raw_fps_std=sample.fps_std if sample else None,
273+
raw_fps_min=sample.fps_min if sample else None,
274+
raw_fps_max=sample.fps_max if sample else None,
275+
benchmark_info=benchmark_info,
276+
observed_backend=observed_backend.to_dict() if observed_backend else None,
277+
config_mismatch=config_mismatch,
278+
runtime_contract=runtime_contract,
279+
runtime_contract_hash=runtime_contract_hash,
280+
runtime_info=runtime_info,
281+
gpu_diag=(sample.gpu_diag or None) if sample else None,
282+
provenance=sample.provenance if sample else None,
283+
launch_config=launch_config,
284+
launch_config_hash=launch_config.get("launch_config_hash"),
285+
benchmark_contract_hash=launch_config.get("benchmark_contract_hash"),
286+
baseline_epoch=launch_config.get("baseline_epoch", 1),
287+
task_config_snapshot={
292288
"task_id": task_id,
293289
"backend": backend_key,
294290
"physics_backend": physics_backend,
@@ -306,10 +302,10 @@ def main() -> int:
306302
"runtime_contract_hash": runtime_contract_hash,
307303
"baseline_epoch": launch_config.get("baseline_epoch", 1),
308304
},
309-
}
305+
)
310306

311307
out = artifact_dir / "perf_smoke_test_result.json"
312-
out.write_text(json.dumps(bench_result, indent=2))
308+
out.write_text(json.dumps(bench_result.to_dict(), indent=2))
313309

314310
status = (
315311
f"failure_phase={failure_phase!r}, perf_smoke_test_info_present={perf_smoke_test_info_present}, "

0 commit comments

Comments
 (0)