Repository navigation
CI perf: Gradle e2e shards unbalanced — b-p leg is the merge-queue critical path (~3 min/merge-group run) #1171
Description
Measurement
-
Merge queue. After Cut merge-group CI from ~46 to ~20 min: shard Gradle e2e and test legs, skip test-release in queue, cancel orphaned runs #1133 merged (2026-10-08 18:55Z), merge_group CI reaches
ci-okin 22–25 min. In 8 of the 9 non-cancelled runs since then, the last job to finish beforeci-okwas a Gradle leg ofe2e (ubuntu-latest, e2e_redirect_gradle_build | e2e_gradle_discovery_build …). Examples:- 37828932401: the 8.14.3 b-p leg ends at 23.1 of 23.2 min.
- 37822515034: the 8.14.3 leg ends at 24.3 of 24.4 min.
- 37828927026: the 8.14.3 leg ends at 23.6 of 23.7 min.
-
PRs. Since 10-08 02:16Z, an
e2e (ubuntu-latest, …)leg was the last job beforeci-okin 94 of 111 successful PR CI runs. PR push →ci-okp50 is 41 min and p90 53 min. -
Per-leg times. "Run e2e tests" step in three merge_group runs (37828932401, 37828929076, 37822515034), per Gradle line:
Shard Step time (s) hosted_[b-p]479–803. Slowest leg in 2 of 3 runs: 8.14.3 = 784 / 803, 7.6.6 = 750 hosted catch-all 388–696 agent + hosted_3/4/5 415–663 vendor 278–463. Finishes 5–8 min before the critical leg Job links: 8.14.3 b-p, 784 s, 7.6.6 catch-all, 694 s, 8.14.3 b-p, 803 s, 7.6.6 b-p, 750 s.
Where the time goes
Breakdown of job 113492360128, 817 s in total:
- Setup and checkout: 8 s. Downloading the e2e-bin artifact: 18 s. Gradle distribution: 2 s. Maven Central traffic: none (FakeCentral).
- Test step: 784 s (96%), with libtest's default 4 threads saturating 4 vCPUs. Test time adds up to 3,064 test-seconds, about 3.9 tests running at once.
Slowest tests:
| Test (Gradle version) | Seconds |
|---|---|
hosted_fallback_snippet_compiles_kotlin (8.14.3) |
363 |
hosted_config_cache_second_row (8.14.3) |
348 |
hosted_vendored_takeover_and_eject (7.6.6) |
287 |
hosted_multiproject_buildsrc_includebuild |
275 |
hosted_stale_lock_fails |
248 |
hosted_tamper_fails |
242 |
Most other hosted tests take 140–190 s.
In the agent leg, three suites (12 s, 200 s, 373 s) run back-to-back in a for loop, so each suite's tail is serialized.
Root cause
The shards from #1133 split by test-name prefix (ci.yml around lines 1240–1255, enforced by scripts/test_ci_gradle_prefixes.py), not by measured duration. Per Gradle line the total is about 1,900 s across 4 legs, which would be about 480 s each if balanced, but the heaviest leg takes 700–800 s.
Each test is slow by design: it uses a fresh GRADLE_USER_HOME and --no-daemon (gradle_build_common/mod.rs:283). Caching ~/.gradle would not help.
Proposed fix
- Rebalance by duration. Move about 700 test-seconds into the vendor leg:
hosted_config_cache_second_rowandhosted_fallback_snippet_compiles_kotlinfrom b-p, andhosted_vendored_takeover_and_ejectfrom the catch-all. Update the skip/filter lists andscripts/test_ci_gradle_prefixes.pyso that every test still runs in exactly one leg. - Run the agent leg's three suites concurrently (
&+wait, or onecargo testinvocation with multiple--test) instead of a sequentialforloop. That saves about 1 min on that leg. - Optional, needs a cost decision: use an 8-vCPU larger runner with
--test-threads=8for the two heaviest Gradle legs. The tests are CPU-bound, so this is an estimated ~6 min off the leg.
Expected saving
- Critical leg goes from about 13.5 to about 10.5 min. That cuts ~3 min of merge_group wall time (p50 ~23 → ~20 min) on every queue entry, and ~3 min of PR push→
ci-oklatency on Gradle-heavy runs. - Job-minutes: about neutral.
- Note that Decouple CI platform builds and share compatible Rust caches #1143 (start Linux e2e as soon as the Linux
e2e-buildis done) also shortens this path. With both changes, the Gradle legs stay the long pole, so this remains additive.
Coverage and risk
- Test selection is unchanged, only reassigned between legs.
test_ci_gradle_prefixes.pymust keep asserting full and disjoint coverage. - Required checks
ci-okandclippyare unchanged. - Leg times vary about ±20% run to run (8.14.3 b-p: 784 / 590 / 803 s), so balance on the p90.
Effort
S
ROI
ROI = critical-path saving × confidence / effort. Critical-path minutes are weighted at 1.0 per minute.
- 3 min × 0.7 confidence / effort S (1) = ROI 2.1
Activity
mikolalysenko commentedon Oct 8, 2026
[agent] Triaged as
priority:p3(CI-only). No open PR references this yet; it is not a duplicate of the other CI-perf reports (#1170–#1178 each target a different workflow cost).Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
Fresh numbers from the profiler run at 2026-10-09 04:16 UTC. Sample: 23 successful
CImerge_group runs since 18:00.Gradle e2e shard run time, p50 / p90 (n=22 each):
gradle_hosted_b_b--skip--skip_b--skip_b--skipci-okin 6 of 23 runs. Those runs were mostly between 19:00 and 21:00, while the queue was saturated: shard queue waits were 3.3–6.7 min. Since 22:50,coverage-docker (sbt)→coverage-mergehas been last instead (CI perf: coverage-docker (sbt) — 5.6-min image rebuild warms 3 unused toolchains; merge-queue critical path (~2.5 min/merge-group run) #1225).Generated by Claude Code
mikolalysenko commentedon Oct 11, 2026
CI profiler, 2026-10-11 12:17 UTC: resolved by #1375 (lean per-package-manager gate, merged 2026-10-09 21:29).
The 8 unbalanced Gradle shards no longer run on PRs or in the merge queue. Lean scope runs one Gradle e2e leg (
e2e_gradle_discovery_build e2e_gradle_agent_build e2e_redirect_gradle_build …) at p50 4.4 / p90 4.7 min (n=29 merge_group runs in the last 24h). The leg was the last job beforeci-okin 1 of 30 merge_group runs, against 6 of 23 when this issue was refreshed. The merge-queue critical path is nowe2e-build-windows(#1385), and a whole merge_group run takes p50 9.6 min, where one shard alone used to take 13.2. The full-shard matrix runs only nightly, on dispatch and in full scope, where shard balance doesn't gate merges. Closing as completed.Generated by Claude Code