Repository navigation
ci: shard the default microVM suite by scenario duration - #763
Merged
Merged
Conversation
6 tasks
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The planner leaves empty shards for valid duration and shard-count inputs, causing regeneration to reject its own output.
1 open issue
What changed in this PR
This PR reduces NVX CI turnaround by splitting each backend’s default microVM suite into two duration-balanced shards, while preserving guest, sandbox, and debug-kernel coverage.
Changes:
- Adds a checked-in partition, duration-based planner, and
test-microvm --shardsupport. - Runs shards through backend matrices with separate diagnostic artifacts.
- Adds partition and workflow validation tests, classifier updates, and documentation.
| File | Description |
|---|---|
scripts/test_nvx_tools.py |
Checks shard matrices, coverage, artifact names, and classification. |
scripts/test_microvm_tests.py |
Tests partition validation, planning, reports, and shard execution. |
scripts/nvx.py |
Registers plan-microvm-shards. |
scripts/nvx_tools/microvm_tests.py |
Adds default-suite selection and shard dispatch. |
scripts/nvx_tools/microvm_shards.py |
Implements partition loading, validation, and duration-based planning. |
scripts/nvx_tools/microvm_shards.json |
Stores backend-specific durations and shard assignments. |
scripts/nvx_tools/ci.py |
Classifies shard files as microVM-only inputs. |
doc/usage.md |
Documents shard execution and planner options. |
doc/ci.md |
Explains shard scheduling, artifacts, capacity, and regeneration. |
.github/workflows/run-nvx-microvm-tests.yml |
Accepts shard inputs and separates shard artifacts. |
.github/workflows/ci.yml |
Runs two parallel default-suite shards per backend. |
Review effort: Balanced
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/shard-microvm-suite
branch
from
October 22, 2026 15:46
e2c8862 to
bf0bfe3
Compare
The default Alpine suite of test-microvm runs its 36 scenarios one after another: 457.7 seconds on KVM, 416.0 on MSHV, and 601.5 on WHP, as the medians of the scenario duration reports (#694) of the 18 CI runs of October 21 and 22 that the partition lists add up. On WHP its job bounds every run that skips both OpenVMM suites (#696). No scenario depends on another, so CI can run the suite in parallel shards, as long as every scenario runs in exactly one of them and the shards take about as long as each other, whatever number of scenarios each runs. scripts/nvx_tools/microvm_shards.json is that partition: for each backend, the median duration of every default scenario, the CI runs whose reports gave them, and each shard's scenarios with their total. The backends differ too much for one partition: console-snapshot takes 49 seconds on KVM but 1 on MSHV and 4 on WHP, and console-exit 25 on KVM but 87.5 on WHP. The best partition common to all three that a search found left the longest KVM and MSHV shards about 5% above an even split, where one per backend balances each to within a second, so each backend has its own. The new plan-microvm-shards command writes the file. It takes each backend's median of every default scenario over the reports that it is given, sharded or not, of the Alpine guest on the default kernel with the default processor counts, from runs that ended; a report counts for a scenario only when every execution of it passed. Each scenario starts in its current shard, and one without a shard goes, longest first, to the shortest. A shard that is still empty, as one that --shards adds can be, takes the longest scenario of the longest shard that has more than one, since moving a scenario that takes no time into it would shorten no shard. The plan then moves a scenario, or swaps two of different shards, while that shortens the longest shard by a second or more, so that new medians that move within the run-to-run noise leave the shards alone. A scenario that no report measures keeps its duration, or takes an estimate from --seconds, and --shards changes a backend's number of shards. The first plan, from 13 of the runs, started from the two shards that #696 proposed, the restore, snapshot, and time ABI scenarios and the network, filesystem, and lifecycle ones, which were up to 18% apart on KVM, and a single swap per backend balanced each. Planned again from all 18, which add two later dev pushes, another pull request's run, and two runs of this change, every scenario stayed in its shard: 228.8 and 228.9 seconds on KVM, 208.0 and 208.0 on MSHV, and 300.4 and 301.1 on WHP. test-microvm --shard INDEX/COUNT runs one shard of the default suite. It fails before it runs a scenario unless the file puts every default scenario in exactly one shard of every backend and splits the backend's suite into COUNT shards. A scenario that a later change adds, as #661 added command-line-limit, therefore cannot drop out of a sharded run, and a run that counts a different number of shards than the file fails. The option takes no --scenario, --debug-kernel, or other guest. Only the microVM tests use the new module and its data, so the classifier leaves them out of the platform jobs' inputs, and doc/ci.md says so. New tests check that the checked-in file puts each default scenario in exactly one shard of each backend and is the planner's own plan of its durations, and that the shards of each backend together run the default suite that an unsharded run selects, each scenario once. They check that --shard fails before any scenario runs for a new, removed, doubled, or missing scenario, an empty shard, or a count that differs from the file's, and that it rejects other selections. They also cover the planner's balancing, its threshold, its placement of new scenarios and added shards, which it fills even with scenarios that take no time, its determinism, which reports it counts, the command's options and errors, and malformed files. The classification tests cover the new files. doc/usage.md documents the command and the option, and doc/ci.md how the shards are planned and how to plan them again. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each backend ran its default microVM suite in one job, which took 8.1 to 8.6 minutes on KVM, 7.6 to 7.7 on MSHV, and 10.4 to 11.1 on WHP in the first runs after #695 moved the guest and sandbox steps beside it. The WHP job bounds every run that skips both OpenVMM suites, which #430 wants done within 15 minutes, and it starts only when the Windows NVX CLI job ends, about 5.5 minutes into a run. The default-suite job of each backend is now a matrix of two shards, as scripts/nvx_tools/microvm_shards.json splits its suite, and each leg passes test-microvm --shard its matrix.shard and the matrix's strategy.job-total. A new shard input of run-nvx-microvm-tests.yml, with shard-count, selects the shard, and only the default-suite steps pass them on; every other step and job runs as before. A leg fails when the partition has another number of shards for its backend than the matrix runs, so the legs cannot leave a shard out. fail-fast is off, so a failing shard leaves the others to finish and keep their logs. Since a second upload of an artifact name in a run fails, each shard uploads its failure logs as nvx-microvm-tests-<backend>-shard-<n> and its duration report with -shard-<n> after the kernel. GitHub expands the matrix only once the job's condition holds, so the shards of a backend run together or not at all, under the same fork, draft, path-filter, and cancellation conditions as before, and Re-run failed jobs reruns the failed shards alone. The job keeps its name in the needs of Required status check, Publish development release, and Persist performance baseline, which read its result, and GitHub reports that result as a success only when every leg succeeded. A failed or cancelled shard therefore fails all three closed, as a failed leg of the openvmm-unit-tests or openvmm-vmm-tests-linux matrix does. Two shards per backend is the count at which a shard, about 0.7 minutes of setup and uploads plus half the suite, takes about as long as the guest and sandbox job beside it. In this change's own runs a shard took 4.4 to 4.5 minutes on KVM, 4.2 to 4.9 on MSHV, and 5.7 to 6.4 on WHP, and the guest and sandbox jobs took 3.7 to 4.1, 2.9 to 4.1, and 5.2 to 6.4 since #695. A third shard would end before that job on WHP, and on KVM and MSHV, whose four general runners the two shards, the guest and sandbox job, and the debug-kernel job fill, it would wait for a runner. The debug-kernel jobs, three to five minutes each, stay unsharded and keep their platform rules. A new test checks that each backend's matrix runs exactly the shards of its partition with fail-fast off, that the guest and sandbox and debug-kernel jobs pass no shard, and that the legs' test-microvm commands, parsed with nvx.py's parser, run every default scenario of the backend exactly once, on the Alpine guest and the default kernel with every processor count. The guest and sandbox test now compares that job with the suite's job apart from its shards, and it and the scenario duration test check that every leg of every microVM job uploads its failure logs and its report under a name of its own. The guest artifact test checks the downloads of each leg. doc/ci.md describes the shard jobs, their artifacts and results, the runners that they hold, and how to change their number. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/shard-microvm-suite
branch
from
October 22, 2026 16:26
bf0bfe3 to
000b1a1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Closes #696.
Part of #430.
Summary
Each backend ran its default Alpine microVM suite, 36 scenarios one after another, in one job, and the WHP one bounds every run that skips both OpenVMM suites. This PR splits each backend's default suite into two shards of about equal duration, planned from the per-scenario duration reports that #707 added, and runs each shard as a leg of the backend's
nvx-microvm-tests-<backend>matrix, in parallel on the backend'sgeneralrunners.scripts/nvx_tools/microvm_shards.json, which a newnvx.py plan-microvm-shardscommand generates from CI's duration reports, and which tests verify.test-microvm --shard INDEX/COUNTruns one shard, and fails before it runs a scenario unless every default scenario is in exactly one shard of every backend and the backend has COUNT shards. A new scenario, as openvmm: refuse kernel command lines that the guest kernel would truncate #661 addedcommand-line-limit, therefore can't silently drop out of CI.Why
The partition uses the per-backend medians of the default-suite reports of the 18 CI runs from October 21 and 22 that carry them, which the file lists: #707's and #706's PR runs, the
devpushes of0e73e460f,b5af359c9,911d5d86e,2724c69fc,c9aa40666, anda6785cd0b, the other PR runs since #707 merged, and this PR's first two runs.smp↔filesystem-comma-pathstime-abi-conformance↔directional-network-policysnapshot-core↔host-loopback-policyThe first plan, from the 13 runs available when I started, started from the two groups that ci: shard the default microVM suite by scenario duration #696 proposed (restore/snapshot/time ABI vs network/filesystem/lifecycle) and needed one swap per backend. Planned again from all 18 runs, as the documented regeneration does, every scenario stayed in its shard.
The backends differ materially:
console-snapshottakes 49.1 s on KVM, 1.0 on MSHV, and 3.9 on WHP;console-exit25.2, 27.2, and 87.5 s;private-network-ingress31.3, 31.6, and 17.4 s;managed-lifecycle4.2, 4.3, and 17.8 s. The best partition common to all three that a local search found left the longest KVM and MSHV shards about 5% (12 s) above an even split, so each backend has its own.Jobs in the three runs after ci: run the guest and sandbox steps beside the default microVM suite #706 split the guest and sandbox steps off (39779640214, 39785687509, 39792079980):
The WHP microVM jobs start about 4 to 5.5 min into a run, when
NVX CLI / Windowsends.The raw per-scenario table, with sample counts and ranges, and the timings of this PR's runs are in the comment on #696.
Changes
scripts/nvx_tools/microvm_shards.pyloads and checks the partition, reads scenario duration reports, and plans the shards. A report counts only for the default suite (Alpine, default kernel, default processor counts) of a run that ended, and only for scenarios whose every execution passed. Each scenario starts in its current shard; one without a shard goes, longest first, to the shortest; a shard that is still empty takes the longest scenario of the longest shard that has more than one; then the best move or swap is applied while it shortens the longest shard by at least a second, so noise in new medians doesn't reshuffle a balanced partition.scripts/nvx_tools/microvm_shards.json: per backend, the median of every default scenario, the runs whose reports gave them, and each shard's scenarios and total.nvx.py plan-microvm-shards [--report PATH]... [--seconds SCENARIO=SECONDS]... [--shards BACKEND=COUNT]... [--partition PATH]regenerates it.nvx.py test-microvm --shard INDEX/COUNT, with the runtime checks above; it takes no--scenario,--debug-kernel, or other--guest.ci.yml: eachnvx-microvm-tests-<backend>job is afail-fast: falsematrix,shard: [1, 2], which passesshard: ${{ matrix.shard }}andshard-count: ${{ strategy.job-total }}; jobs show asNVX microVM tests / <platform> / Shard N of 2.run-nvx-microvm-tests.yml: newshardandshard-countnumber inputs (default 0); only the default-suite steps pass--shard. Each shard uploads its failure logs asnvx-microvm-tests-<backend>-shard-<n>and its durations asnvx-microvm-durations-<os>-<backend>-default-shard-<n>-<run-id>-<run-attempt>.doc/ci.mddescribes the shards, how the partition is planned and regenerated, the shard jobs' results and artifacts, and the runners that they hold.Design notes
Required status check,Publish development release, andPersist performance baselinekeep readingneeds.nvx-microvm-tests-<backend>.result, which issuccessonly when every shard succeeded: a failed or cancelled shard fails all three closed, as a failed leg of theopenvmm-unit-testsoropenvmm-vmm-tests-linuxmatrix already does. Each leg derives COUNT fromstrategy.job-total, so a matrix that runs fewer or more legs than the partition has shards fails at run time, and a test checks that the matrix lists exactly shards 1..COUNT.devpushes and full runs, the two shards, the guest and sandbox job, and the debug-kernel job already fill the four general runners, so it would mostly queue.Tests
MicrovmShardTests(scripts/test_microvm_tests.py):--shardfails before any scenario runs, and writes no report, for a new, removed, doubled, or missing scenario, an empty shard, and fewer or more shards than the partition's; it rejects--scenario,--debug-kernel, another guest, and malformed values;test_ci_runs_each_default_microvm_suite_in_its_shards(scripts/test_nvx_tools.py): each backend's matrix lists exactly its partition's shards withfail-fast: false; its condition doesn't use the matrix; the guest and sandbox and debug-kernel jobs pass no shard; and each leg's resolvedtest-microvmcommand, parsed withnvx.py's parser, runs every default scenario of the backend exactly once on the Alpine guest, the default kernel, and every processor count.Validation
000b1a1d7, in run 39814103094, and before onbf0bfe346in 39807968692; the skipped jobs are the MSHV and WHP debug-kernel jobs, which pull requests skip, and the release and performance persistence, which onlydevpushes run. The first run, 39805377165, was superseded by the push that fixed the review finding, after all of its microVM jobs had passed. No job needed a rerun.a6785cd0b:ruff checkandruff format --check, andpyrightfor the Linux and Windows platforms: clean.scripts/test_nvx_tools.py(508 tests) andscripts/test_microvm_tests.py(237 tests): pass; the first commit's tree passes its own shard and workflow tests too.validate-nvx's Windows sequence (compileall, everyscripts/test_*.pymodule,test_adversarial.py,test_hosts.py, and the--helpchecks, plusplan-microvm-shards --help): passes, except 18test_setup_azurefailures in its Linux setup-script tests, which fail identically on an export oforigin/devon this host (its bash), so they're pre-existing and unrelated.check_markdown_links.pyandgit diff --check: clean.adversarial.yml's runner label, and thequeuekey ofconcurrency, which this actionlint doesn't know).plan-microvm-shardswithout reports reproduces the checked-in file byte for byte; with--shards whp=36it writes a valid partition.Measured effect
This PR's three runs are full-matrix runs, since it changes
ci.yml, so the OpenVMM suites (≈18–21 min) bound their wall time. Per backend, the default suite's lane runs from when its jobs became ready to when its last shard ended:e2c88625f, superseded after its microVM jobs)bf0bfe346)000b1a1d7)Minutes. Against the single default-suite job of the three runs after #706, measured the same way:
devpush held two of the four, and 2.5 and 3.5 min in the third, while another run held one; the guest and sandbox job waited 3.2 min in the second. Even so, KVM's lane was shorter than any of the three before.Platform / Windows / WHPdid, at 14.5 and 16.2 min, or about 15 and 16.5 with the regression gate and the required check after it. That job (8.9 min) starts afterNVX CLI / Windows(3.8–5.3 min) and waits for its pool's single benchmark runner (0.1, 2.0, and 5.5 min here), so it, not the microVM tests, now bounds those runs, at about 15 min when that runner is free.Expected effect
NVX CLI / Windows(≈4–5.3 min) followed byPlatform / Windows / WHP(≈8.8–8.9 min) on its single benchmark runner: about 14.5–15 min when that runner is free, which approaches CI: maximize workflow parallelism and reduce turnaround time #430's 15-min target; queueing on that benchmark runner, and on KVM's four general runners, is what can still push them past it.Post-merge verification
devruns after merge, against five comparable runs before it, on ci: shard the default microVM suite by scenario duration #696, and say whether runs that skip both OpenVMM suites now have a median of at most 15 min.