Skip to content

ci: speed up the integration setup step - #695

Open
kolyshkin wants to merge 3 commits into
containers:mainfrom
kolyshkin:ci-speedup-setup
Open

ci: speed up the integration setup step#695
kolyshkin wants to merge 3 commits into
containers:mainfrom
kolyshkin:ci-speedup-setup

Conversation

@kolyshkin

@kolyshkin kolyshkin commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Three changes:

  • Shallow-clone cri-o and bats. The cri-o clone is the only one in the script without a depth limit, and it is the largest repository involved. Only the default branch's tree is used (scripts/versions, the test suites, and the vendored ginkgo), so the history is fetched and discarded every run. bats is switched from clone-then-checkout to fetching the tag directly.

  • Cache GOCACHE and the module cache. The script builds runc, ginkgo, critest and crictl from source, and the cri-o job builds cri-o itself; every run recompiles the standard library and the vendored trees from scratch. setup-go's own cache is keyed on a go.sum this repository does not have, hence caching the two directories by hand. The key follows the versions pinned in the setup script, with restore-keys so that a version bump still gets an incremental first build, and includes the job name so the two jobs do not race for one cache entry.

    .github/zizmor.yml comes with it: zizmor's cache-poisoning audit fires because a tag push can trigger this workflow, and a poisoned cache could then reach a published artifact. This workflow publishes nothing -- the release build is in static.yml, which does not cache -- so the finding is ignored for this file only.

  • Oversubscribe the cores for the cri-o suite. test/test_runner.sh defaults JOBS to nproc, which is 4 on a hosted runner; the suite waits on cri-o startup, container state and stop timeouts far more than it computes, so it gets nproc * 2 instead. nproc * 4 was measured too and rejected: it is another 1.1 minutes, which does not stand out from the run-to-run spread.

Overall this saves about 5-6 minutes, or about 15%. Detailed measurements are in the comments below.

🤖 Generated with Claude Code

@kolyshkin
kolyshkin force-pushed the ci-speedup-setup branch 3 times, most recently from 0099ce2 to 3bf9f8c Compare August 28, 2026 20:54
@kolyshkin

kolyshkin commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Measurements

Baseline is the last five integration runs on main (15 jobs). The PR figures are the nine jobs of runs 33205171374 (attempt 2), 33210235032 and 33212882666, all with a confirmed cache hit. Medians, in seconds.

hack/github-actions-setup, by function:

main (n=15) PR (n=9)
cri-o clone 12 4
install_packages 24 24
install_runc 21 16
install_crun 49 47
install_testdeps 76 63
whole step 180 153
whole step, range 134-415 103-166

The median moves by 27 seconds. The range is the more useful column: the two main jobs that spent 166 and 254 seconds in the cri-o clone are what produced that 415-second worst case, and with --depth 1 the step now stays inside a 103-166 second band. That tail was the actual complaint.

On the Go cache, a correction to an earlier version of this comment. It first reported install_testdeps at 11s and install_runc at 6s, which came from a single run and does not hold up. Those numbers are the best case, and the reason is specific: that run was a re-run of the same commit, so the cri-o tree it cloned was byte-identical to the one the cache had been built against. The two later runs, on new commits and a few hours further along cri-o's main, get 63-68s and 15-18s -- still faster than the baseline, but not dramatically.

That is inherent to what is being cached. cri-o is cloned from its main and moves between runs, so the vendored trees drift out from under GOCACHE and progressively more of the build has to be redone. The cache is worth keeping -- it costs 5-9 seconds to restore and never makes anything slower -- but it should be sold as roughly half a minute off the median, not as the 75 seconds one lucky run suggested. The same applies to make -C "$CRIO_DIR" all test-binaries inside the cri-o job: 0.3m on the re-run, 1.6m on the runs where cri-o had moved.

Job totals: conmon 4.1m → 3.0-3.8m, cri-o (1) 5.1m → 3.7-5.5m. For cri-o (0), which is dominated by the bats suite rather than by setup, see the JOBS comment below.

@kolyshkin

kolyshkin commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

JOBS result

Three cri-o (0) runs on this branch, plus the baseline. All green, no test failed, none needed its retry.

