Repository navigation
ci: investigate why the Windows OpenVMM builds miss sccache #693
Copy link
Copy link
Description
Parent: #430, workstream 2.
Summary
The Windows OpenVMM builds hit sccache far less often than the Linux ones, and the misses don't depend on the cache size. Find which compilations miss and why, fix what can be fixed, and break down the roughly 14 minutes that the WHP VMM-test job spends before its tests start.
Data
Rust hit rates from sccache --show-stats in four full-matrix dev pushes (39592496133, 39585909588, 39472818902, and 39254314492):
| Job | Rust hit rate | Cache size after the job |
|---|---|---|
| OpenVMM unit tests / Windows | 77.7-77.8%, and 18.7% once | 4-10 GiB |
| OpenVMM vmm-tests / Windows / WHP | 82.6% | 4-10 GiB |
| OpenVMM unit tests / Linux | 99.3% | 8-10 GiB |
| OpenVMM vmm-tests / Linux / KVM | 99.7% | 5-7 GiB |
- The jobs ran on several runners, and each runner keeps its own cache on local disk (
C:\actions-runner\_work\_sccacheon Windows) with a 10 GiB maximum. The setup scripts setSCCACHE_CACHE_SIZE=10G, andvalidate-runnerchecks it. - The rates repeat almost exactly across runners and cache sizes, so the same compilations probably miss every time. ci: cut OpenVMM and CLI work from the critical path and the WHP pool #436 attributed the misses to a full cache, but the same rates appear when the cache holds 4 or 7 GiB.
- The 18.7% case ran on
azure-windows-1, whose cache held 4 GiB, which suggests that runner hadn't built the unit tests recently. There are nine WHP runners (ci: dedicate the benchmark runners to the platform jobs #649), and since ci: run the OpenVMM suites on dev pushes only when their inputs change #660 the OpenVMM suites run less often, so any one runner's cache sees each build less often. - In the reference WHP VMM-test job: 1,779 compile requests, 1,174 hits, 247 misses, and 324 non-cacheable calls (273 because of their crate type). The average compilation took 3.4 seconds.
Tasks
- List the crates that miss in consecutive runs on the same runner, for example with
SCCACHE_LOG=debug, and find what changes in their arguments, environment, or inputs. - Fix the causes that NVX controls, and report those in OpenVMM's build upstream.
- Break down the WHP
test-openvmmstep into flowey's own build,restore-packages, downloads, compilation, linking, and test execution. - Evaluate a cache shared by each pool's runners, which sccache supports through remote storage backends, against the per-runner local caches.
- Revisit
SCCACHE_CACHE_SIZEonce the misses are understood.
Acceptance criteria
- The causes of the Windows misses and the step's breakdown are recorded here.
- Any fix reports hit rates and step times before and after, over at least five comparable runs.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
ppenna commentedon Oct 21, 2026
Findings
The NVX-side fix is #714. The data below come from the 25 Windows OpenVMM jobs of
devpushes from October 19 to 21 (table at the end) and from one diagnostic run on a temporary branch, 39616159470. That run built each suite twice in one job on the same WHP runner withSCCACHE_LOG=debug, and uploaded the sccache logs and the hashes of every proc-macro DLL.Why the Windows builds miss
_LINK_=/Breproconfig.rsinHashMaporderProc macros, Windows only. sccache can't cache proc macros, so every build compiles and links them again, and it keys every compilation on the contents of the files that it names with
--extern. MSVC'slink.exestamps each DLL with the time of the link and a random PDB signature unless it gets/Brepro, so every proc macro came out different in every build, and every crate that uses one directly missed. On Linux, the linker reproduces the shared library exactly. In the diagnostic run, without/Brepro(job,azure-windows-3):--cfgflags, which sccache sorts. But all 99 proc-macro DLLs differed between the builds;--extern, and none of the 1,174 hits did;bitfield_struct(80 misses) andasync_trait(70), thenmesh_derive(18),paste(11), andwindows_implementandwindows_interface(10 each). The costliest wereflowey_lib_hvlite(45 s),openvmm_core(35 s),petri(17 s), andnetvsp(15 s): 791 s of compiler time per job;openvmmandpipettein target directories of their own, and thelight, host, and target builds compile some crates separately. The misses were spread over seven target directories (119 intarget/openvmm, 54 in the artifact-discovery build, 39 intarget/light, and the rest in four others), andzerocopy,thiserror, andfutures-utileach missed seven times per job.With
_LINK_=/Brepro(job,azure-windows-6), the 99 DLLs were byte-identical between the two builds, and the second build hit 1,417 of 1,421 compilations (99.72%). The unit tests (job,azure-windows-1) went from 293 to 7 misses (99.47%). All 29 WHP VMM tests and 4,933 unit tests passed in every build. A local reproduction with the pinned sccache 0.18.0 and Rust 1.95.0 behaves the same way: only the crate that names the proc macro misses, and/Breprofixes it. ci: run the OpenVMM unit tests off the hypervisor runners #697's experiments corroborate it on another machine: onwindows-latest, a second build with the sccache that the first had just filled still missed the same 293 of 1,318 compilations (39615224333, details).smoltcp, all platforms. OpenVMM pins smoltcp 0.12.0, whose build script collects its configuration in a
HashMapand writesOUT_DIR/config.rsin the map's iteration order, which changes in every process.smoltcptherefore misses in every build, and so doconsomme,net_consomme, andopenvmm_resources, whosesmoltcpdependency changed: these are the 4 misses that remain with/Brepro, and probably Linux's 4 as well. smoltcp sorts the output since smoltcp-rs/smoltcp@e4b5b68, released in 0.14.0, so this needs an OpenVMM dependency update; NVX can't fix it.Probes. The build scripts of
proc-macro2,anyhow, andthiserrorcompile probes that fail on stable Rust by design. sccache counts them as failed compilations and never caches them: 17 per VMM-test job and 8 per unit-test job, at about 0.2–0.3 s each.Cold runners. Each runner's cache lives in
_work\_sccacheon its OS disk. The four unit-test jobs with 1,072 misses (18.66%) ran onazure-windows-6,-8,-4, and-1, whose caches held 3–4 GiB after the job, although-4and-1had held 10 GiB the evening before. Their caches had been wiped, consistent with the VMSS image upgrades that ci: recover Windows runners after VMSS image upgrades #669 handles, which reimage the OS disk. Their 246 hits match thelightbuilds of flowey andxtask, which the VMM-test jobs share. Such a job took a median of 1,039 s instead of 876 s.Not causes: the cache size, since the same 247 and 293 compilations missed at 4 to 10 GiB; paths and the environment, which were identical between consecutive builds, with
CARGO_HOMEat the same_work\_temp\cargo-homein every job; and C or C++. cc-rs finds MSVC'scl.exeitself, without sccache, so the Windows statistics show no C or C++ requests; its objects are reproducible, and its static libraries differ only in archive member timestamps, which sccache ignores.Breakdown of the WHP
test-openvmmstepThe reference job, the median of the 13 WHP VMM-test jobs below, and the second build with
/Breproin the diagnostic run:/Breprocargo xflowey,lightprofile)restore-packagesrelease)sample-vmgs.vhd)openvmmbuild, intarget/openvmmpipettebuild, intarget/pipetteguest_test_uefibuildxtaskbuild), nextest archive, and test listinglto = "thin"anddebug = true, so the final crate of each executable does the program's codegen and its link, which sccache can't cache. In the/Breprobuild, they took 188 s for the VMM tests'testsexecutable, 193 s foropenvmm, 10 s each forpipetteandxtask, and 3 s forflowey_hvlite: about 405 of the 641 s.rustc --emit=dep-infopass each (564 s in total across the 8 vCPUs), 99 proc macros and 172 build scripts and executables that sccache can't cache, and the 4 smoltcp misses. Before the fix, the misses added 791 s of compiler time per job.openvmm's final crate, and the ~405 s of final codegen and linking remain in any job structure. A test profile without ThinLTO or with line-table debug information would shorten the latter, but it would change the binaries that the tests exercise, so both are OpenVMM's decisions.The Windows unit-test step:
xtaskbuild for the fuzz-crate query 134 → 55 s, test build 418 → 339 s, test listing 76 → 59 s, tests 45 s, doctest build 104 → 42 s, and doctests 99 s; the whole step 876 → 643 s (median of the 8 warm jobs below, then the second build with/Brepro).First builds with
/BreproEach runner's first build with
/Breprostill misses the affected crates once, because the new DLLs give them new keys. In #714's run 39626778440, the WHP VMM tests onazure-windows-1, which already held thelightentries from the diagnostic run, hit 86.00% (199 misses), spent 43 s instead of 111 s on flowey's build and 798 s before the tests started, and passed all 29 tests. The unit tests onazure-windows-4hit 77.77%, as before, in their first/Breprobuild there. In the next run, 39641375387, the WHP VMM tests onazure-windows-9missed 238 compilations (83.25%), exactly like the diagnostic run's first/Breprobuild, because the target directories now share the proc-macro DLLs, and the unit tests onazure-windows-7missed 293. Later builds on a runner should hit like the diagnostic run's second builds; the five-run comparison will be added here after #714 merges.Shared cache
SCCACHE_MULTILEVEL_CHAIN=disk,azure)gha)C:\actions-runner\_workon every Windows runner and/opt/nvx-runner/_workon every Linux one, soSCCACHE_BASEDIRSisn't needed. With/Brepro, the proc-macro DLLs are identical across runners too.devbuild on that runner computes, including in the OpenVMM producers that feed the development release. A shared cache that pull requests can write would extend that from one runner to the pool. Writes must therefore come only fromdevpushes and full runs, through an identity that pull-request jobs can't obtain: two federated credentials,repo:microsoft/nvx:ref:refs/heads/devwith Storage Blob Data Contributor andrepo:microsoft/nvx:pull_requestwith Storage Blob Data Reader andSCCACHE_AZURE_RW_MODE=READ_ONLY. The local tier keeps today's per-runner exposure.disk,azurewith OIDC federation and read-only pull requests, with key prefixes per operating system and a lifecycle rule of 14–30 days. Theghabackend needs no Azure resources and isolates pull requests by design, but it would evict the repository's other caches and is likely to be throttled. Either is a maintainer decision about Azure resources, identities, and repository variables.SCCACHE_CACHE_SIZEKeep 10 GiB for now. The misses never depended on the size, and the working set of both Windows suites fits: a wiped runner's cache held 3–4 GiB after one unit-test job, and runners that had run both suites held 5–10 GiB. Until #714, each job also wrote 240–290 entries that no later job could hit, about 650 MiB per Windows unit-test build by #697's measurement, which filled the caches to the limit within a dozen or so jobs and made sccache evict live entries; with
/Brepro, a job writes only the 4–7 smoltcp entries. During the jobs,C:had about 70 GB free (51.9–60.4 GB used of 126–127 GB). Revisit after the post-merge runs: if a runner stays at 10 GiB and warm jobs miss more than the smoltcp chain, raise the limit to 20 GiB in both setup scripts,validate-runner, and their tests together, and rerun the setup on every runner before merging, sincevalidate-runnerrequires the exact value. A larger local cache doesn't survive a reimage.Follow-ups
build-openvmm-binary.yml) and thenvx-rsbuilds on the Windows runners also link proc macros withlink.exeand would gain the same way, but ci: link the Windows OpenVMM test builds with /Brepro for sccache #714 leaves them out: the producer builds only when its binary cache misses, and/Breprowould change the publishedopenvmm.exe, which needs its own decision. Setting_LINK_=/Breproin the runner environment would cover every build, but it needs the setup scripts,validate-runner, and every runner updated together.Draft upstream report for microsoft/openvmm (not filed)
Title: Update smoltcp to 0.14 so that builds are reproducible and cacheable
smoltcp 0.12.0's
build.rscollects its configuration in astd::collections::HashMapand writesOUT_DIR/config.rsby iterating it, so the order of the generated constants changes in every build process. The generated source therefore differs between two builds of the same revision, and build caches that hash sources, such as sccache, misssmoltcpevery time.consomme,net_consomme, andopenvmm_resourcesmiss after it, because thesmoltcprlib that they depend on changed.In NVX's CI, with
SCCACHE_LOG=debug, two consecutive builds of the VMM tests on the same Windows runner gave these four crates different cache keys while every other key matched oncelink.exegot/Brepro(https://github.com/microsoft/nvx/actions/runs/39616159470). They miss 4 times per VMM-test build and 7 times per workspace unit-test build, on Linux too.smoltcp sorts the output since smoltcp-rs/smoltcp@e4b5b68179 ("Sort generated config.rs outputs"), released in 0.14.0. Proposed fix: update the workspace dependency to
smoltcp = { version = "0.14", default-features = false }and adaptconsommeto the API changes between 0.12 and 0.14.The 25 Windows OpenVMM jobs of
devpushesTime is the VMM-test time before tests start, or the whole unit-test step, in seconds.
ci: link the Windows OpenVMM test builds with /Brepro for sccache
ci: link the Windows OpenVMM test builds with /Brepro for sccache