Repository navigation
ci: record benchmark host provenance and storage telemetry - #479
Merged
Pedro Henrique Penna (ppenna) merged 1 commit intoOct 10, 2026
Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
Pedro Henrique Penna (ppenna) merged 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
A transient counter-read failure can suppress otherwise available phase telemetry because endpoint samples are not selected per counter group.
1 open finding
What changed in this PR
Adds benchmark-attempt tracking, safe host provenance, and Windows storage telemetry to diagnose lifecycle performance instability.
Changes:
- Preserves and identifies lifecycle acceptance attempts.
- Records host/storage provenance and capture telemetry.
- Expands CI validation, summaries, tests, and documentation.
| File | Description |
|---|---|
.github/actions/run-benchmark/action.yml |
Retains attempts and publishes telemetry. |
.github/actions/validate-nvx/action.yml |
Runs telemetry tests. |
.github/skills/nvx-benchmark/SKILL.md |
Adds provenance verification. |
doc/benchmarks.md |
Documents telemetry and policy. |
doc/usage.md |
Documents new CLI options. |
scripts/nvx_tools/benchmark.py |
Collects provenance and telemetry. |
scripts/nvx_tools/host_telemetry.py |
Implements host and storage monitoring. |
scripts/nvx_tools/performance.py |
Tracks attempts and produces summaries. |
scripts/setup/README.md |
Documents storage-change policy. |
scripts/test_host_telemetry.py |
Tests telemetry implementation. |
scripts/test_nvx_tools.py |
Tests benchmark and CI integration. |
scripts/test_performance.py |
Tests attempt collection and reporting. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Windows/WHP lifecycle acceptance split snapshot generation into fast and slow clusters in runs 35382585623 and 35393916034, isolated to capture.mapped_memory_flush. #118 made such a series remeasure once, and b51a0b8 moved benchmark scratch off the burst-limited OS disk, but a future split would still leave three gaps (#119): - The published CSV could not be traced to the bounded attempt that supplied it; only an unlabeled copy in acceptance.json remained. - Results did not identify the runner, its Azure VM size and disks, or the volumes behind the workspace, temporary, and scratch directories. - Nothing recorded the host's disk, CPU, writeback, or Defender activity during the flush, so disk throttling, scanning, writeback, and unrelated host load could not be told apart. Attempt identification: - performance validate-openvmm --attempt-log appends each attempt's file, SHA-256 digest, outcome, exit status, and error to acceptance-attempts.json. - performance collect --lifecycle-attempt-log fails before it writes a CSV unless acceptance.json is a byte-identical copy of exactly one valid attempt, prints that attempt, and lists every attempt in the step summary. - Both acceptance steps remove a stale log and stale attempt files before measuring, and both publish steps pass the log. Host provenance: - The new nvx_tools.host_telemetry module records the runner and machine names, OS, CPU model, logical processor count, and memory, and the volume, file system, size, disk number, and SCSI address behind each benchmark directory. - On a possible Azure VM it queries the instance metadata service once, bypassing proxies, and keeps only the VM size and each managed disk's SKU, size, caching, write-accelerator, ephemeral, LUN, and Ultra Disk limit fields, plus the temporary disk size. It never records names, resource or VM identifiers, the subscription, tags, network addresses, or credentials, and records a failed query as azure.error. - Lifecycle JSON results and benchmark-metadata.json record it under host, acceptance and diagnostic suites print it, and summaries add a host table. The device-io resume identity ignores host. Storage telemetry: - With --snapshot-profile on Windows, a coordinator thread samples volume performance IOCTLs, GetSystemTimes, and SystemPerformanceInformation about every 50 ms, and Defender process CPU and I/O from SystemProcessInformation about every 500 ms, from just before the snapshot request until the source process exits. These interfaces need no privileges; the Network Service account of the CI jobs cannot read performance counters. Failed reads are recorded and never fail the benchmark. - Each retained capture maps its capture.mapped_memory_flush and capture.snapshot_generation phases onto the observer clock through the tightest record offset and summarizes throughput, IOPS, latency, queue length, busy time, CPU, writeback, dirty cache, and Defender activity. Each counter group spans its own samples nearest to each phase boundary, so a failed read does not blank the group, and reports its sampled interval when that differs from the phase's. - The job log prints each sample's flush with its host activity, and the step summary tabulates it for every attempt, rejected ones included. Linux captures flush no file-backed guest RAM image and record no storage telemetry. The benchmark documentation explains how to read the telemetry, keeps runner identity diagnostic only, and requires any storage change to apply to every pooled runner together with a history reset of the affected metrics. Validation: - On Windows 11 without elevation, the SystemProcessInformation offsets match GetProcessTimes and GetProcessIoCounters exactly, available pages match GlobalMemoryStatusEx, and a child process's mapped-view flush moved the flush counters by the bytes the volume wrote. - ruff check and format, compileall, and Pyright for Linux and Windows report no findings. The validate-nvx unit tests (932 on Windows), the adversarial (70) and host inventory (4) tests, the help smoke checks, and git diff --check pass. - On bare-metal Linux/MSHV (Azure Linux 3.0, Python 3.12) and Linux/KVM (Ubuntu, Python 3.14) hosts, test_host_telemetry, test_performance, and test_nvx_tools pass (554), including the bash acceptance-step test. The CI acceptance and performance-suite steps, run verbatim with CI inputs, recorded one valid attempt, and collect named it for all 29 metrics and listed the attempt and host in the summary. - On bare-metal Windows Server 2025/WHP, the verbatim acceptance step rejected attempt 1 as unstable (a 2/8 split between 488 and 725 ms), recorded it, and remeasured; collect named attempt 2 of 2 and reported both attempts' telemetry. That run showed that Windows Server keeps disk counters on demand: the volume IOCTL failed until the sampler held the disk performance WMI block open, after which the Windows counter test passes there too. - A rerun overlapped another session's cargo build of OpenVMM. During its flushes, CPU busy time rose to 7-70% (1-2% idle), the dirty cache to 205-450 MiB (0.1 MiB idle), lazy writes to 38 MiB/s, and write latency to 30-240 ms as flushes grew from 477 to 1509 ms. - Fourteen interleaved e2e runs on the otherwise idle host gave a snapshot-generation p50 median of 527 ms with telemetry and 522 ms without; cold start (476 ms) and restore (94 ms) matched. - In PR CI run 38067120152, the Azure Windows runner, whose jobs run as Network Service, recorded every counter group without errors, and collect named the published attempt. During each flush, the 512 GiB Premium_LRS (P20) scratch disk wrote 148-165 MiB/s at 94-100% busy, near that tier's 170 MB/s burst limit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/fix-issue-119-validate-implementation
branch
from
October 10, 2026 16:43
3d90017 to
e276144
Compare
Pedro Henrique Penna (ppenna)
deleted the
agents/fix-issue-119-validate-implementation
branch
October 10, 2026 19:15
This was referenced Oct 10, 2026
Pedro Henrique Penna (ppenna)
added a commit
that referenced
this pull request
Oct 11, 2026
PR #479 added scripts/nvx_tools/host_telemetry.py, which records benchmark host provenance and Windows capture storage telemetry, but the source tree did not list it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 task
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 #119.
Summary
Windows/WHP lifecycle acceptance has split snapshot generation into fast and slow clusters, isolated to
capture.mapped_memory_flush(runs 35382585623 and 35393916034). #118 made such a series remeasure once, and #203 moved benchmark scratch to a data disk, but the next split would still be hard to explain. This change makes every split self-describing:performance validate-openvmm --attempt-logappends each bounded attempt's file, SHA-256 digest, outcome (valid,unstable,invalid), exit status, and error toacceptance-attempts.json.performance collect --lifecycle-attempt-logfails before writing a CSV unlessacceptance.jsonis a byte-identical copy of exactly one valid attempt, prints the attempt that supplied the published metrics, and lists every attempt in the step summary. Both acceptance steps clear a stale log and stale attempt files first.nvx_tools.host_telemetrymodule recordshostin lifecycle JSON results andbenchmark-metadata.json: runner and machine names, OS, CPU model, logical processors, memory, and the volume, file system, size, disk number, and SCSI address behind the workspace, temporary, and scratch directories. On a possible Azure VM it queries instance metadata once, without a proxy, and keeps only the VM size and managed-disk SKU, size, caching, write-accelerator, ephemeral, LUN, and Ultra Disk limit fields. It never records names, resource or VM IDs, the subscription, tags, addresses, or credentials. Summaries add a benchmark host table.--snapshot-profile, each retained capture samples volumeIOCTL_DISK_PERFORMANCE,GetSystemTimes, andSystemPerformanceInformationabout every 50 ms, plus Defender (MsMpEng.exe,MpDefenderCoreService.exe) CPU and I/O fromSystemProcessInformationabout every 500 ms. These work for the Network Service account that runs CI jobs, which cannot read performance counters. Windows keeps disk counters on demand, so the sampler holds the disk-performance WMI data block (query access for every account) open while it samples. Each capture'scapture.mapped_memory_flushandcapture.snapshot_generationwindows are mapped onto the observer clock and summarized: throughput, IOPS, latency, queue length, busy time, CPU busy time, lazy-writer and flush rates, dirty cache, and Defender activity. Each counter group spans its own samples nearest to the phase boundaries, so one failed read cannot blank a group; a group whose samples differ from the phase's reports its own interval. The job log prints each sample's flush with its host activity; the step summary tabulates every attempt, rejected ones included. Failed reads are recorded and never fail a benchmark. Linux captures flush no file-backed RAM image and record none.doc/benchmarks.mdexplains how to tell disk throttling, scanning, writeback, and unrelated host load apart, keeps runner identity diagnostic-only, and requires any runner storage change to apply to every pooled runner together with a history reset of the affected metrics.Validation
Local (Windows 11, non-elevated):
SystemProcessInformationoffsets matchGetProcessTimesandGetProcessIoCountersexactly; available pages matchGlobalMemoryStatusEx; a child process's mapped-view flush moved the flush counters by the bytes the volume wrote.ruff check,ruff format --check,compileall, and Pyright (Linux and Windows) report no findings. Thevalidate-nvxunit tests (932), adversarial (70) and host-inventory (4) tests, help smoke checks, andgit diff --checkpass.Bare metal, with each CI step run verbatim from this revision's
run-benchmarkaction using CI inputs (--cpus 0-7, reserve 0, timeout 40, 1 warmup + 10 runs):The Windows run exposed that Windows Server keeps disk counters on demand: the volume
IOCTL_DISK_PERFORMANCEfailed (ERROR_FILE_NOT_FOUND) until something enabled collection. The sampler now holds the disk-performance WMI block open (its descriptor grants query access to Everyone and full access to Network Service), and the Windows counter test passes on that host.A rerun overlapped another session's
cargo build --release -p openvmm, and the telemetry captured it: during flushes, CPU busy time rose to 7–70% (1–2% idle), dirty cache to 205–450 MiB (0.1 MiB idle), lazy writes to 38 MiB/s, and write latency to 30–240 ms, as flushes grew from 477 to 1509 ms.Overhead: 14 interleaved e2e runs (7 pairs, both orders) on the otherwise idle Windows host gave a snapshot-generation p50 median of 527 ms with telemetry and 522 ms without; cold start (476 ms) and restore (94 ms) matched.
CI Azure runners (Standard_D8s_v5, jobs run as Network Service): in runs 38067120152 (
azure-windows-3) and 38068806476 (azure-windows-5), every counter group was recorded without errors, the IMDS provenance kept only the sanitized VM size and disk fields, and collect named attempt 1 of 1. In all 20 captures, the 512 GiBPremium_LRS(P20) scratch disk wrote 148–183 MiB/s duringcapture.mapped_memory_flushat 93–100% busy (average write latency 66–174 ms, queue 1.0–1.8), while CPU busy time stayed at 0–7%, the dirty cache stayed under 2 MiB, and Defender used at most 9.5% of one CPU. That rate is close to the P20 tier's documented 150 MB/s provisioned and 170 MB/s credit-based burst throughput, so the flush appears bound by the scratch disk rather than by host CPU, writeback, or scanning. A faster disk tier would be a storage change that falls under the history-reset policy above.The storage migration in #203 has aged out of the current 10-point Windows windows (all ~0.95–1.11 s), so no history reset is needed for it now.