JOBS tests parallel pass per test serial pass
main 33016232632 4 306 25.6m 5.0s 1.4m
run 2 33205171374 4 315 31.1m 5.9s 1.6m
run 3 33210235032 8 315 19.4m 3.7s 1.4m
run 4 33212882666 16 315 18.3m 3.5s 1.3m

nproc * 2 is where the win is: against run 2, which ran the same 315 tests, 31.1m to 19.4m, a 38% cut. cri-o (0) as a whole went 35.7m to 25.5m.

nproc * 4 was tried and rejected. It bought 1.1 further minutes, which is well inside the noise -- the two JOBS=4 runs above differ by 5.5 minutes on their own -- and there is no reason to run sixteen container test suites at once on a four-core runner for a difference that cannot be distinguished from variance. The multiplier stays at 2.

The caveat from the previous comment still applies: three runs is not a distribution, and this needs watching after it merges. What makes it credible for now is that the JOBS=8 gain (11.7m) is twice the spread seen between two identically configured runs, and that per-test cost fell from 5.9s to 3.7s while the serial pass, which is unaffected by JOBS, stayed flat at 1.3-1.6m across all four.

Two other things worth recording, both arguing for more samples rather than fewer:

  • Setup was 138s in run 2, 162s in run 3 and 167s in run 4, on a confirmed cache hit every time. Runner variance, not the cache.
  • make -C "$CRIO_DIR" all test-binaries took 0.3m in run 2 and 1.6m in run 3, also on a cache hit. cri-o is cloned from its main, so it moves between runs and the Go cache goes progressively stale against it. Either way it is about a minute, and not the thing worth optimising.

@kolyshkin
kolyshkin force-pushed the ci-speedup-setup branch 2 times, most recently from 199b413 to b84710d Compare August 28, 2026 21:56
kolyshkin and others added 3 commits August 28, 2026 14:58
The cri-o clone is the only one in this script without a depth limit,
and it is by far the largest repository of the lot. Only the default
branch's tree is used -- scripts/versions, the test suites, and the
vendored ginkgo -- so its history is fetched and thrown away on every
CI run.

Most of the time that is not expensive: across fifteen jobs on main the
clone took a median of 12 seconds, against 4 with --depth 1. The tail
is what hurts. Two of those fifteen took 166 and 254 seconds, and the
worse of them stretched the whole setup step to 415 seconds, against a
median of 180. Nine jobs into this branch the step has stayed within a
103 to 166 second band.

While at it, fetch the bats tag directly instead of cloning everything
and checking the tag out afterwards.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
The setup script builds runc, ginkgo, critest and crictl from source,
and the cri-o job then builds cri-o itself. All of that is Go, and all
of it recompiles the standard library and the vendored trees from
scratch on every run.

setup-go's built-in cache is keyed on a go.sum this repository does not
have, so cache GOCACHE and the module cache by hand instead. The key
follows the dependency versions pinned in hack/github-actions-setup, so
a bump gets a fresh cache; restore-keys still seeds it from the previous
one, which keeps that first build incremental. The job name is part of
the key so that the two jobs, which build different things, do not race
each other for a single cache entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KP78Qd5ev6FaneLGaqoWud
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
cri-o's test/test_runner.sh defaults JOBS to nproc, which is 4 on a
GitHub-hosted runner, and that is where the bulk of the job goes: the
parallel bats pass takes 25 to 31 minutes of a roughly 35 minute job,
while the serial pass that follows it takes under two.

The suite is not CPU-bound. Nearly all of that time is spent waiting
for cri-o to come up, for container state to settle, and for stop
timeouts to expire, so running more tests than there are cores mostly
fills in idle time. Over the same 315 tests the parallel pass goes from
31.1 minutes at JOBS=4 to 19.4 at nproc * 2, taking the whole job from
35.7 minutes to 25.5, with no test failing or needing the retry that
test_runner.sh already configures.

nproc * 4 was measured as well and is not worth it: 18.3 minutes, a
difference that does not stand out from the run-to-run spread, in
exchange for sixteen concurrent container test suites on a four core
runner.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
@kolyshkin
kolyshkin marked this pull request as ready for review August 28, 2026 23:02
@kolyshkin

kolyshkin commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator Author

Instead of the last patch, I opened an upstream PR: cri-o/cri-o#10285 which is more natural.

But let's keep it for now and we can revert it later once the above PR will land.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant