benchmark: remeasure samples without marker-time RSS - #199
Merged
Merged
Conversation
The lifecycle benchmark samples OpenVMM's peak RSS once, when the restored guest prints OPENVMM-SNAPSHOT-RESTORE-OK. The guest then runs a prequeued `nvx-exit 0`, so OpenVMM can exit before the coordinator reads its counters. measure_once() originally swallowed the failed read and recorded 0, which performance collection rejected: value at .../acceptance.json:snapshot_restore.mshv.peak_rss_min_bytes must be positive and finite ca431cf (#192) avoided the zero by falling back to a sample taken just after launch, before the restore. On bare-metal hosts that sample was 0.004-0.76 MiB, while the marker-time value is about 36 MiB on KVM and 58 MiB on MSHV. Forcing the race on 3 of 11 KVM restores recorded 0.211, 0.016, and 0.094 MiB among 36 MiB samples, and validate-openvmm accepted the artifact. Instrumented bare-metal runs on Linux/KVM, Linux/MSHV, and Windows/WHP showed that no other reading can stand in for the marker-time sample: - Linux stops publishing VmHWM in /proc/<pid>/status once the unreaped process releases its address space: within 0.18-4.6 ms of the observed marker on KVM and 1.4-1.8 ms on MSHV. One natural KVM miss occurred when the coordinator dequeued the marker 4.5 ms after the reader thread queued it. Without a fallback, a 150 ms observer delay zeroed all ten restore samples on both backends and failed validate-openvmm as in CI. - wait4() ru_maxrss was typically 0.1-1.8 MiB below the marker-time VmHWM. It also covers the coordinator image that the child inherits before exec: a /bin/true child of a 200 MiB Python process reports 210 MiB. - Windows keeps a terminated process's counters readable through the retained Popen handle, so a late read succeeds. It returns the lifetime peak working set, which teardown raises by about 0.9 MiB (5%). Sample RSS explicitly at the measurement point instead: - live_peak_rss_bytes() returns the peak RSS only while OpenVMM is running and None once it has exited. Linux reads /proc/<pid>/status without polling, so the child is not reaped before the sample and its PID cannot be reused; a missing entry or VmHWM field means the address space is gone. Windows accepts a counter read only if the process is still running after the read. Non-positive values and unexpected errors are raised rather than masked. - measure_once() reports None instead of substituting a value. Teardown timing and the guest-exit status check are unchanged. - benchmark() discards and repeats a measured attempt without marker-time RSS, up to three attempts per sample. Each attempt reruns before_each, and only the accepted attempt's lifecycle profile is kept. The new peak_rss_remeasured_count field records discarded attempts, and three consecutive misses fail the benchmark without writing a result. Warmups report the RSS as unavailable. - The restore-memory workload, which does not prequeue a guest exit, rejects a missing marker-time RSS explicitly. Positive-value validation in performance collection is unchanged. Replace the ca431cf tests with coverage for a process that exits before the marker-time read, including a nonzero guest exit status; Linux zombie and missing /proc entries; Windows exits before and during the counter read; remeasurement and exhaustion in benchmark(); and collection of a remeasured artifact alongside rejection of a zero sample. Document the sampling contract in doc/benchmarks.md. Validated on bare-metal linux-kvm, linux-mshv, and windows-whp hosts using CI's lifecycle flags. Forcing the race on every fourth restore attempt discarded and remeasured three attempts and produced a valid acceptance artifact. Forcing it on every attempt failed the benchmark explicitly without writing a result. Natural runs with OpenVMM logging enabled and CI-equivalent runs recorded only positive samples without remeasurement. The final tree was revalidated on top of f0d103c with the v0.1.0-dev.31a5478d4e9f release artifacts (OpenVMM 9073d248). Fixes #193 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8bfbc823-031c-4ef3-af1b-4f1a094634c1
Copilot started reviewing on behalf of
Pedro Henrique Penna (ppenna)
September 23, 2026 01:29
View session
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The implementation satisfies #193’s acceptance criteria with focused cross-platform regression coverage.
Review effort: Balanced
Findings: None
What changed in this PR
Fixes #193 by remeasuring lifecycle samples when OpenVMM exits before marker-time peak RSS collection.
Changes:
- Adds live-process RSS sampling and bounded retries.
- Records discarded attempts while preserving accepted lifecycle profiles.
- Adds cross-platform regression tests and documents sampling semantics.
| File | Description |
|---|---|
scripts/nvx_tools/benchmark.py |
Implements live RSS sampling and remeasurement. |
scripts/test_nvx_tools.py |
Tests exit races, retries, and exhaustion. |
scripts/test_performance.py |
Verifies collection and zero-value rejection. |
doc/benchmarks.md |
Documents marker-time RSS behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Fixes #193.
Summary
The lifecycle benchmark samples OpenVMM's peak RSS once, at the restored guest's readiness marker. The guest then runs a prequeued
nvx-exit 0, so OpenVMM can exit before the coordinator reads its counters.This PR samples RSS only while OpenVMM is still running. If an attempt loses that race, the benchmark discards it and measures again instead of recording a substituted value. This replaces the post-launch fallback added by
ca431cf(#192). That fallback avoided zeros, but the values it recorded come from before the restore.Root cause
Instrumented runs on bare-metal Linux/KVM, Linux/MSHV, and Windows/WHP hosts showed:
VmHWMdisappears from/proc/<pid>/statusonce the unreaped process releases its address space. That happened within 0.18–4.6 ms of the observed marker on KVM and 1.4–1.8 ms on MSHV.0. With a 150 ms observer delay, all ten restore samples were zero on both backends, andvalidate-openvmmfailed as in CI.Popenhandle keeps a terminated process's counters readable. It returns the lifetime peak working set, which includes teardown.None of the other readings can stand in for the marker-time sample:
ca431cf)wait4()ru_maxrssVmHWM. It also includes the coordinator image the child inherits before exec: a/bin/truechild of a 200 MiB Python process reports 210 MiB.On KVM with
ca431cf, I forced the race on 3 of 11 restores. The acceptance artifact then recorded these restore peak-RSS samples (MiB):validate-openvmmaccepted the artifact (exit 0). MSHV likewise recorded 0.473, 0.160, and 0.277 MiB.Changes
live_peak_rss_bytes()returns the peak RSS only while OpenVMM is running, andNoneonce it has exited./proc/<pid>/statuswithout polling, so the child is not reaped before the sample and its PID cannot be reused. A missing entry orVmHWMfield means the address space is gone.measure_once()returnsNonefor peak RSS when OpenVMM exited first. Teardown timing and the guest-exit status check are unchanged.benchmark()discards and repeats such an attempt, up to 3 attempts per sample.before_each, and only the accepted attempt's lifecycle profile is kept.peak_rss_remeasured_countfield records discarded attempts.peak RSS=unavailable.performance.pyis unchanged.ca431cftests are replaced by coverage for:/procentries;benchmark();doc/benchmarks.mddocuments the sampling contract.Validation
Local (Windows):
ruff check,ruff format --check, strictpyrightfor Linux and Windows (0 errors), thevalidate-nvxunit suites (328 tests),test_adversarial.py, andtest_hosts.py.Bare-metal runs used CI's lifecycle flags:
To force the race, a runtime-only wrapper delayed selected samples by 150 ms. The wrapper is not part of this PR.
Final tree on
f0d103c, with releasev0.1.0-dev.31a5478d4e9f(OpenVMM9073d248):linux-kvm-baremetallinux-mshv-baremetalwindows-whp-baremetal--suite e2eEarlier iterations of this patch ran on
d2f7947with releasev0.1.0-dev.d2f79471ad83(OpenVMMdd529ed0). After review, the Windows sampler was tightened to also reject an exit during the read.OpenVMM exited before its peak RSS was sampled at the marker in 3 consecutive attempts for sample 1/1and wrote no result.