Repository navigation
microvm tests: record each scenario's duration in a JSON Lines report - #707
Open
Pedro Henrique Penna (ppenna) wants to merge 3 commits into
Open
Pedro Henrique Penna (ppenna) wants to merge 3 commits into
Pedro Henrique Penna (ppenna) wants to merge 3 commits into
Conversation
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues were identified in reporting, scenario integration, tests, or artifact collection.
No open issues 🎉
What changed in this PR
Adds per-scenario timing reports to NVX microVM tests, providing data for future duration-balanced CI sharding.
Changes:
- Flushes progress lines and incrementally records timing, outcomes, and run metadata.
- Uploads reports from every microVM job regardless of outcome.
- Documents the report format and adds reporting and workflow tests.
| File | Description |
|---|---|
scripts/test_nvx_tools.py |
Tests report uploads and artifact naming. |
scripts/test_microvm_tests.py |
Tests timing, progress flushing, and failure handling. |
scripts/nvx_tools/microvm_tests.py |
Integrates reporting into scenario execution. |
scripts/nvx_tools/microvm_durations.py |
Implements incremental JSON Lines timing reports. |
doc/usage.md |
Documents report fields and lifecycle. |
doc/project-structure.md |
Lists the new reporting module. |
doc/ci.md |
Documents timing artifact collection. |
.github/workflows/run-nvx-microvm-tests.yml |
Uploads reports regardless of job outcome. |
Review effort: Balanced
test-microvm announces each scenario with a "Running ..." line on stdout, which Python buffers in blocks when it writes to a pipe, as on the CI runners. The lines therefore reached the job log in bursts. In run 39585909588, the WHP log received the default suite's 41 progress lines in two: 16 when the managed-lifecycle scenario flushed its Cargo command, 2 minutes 24 seconds into the step, and the other 25 when the process exited, 7 minutes 39 seconds later. The log's timestamps could not attribute the suite's 10 minutes to its scenarios. Print each progress line with flush=True, as the benchmarks print theirs, so that it reaches the log when its scenario starts. A test runs every scenario with stubs, each of which requires that stdout has flushed everything printed so far, its own progress line last. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
test-microvm timed nothing finer than the CI step that ran it: in run 39585909588, the default Alpine suite took 603 seconds on WHP and 459 on KVM, with no breakdown by scenario. Sharding the suite by duration (#696) needs each scenario's own time. Write scenario-durations.jsonl into --output-dir. A header names the schema and its version, and the run's start, guest, backend, kernel type, processor counts, selected scenarios, and, in GitHub Actions, the run and job. One record per span of the run follows, appended as soon as the span ends, so that a run that fails, is interrupted, or is killed keeps every span that it completed. The spans follow one another without gaps from the start of the Python process, which Linux reports in /proc/self/stat and Windows through GetProcessTimes: the setup, until the first scenario starts; one span per scenario execution; and the teardown, which sums up the time ABI evidence and records the run's outcome. They therefore add up to the command's time, with the suite's setup apart from the scenarios. Each scenario record names its scenario, guest, backend, kernel type, and the processor counts that it covers, with its start, its duration, and its outcome: passed, failed with the error, or interrupted. The dispatch moves into _run_scenarios, which runs inside the report's with block, and each scenario starts its span by printing its progress line through the report, which still flushes it. The report has a module of its own, microvm_durations.py, whose names a later aggregation can share. Tests drive the report with fake clocks through the spans' contiguity, timestamps, and appends, failures and interruptions in a scenario and in the setup, the replacement of an earlier report, and a report that cannot end without masking the run's error, and check the GitHub identity and the process start on Linux and Windows. Through the runner, they check one record per execution of every scenario with its processor counts, and the records that precede a failed scenario. doc/usage.md describes the format. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The microVM workflow uploaded its test results only when a job failed, so a passing run kept no record of where its time went, and the duration reports that test-microvm now writes would stay behind on the runners. Sharding the default suite by scenario duration (#696) needs them from many runs, passing or not. Add one step to the reusable workflow, which every microVM job runs, the debug-kernel ones too. It uploads the report of each test-microvm step, build/test-results/*/scenario-durations.jsonl, whether the job passes, fails, or is cancelled, as the 30-day artifact nvx-microvm-durations-<os>-<backend>-<kernel>-<run-id>-<run-attempt>, whose name differs for every job and attempt, so that each attempt keeps its own reports. The failure-log upload stays as it was. A test pins the step's condition and path, checks that the path's pattern finds the report of every test-microvm step and that the name differs for each of the six jobs that call the workflow and each attempt, and keeps the failure-log upload to failures. doc/ci.md describes the upload, and doc/usage.md points to it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/microvm-scenario-durations
branch
from
October 21, 2026 19:59
f12c2cf to
a904264
Compare
This was referenced Oct 21, 2026
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 #694.
Part of #430.
Summary
test-microvmnow flushes each scenario's progress line, and writesscenario-durations.jsonl, a JSON Lines report of how long each scenario execution takes, into its--output-dir, appending each record as soon as its span ends. Every microVM job, the debug-kernel ones included, uploads the reports of itstest-microvmsteps whether it passes, fails, or is cancelled, so that #696 can shard the default suite by measured duration.Why
test-microvmprinted itsRunning ...lines through block-buffered stdout. In the WHP job of reference run 39585909588, the default suite's 41 progress lines reached the log in two bursts: 16 at 2 min 24 s, whenmanaged-lifecycleflushed its Cargo command, and the other 25 when the process exited, 7 min 39 s later. The suite's 603 s on WHP and 459 s on KVM had no finer breakdown.Changes
microvm tests: flush each scenario's progress line: the 38Running ...prints ofrun(), one per scenario, passflush=True, as the benchmarks' progress lines do.microvm tests: record each scenario's duration in a JSON Lines report:scripts/nvx_tools/microvm_durations.py, holdsDurationReport.run()keeps its setup and opens the report around the dispatch, which moves unchanged into_run_scenarios(); each scenario starts its span withreport.start(scenario, progress line, processors=...), which writes the previous span's record and then prints the progress line, flushed;doc/usage.mddocuments under "Scenario duration report":runheader:schemanvx-microvm-durations,schema_version1,started_at,guest,backend,kernel(defaultordebug), theprocessorscounts, the selectedscenarios, andgithub, the job's repository, workflow, run ID and attempt, job, event, ref, SHA, runner name, and runner OS, ornulloutside GitHub Actions;setup, from the Python process's start (/proc/self/staton Linux,GetProcessTimeson Windows) until the first scenario, onescenarioper execution, andteardown, which sums up the time ABI evidence and carries the run's outcome;started_at,seconds,outcome(passed,failed, orinterrupted), anderror; ascenariorecord also hasscenario,guest,backend,kernel, andprocessors: the counts that the execution covers, such as[4]forsmpon 4 vCPUs, all of them forsmp-snapshotandrestore-processors, ornullfor a scenario that takes none;teardown; a killed run has noteardown.ci: upload the scenario duration reports of every microVM job: oneif: always()step at the end ofrun-nvx-microvm-tests.yml, which all six microVM jobs run, uploadsbuild/test-results/*/scenario-durations.jsonlasnvx-microvm-durations-<os>-<backend>-<kernel>-<run-id>-<run-attempt>, kept 30 days. The failure-log upload is unchanged.A report looks like this, cut to a few lines:
{"record":"run","schema":"nvx-microvm-durations","schema_version":1,"started_at":"2026-10-21T14:21:02.007Z","guest":"alpine","backend":"whp","kernel":"default","processors":[1,2,4,8],"scenarios":["console-exit","...","workload-identity"],"github":{"repository":"microsoft/nvx","workflow":"CI","run_id":"...","run_attempt":"1","job":"test","event_name":"pull_request","ref":"refs/pull/.../merge","sha":"...","runner_name":"azure-windows-6","runner_os":"Windows"}} {"record":"setup","started_at":"2026-10-21T14:21:02.007Z","seconds":0.812,"outcome":"passed","error":null} {"record":"scenario","scenario":"guest-boot","guest":"alpine","backend":"whp","kernel":"default","processors":null,"started_at":"2026-10-21T14:21:02.819Z","seconds":3.215,"outcome":"passed","error":null} {"record":"scenario","scenario":"console-exit","guest":"alpine","backend":"whp","kernel":"default","processors":[1],"started_at":"2026-10-21T14:21:06.034Z","seconds":5.902,"outcome":"passed","error":null} {"record":"teardown","started_at":"2026-10-21T14:31:41.951Z","seconds":0.204,"outcome":"passed","error":null}Decisions
schema_versionchanges only when a reader of version 1 would misread a report.Interactions with other pull requests
command-line-limitscenario to the default suite. This branch is rebased onto it: the first commit flushes its progress line, and in the second it starts its own span, so the report records it like every other scenario. The runner tests enumerateMICROVM_TEST_SCENARIOS, so they cover it, and they fail if it isn't recorded.guests-and-sandboxesinput. Both jobs would then run this upload step under the same artifact name, and the second upload would fail. Whichever of the two merges second must add${{ inputs.guests-and-sandboxes && '-guests' || '' }}to the artifact name, as ci: run the guest and sandbox steps beside the default microVM suite #706 does for its failure logs, and extendtest_every_microvm_job_uploads_its_scenario_durationsto the new jobs.microvm_tests.py, and nvx-rs: hold the guest port permission for one transaction at a time #662, which merged after it, merges cleanly.Tests
scripts/test_microvm_tests.py:kernel, and the records kept before a failed scenario;DurationReportwith fake clocks: exact spans, timestamps, and sums, each record on disk as soon as its span ends, LF line ends, failures (also with an empty or a 5,000-character message), interruptions, a failure before the first scenario, a run without scenarios, replacement of an earlier report, and a report that cannot be completed without masking the run's own error;/proc/PID/statparser with a command name that holds spaces and parentheses, and the process age on Linux and Windows.scripts/test_nvx_tools.py: the upload step's condition and path, that its pattern finds the report of everytest-microvmstep, that its name differs for each of the six jobs that call the workflow and each attempt, and that the failure-log upload still runs only on failure.Validation
f2b4a11ef:test_nvx_tools(490 tests) passes;test_microvm_tests(222 tests) passes, apart fromtest_host_loopback_allow_script_checks_publication_and_traffic, whose Git for Windowsshruns exceeded their 10-second timeout while other jobs loaded the host; it passes on its own, and this PR doesn't touch it. The module also passes at each of the first two commits, with the same exception;test_performance,test_guest_binaries,test_development_release,test_host_telemetry,test_time_abi,test_tool_versions,test_markdown_links,test_adversarial, andtest_hostspass, as docompilealland every--helpcheck ofvalidate-nvx;test_setup_azure, which runs the Linux setup scripts, fails on this host ondevtoo, and this PR doesn't touch it;ruff check,ruff format --check,pyrightfor Linux and Windows,check_markdown_links.py, andgit diff --checkpass.test_microvm_teststests pass, including the process age from/proc/self/stat.run-nvx-microvm-tests.ymlandci.ymlare clean; the only finding across the workflows is the existing unknown runner label inadversarial.yml.a904264bc: every check passes; the MSHV and WHP debug-kernel jobs, both OpenVMM suites, the release, and performance persistence skip, as pull requests and these inputs expect. The KVM, MSHV, and WHP microVM jobs and the KVM debug-kernel job each uploaded a report for everytest-microvmstep, 14 in all, whose spans account for each step's time within 0.04 s on Linux and 0.48 s on Windows, where Windows PowerShell starts before Python and exits after it. Each default-suite report records 42 executions,command-line-limitamong them. Run 39619765285 onf12c2cf57, before the rebase, gave the same results. #694 has the per-step accounting and the per-scenario durations of both runs.devatc6ea9e27d, after nvx-rs: hold the guest port permission for one transaction at a time #662 changedrun_snapshot_core:git merge-treereports no conflict, andtest_microvm_tests(223 tests) passes on the merged tree.Expected effect
test-microvmstep, with per-scenario durations for ci: shard the default microVM suite by scenario duration #696 to aggregate across runs.Post-merge verification
devpush.