@@ -329,8 +329,40 @@ def _build_stale_image_section(skewed: list[tuple[str, str, DependencySkew]]) ->
329329 )
330330
331331
332- def _build_summary_markdown (rows : list [tuple ], * , blocking : bool ) -> str :
333- """Build reviewer-first Markdown for the sticky PR comment."""
332+ def _coverage (rows : list [tuple ]) -> tuple [list [str ], int ]:
333+ """Return ``(missing_labels, expected_total)`` for the configured matrix.
334+
335+ A bucket whose job died before ``build_bench_result`` uploads nothing, so it
336+ contributes no row. Grading only the rows that arrived would report an
337+ all-clear over a bucket that was never measured, which is exactly the class
338+ of silent green this gate exists to catch. Returns ``([], 0)`` when the
339+ matrix cannot be read, so this can never invent a failure.
340+
341+ Counts are over distinct buckets, not rows, so a duplicated artifact cannot
342+ inflate the total.
343+ """
344+ try :
345+ expected = {(task .task_id , task .backend_key ) for task in load_tasks ()}
346+ except Exception :
347+ return [], 0
348+ reported = {(result .task_id , result .backend ) for result , _ in rows }
349+ missing = sorted (f"{ task_id } /{ backend } " for task_id , backend in expected - reported )
350+ return missing , len (expected )
351+
352+
353+ def _build_summary_markdown (
354+ rows : list [tuple ], * , blocking : bool , missing : list [str ] | None = None , expected_total : int = 0
355+ ) -> str :
356+ """Build reviewer-first Markdown for the sticky PR comment.
357+
358+ Args:
359+ rows: ``(OracleResult, BenchResult)`` pairs for every scored bucket.
360+ blocking: Whether the gate is in blocking mode, for the mode banner.
361+ missing: ``task/backend`` labels that produced no result at all. Kept in
362+ the headline so the comment cannot disagree with the commit status.
363+ expected_total: Size of the configured matrix, for the "N of M" phrasing.
364+ """
365+ missing = missing or []
334366 counts = {verdict : 0 for verdict in OracleVerdict }
335367 for result , _ in rows :
336368 counts [result .verdict ] += 1
@@ -347,6 +379,11 @@ def _build_summary_markdown(rows: list[tuple], *, blocking: bool) -> str:
347379 overall = "❌ No benchmark results were produced"
348380 elif unexplained_failures :
349381 overall = "❌ One or more benchmarks failed before producing usable performance data"
382+ elif missing :
383+ # Ranked above skew/BLOCK/WARN: the rows that did arrive may all be clean,
384+ # but the change is not covered, so no all-clear may be printed.
385+ reported_n = expected_total - len (missing )
386+ overall = f"❌ Only { reported_n } of { expected_total } benchmark buckets reported — coverage is incomplete"
350387 elif skewed :
351388 overall = "⚠️ The CI image is stale for this PR, so some tasks could not be measured"
352389 elif counts [OracleVerdict .BLOCK ]:
@@ -359,20 +396,25 @@ def _build_summary_markdown(rows: list[tuple], *, blocking: bool) -> str:
359396 warnings_label = "warning" if counts [OracleVerdict .WARN ] == 1 else "warnings"
360397 blocks_label = "blocking signal" if counts [OracleVerdict .BLOCK ] == 1 else "blocking signals"
361398 failures_label = "benchmark failure" if counts [OracleVerdict .HARD_FAILURE ] == 1 else "benchmark failures"
362- count_summary = " · " .join (
363- (
364- f"✅ { counts [OracleVerdict .PASS ]} passed" ,
365- f"⚠️ { counts [OracleVerdict .WARN ]} { warnings_label } " ,
366- f"🚫 { counts [OracleVerdict .BLOCK ]} { blocks_label } " ,
367- f"❌ { counts [OracleVerdict .HARD_FAILURE ]} { failures_label } " ,
368- )
369- )
399+ count_parts = [
400+ f"✅ { counts [OracleVerdict .PASS ]} passed" ,
401+ f"⚠️ { counts [OracleVerdict .WARN ]} { warnings_label } " ,
402+ f"🚫 { counts [OracleVerdict .BLOCK ]} { blocks_label } " ,
403+ f"❌ { counts [OracleVerdict .HARD_FAILURE ]} { failures_label } " ,
404+ ]
405+ if missing :
406+ # Name them: "8 of 9" alone leaves the reviewer guessing which task is
407+ # unmeasured, and a missing bucket is the one a regression can hide in.
408+ count_parts .append (f"🚫 { len (missing )} did not report ({ ', ' .join (missing )} )" )
409+ count_summary = " · " .join (count_parts )
370410 mode = (
371411 "**Blocking:** BLOCK and HARD FAILURE results fail the check."
372412 if blocking
373413 else (
374- "**Advisory:** this job stays green whatever the verdict. A BLOCK or HARD FAILURE still shows up"
375- " as a red `perf-smoke-test` commit status and in this table -- it just does not fail the PR."
414+ "**Advisory:** this job stays green whatever the verdict. Anything that is not a clean pass --"
415+ " a BLOCK, a HARD FAILURE, or a bucket that never reported -- still shows up as a red"
416+ " `perf-smoke-test` commit status, and the reason is in the overall result above."
417+ " It just does not fail the PR."
376418 )
377419 )
378420
@@ -425,7 +467,13 @@ def _write_github_output(**values) -> None:
425467
426468
427469def _verdict_outputs (
428- rows : list [tuple ], * , has_block : bool , has_hard_failure : bool , blocking : bool , expected_buckets : int | None = None
470+ rows : list [tuple ],
471+ * ,
472+ has_block : bool ,
473+ has_hard_failure : bool ,
474+ blocking : bool ,
475+ missing : list [str ] | None = None ,
476+ expected_total : int = 0 ,
429477) -> dict [str , str ]:
430478 """Derive the reported verdict and the `perf-smoke-test` commit status from the rows.
431479
@@ -440,8 +488,9 @@ def _verdict_outputs(
440488 has_hard_failure: Whether any bucket failed to produce a usable measurement,
441489 excluding failures already excused as a stale CI image.
442490 blocking: The gate's ``blocking`` setting, used only to label the status.
443- expected_buckets: How many buckets the matrix should have produced. When
444- fewer reported, the gate says so instead of grading the survivors.
491+ missing: ``task/backend`` labels that produced no result. Shared with
492+ :func:`_build_summary_markdown` so the commit status and the PR
493+ comment cannot disagree about coverage.
445494
446495 Returns:
447496 The ``overall_verdict`` / ``status_state`` / ``status_description`` /
@@ -453,7 +502,7 @@ def _verdict_outputs(
453502 # most bench jobs never uploaded, would otherwise fall through to an
454503 # affirmative "no regression detected" over measurements that never happened.
455504 unmeasured = [result for result , _ in rows if result .verdict == OracleVerdict .HARD_FAILURE ]
456- missing = max ( 0 , ( expected_buckets or 0 ) - len ( rows ))
505+ missing = missing or []
457506
458507 if has_hard_failure :
459508 verdict = OracleVerdict .HARD_FAILURE
@@ -465,7 +514,7 @@ def _verdict_outputs(
465514 description = f"perf-smoke: no usable measurement for { len (unmeasured )} bucket(s); CI image looks stale"
466515 elif missing or not rows :
467516 verdict = OracleVerdict .HARD_FAILURE
468- description = f"perf-smoke: only { len (rows )} of { expected_buckets or '?' } buckets reported a result"
517+ description = f"perf-smoke: only { expected_total - len (missing )} of { expected_total } buckets reported a result"
469518 elif has_block :
470519 verdict = OracleVerdict .BLOCK
471520 description = "perf-smoke: blocking-level performance regression detected"
@@ -703,7 +752,10 @@ def main() -> int:
703752 baseline_update_failed = True
704753 print (f"::error::Baseline push failed: { exc } " )
705754
706- summary = _build_summary_markdown (rows , blocking = blocking )
755+ # One source of truth for coverage, shared by the summary/comment and the
756+ # commit status so the two surfaces cannot contradict each other.
757+ missing_buckets , expected_total = _coverage (rows )
758+ summary = _build_summary_markdown (rows , blocking = blocking , missing = missing_buckets , expected_total = expected_total )
707759 print ("\n ## Performance Smoke Results\n " )
708760 print (summary )
709761 print ()
@@ -729,25 +781,15 @@ def main() -> int:
729781 app_config = args .omni_app_config ,
730782 )
731783
732- # How many buckets the matrix should have produced. The gate always runs the
733- # full matrix, so a shortfall means bench jobs died without uploading -- which
734- # must not be graded as a pass over the survivors. Left as None (check
735- # disabled) if tasks.json cannot be read, so this can never fail a run by
736- # itself.
737- try :
738- expected_buckets = len (load_tasks ())
739- except Exception as exc : # pragma: no cover - defensive
740- print (f"[aggregate] Warning: could not determine expected bucket count: { exc } " )
741- expected_buckets = None
742-
743784 output_values = {
744785 "baseline_read_sha" : baseline_read_sha ,
745786 ** _verdict_outputs (
746787 rows ,
747788 has_block = has_block ,
748789 has_hard_failure = has_hard_failure ,
749790 blocking = blocking ,
750- expected_buckets = expected_buckets ,
791+ missing = missing_buckets ,
792+ expected_total = expected_total ,
751793 ),
752794 }
753795 if baseline_push_result :
0 commit comments