Repository navigation
e2e: the Chainsaw catch budget counts only the global legs, not the per-suite collectors #3754
Description
hack/e2e-chainsaw/.chainsaw.yaml:57-59 records the worst-case diagnostics cost of a failed test as ~19m, and derives it from the two global error.catch legs: the previous-instance log read at timeout: 6m (:82) and the crust-gather snapshot at timeout: 13m (:133). The figure is exact for those two, and the block says outright that it exists so the arithmetic does not have to be rediscovered.
It does not count the catch collectors that individual suites add. Those run on the same failed test, in addition to the global legs. Counting them is easy to get wrong in the direction of undercounting, for two reasons that are worth stating before the table: a suite's catch can sit at test level (spec.catch) or on a single step (spec.steps[].catch), and a script op that omits timeout: does not run unbudgeted. It inherits timeouts.exec, which is 5m.
The enumeration below is (.spec.catch[]?, .spec.steps[]?.catch[]?) | select(has("script")) | .script.timeout over every hack/e2e-chainsaw/*/chainsaw-test.yaml, so it can be reproduced or refuted rather than taken:
| suite | catch script ops |
budget added |
|---|---|---|
harbor |
3, all test-level: one omitting timeout: plus two at 2m |
9m |
qdrant |
2: a 2m at test level and a 5m on the verify-pvc-reclaim-on-delete step |
7m |
bucket |
1 test-level, omitting timeout: |
5m |
openbao |
1 at 2m |
2m |
kubernetes-oidc-system |
1 at 2m |
2m |
kubernetes-oidc-customconfig |
1 at 2m |
2m |
So a failed harbor test can spend ~28m in diagnostics against the ~19m the block states, and a failed qdrant test ~26m.
The gap is not the 2m collectors. It is that harbor and bucket each carry one op with no timeout: at all, and an omitted budget is the largest single contribution while being the only entry a reader cannot see a number for. qdrant's second op is the same shape of surprise from the other direction: it is explicit at 5m, but it lives on a step rather than in spec.catch, so a count that walks only one of the two levels misses it entirely.
This matters because of what the same block says happens at the job ceiling: the E2E job is capped at 180 timeout-minutes, a cap ends the job cancelled, and the ~4m48s grace window is consumed by the earlier Collect report step, so both cozyreport.tgz and the job log are lost. That was measured, on runs 29557003429 and 29568562337. A cascade reddening many suites is the case the block already calls out as spending a large share of the job in diagnostics, and the per-suite collectors make each of those tests cost more than the recorded figure.
Three things worth deciding separately:
- Whether the recorded number should include per-suite collectors, or whether the block should state that it excludes them and where to look instead. The block's own closing advice, "bound the number of snapshotted tests rather than shrinking these timeouts", points at the first.
- Whether a
catchscript may omittimeout:here at all. Inheritingtimeouts.exec: 5mis what makesharborandbucketdominant while reading like the cheapest rows in the table. - Whether per-suite
catchshould be allowed at both nesting levels, given that the two have to be enumerated separately and a partial walk silently undercounts.
Pre-existing: harbor, qdrant, bucket and openbao all carry these at cfaf2e086. The two OIDC rows are the smallest entries in the table and are new; they change the total by 4m and nothing else about the shape of the problem. This is not a regression from a specific change. It is an accounting gap in a comment that is otherwise precise, and worth closing while the comment is still trusted.
Activity
lexfrei commentedon Sep 7, 2026
What that undercount costs at job level, measured on the OIDC pair while raising one of their budgets.
Once the BYO Ready wait goes to 20m (5m sat below the floor, which is the bootstrap hook's own 600s deadline), one failing suite costs 49m: 20m assert, 5m own catch, 6m and 13m for the two global legs, 5m cleanup that fails anyway because the uninstall cannot start while the install is still running.
Both suites pay that in the same run.
execution.parallel: 1makes them serial, the runner passes no--fail-fast, and both legs sit on theConfiguration'sspec.error.catch, so they are charged per failing test rather than once per run. The pair is 98m, and the trigger is ordinary: the release retries forever, so a single platform breakage reds both at full price.Composing that against the worst job observed in a 19-run window (138m) puts the total near 226m against the 215m cap. That is a stack of ceilings which have never fired together rather than a bound, so it is not a claim that a run has failed this way.
Correcting an earlier version of this comment, which softened the attribution in the wrong direction. Computed identically before that change, the same composition is 206m, because the three un-timed collectors it replaces already carried a 15m inherited ceiling, so a failing suite was 39m rather than free. Two things follow and neither replaces the other. 206 against the 215m cap is a fit, with nine minutes to spare, so the change is what crosses it: 226 against 215 is eleven minutes over. And 206 of the 226 is 91%, so almost all of the total was accumulated before, by the collectors this issue is about.
So the accumulated cost is where the work is, and that is this issue, but the crossing belongs to the change and should not be smoothed over. One 13m leg alone takes 26m off the both-fail path, which is more than the 20m the budget correction adds, so bounding the legs restores the fit on its own.