Skip to content

lab(ci): local HVF full-suite resource trend — cost is flat (B7 of #110) - #115

Merged
mobileskyfi merged 4 commits into
mainfrom
lab/76-full-suite-resource-trend
Jul 31, 2026
Merged

lab(ci): local HVF full-suite resource trend — cost is flat (B7 of #110)#115
mobileskyfi merged 4 commits into
mainfrom
lab/76-full-suite-resource-trend

Conversation

@mobileskyfi

@mobileskyfi mobileskyfi commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

B7 of #110. Evidence-producing bite for #76: does per-file cost inflate as the suite progresses (cumulative leakage), or stay flat?

Answer: flat. Full report in test/lab/full-suite-resource-trend/REPORT.md.

Result

12/12 files pass, 1818 s test time, 30.4 min wall, Intel Mac / x86_64 guest under HVF (CI-matching accel), QEMU 11.0.3, SHA 21ac47f, target stable.

Ratios against windows-x86 — the slowest CI platform that actually completes its job (run 30507484030) — over the 10 files carrying timing signal:

median range slope vs position
raw 0.78× 0.68×–1.98× +0.026× per file
VM-only (download excluded) 0.72× 0.66×–0.94× +0.002× per file

No resource accumulates either, across 343 samples at 5 s:

metric net over 30 min
free + inactive (reclaimable) −194 MB
wired + compressed (held) −84 MB
swap used −32 MB

Memory pressure stayed at level 1 (normal) for every sample. Peak QEMU concurrency never exceeded 1, and the settled post-suite sample shows 0 qemu-system processes — no file leaked QEMU into its successor.

The outlier was a cold download, not leakage

provisioning.test.ts ran 1.98× windows-x86 at position 10 of 12 — exactly where a cumulative-leakage story would want its evidence. It isn't. 619 s of its 992 s elapsed with zero QEMU processes running: it was downloading the pinned 7.20.7/7.20.8 images, each needing two retries. Excluding that leaves 373 s of VM work — 0.74×, in line with every other file — and it accounts for essentially the entire raw slope.

That makes this a locally measured demonstration of the #104/B3 confound, and direct support for B3 landing before B11 collects timing samples: with cold download uncontrolled, B11 would carry variance of this size and attribute it to whatever it varied on purpose.

What this does not show

Deliberately narrow, per B7's own instructions:

  • Rules out a deterministic local Intel/HVF full-suite defect. Nothing more.
  • Does not rule out a hosted-runner limit or a runner-only resource interaction — that is B8.
  • This laptop is 8 physical cores / 64 GiB; the hosted macos-15-intel runner is not, and that gap is uncharacterized here. The "no memory pressure" result must not be carried across to the runner.
  • Confirming the 27–32 min estimate (30.4 measured) sharpens CI: macos-x86 full suite wedges at position 10 in provisioning.test.ts — host-level runner loss, mechanism unexplained (HVF) #76's gap rather than closing it — on CI the same work still has ~30 minutes unaccounted for before the runner stops answering.

Contents

  • run.sh — reproduces the CI per-file loop (one bun test process per file, sequentially, same env knobs). Deliberately not bun test test/integration/: running the suite as one process would rule out per-process growth by construction and answer a question CI is not asking.
  • sample.sh — 5 s NDJSON resource sampler, attributing every sample to the running file via a marker file.
  • analyze.ts — folds timing + samples into the two questions; embeds the CI reference with the git show origin/ci-data:… command that produced it.
  • REPORT.md — findings, and the measurement caveats (APFS purgeable reclamation makes disk_free unusable in this run; per-file "QEMU at exit" was blind and orphan detection uses peak concurrency instead).

Verification

bun run check green (biome, tsc, markdownlint, cspell, examples, shellcheck). bun test test/unit/ 841 pass / 0 fail. No CI legs consumed — this bite is local by design.

Refs #76, #110.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a full-suite resource-trend experiment for measuring test-runner performance and system resource usage.
    • Added CI-aligned test execution with timing, memory, swap, load, disk, and QEMU process monitoring.
    • Added automated analysis and Markdown reporting for resource trends, per-file costs, and CI comparisons.
  • Documentation

    • Documented experiment setup, commands, outputs, analysis methods, limitations, and reference results.
    • Added a report covering a successful 12-test run and its findings.

#76 left two candidate explanations for the macos-x86 runner loss: per-file
cost inflating as the suite progresses, or flat cost with the time going
somewhere else. B7 distinguishes them on the one machine that can run the
CI-matching HVF baseline locally.

Adds test/lab/full-suite-resource-trend/ — a sampler plus a runner that
reproduces the CI per-file loop (one `bun test` process per file, sequentially,
same env knobs), because the shape is part of what is being measured. Running
the suite as a single process would rule out per-process growth by construction
and answer a question CI is not asking.

Result: 12/12 pass in 30.4 min, and cost is flat. Against windows-x86 — the
slowest CI platform that completes — the VM-only ratio has a slope of +0.002x
per file across 10 files. No memory accumulates either: free+inactive moves
-194 MB over 30 min, wired+compressed -84 MB, swap falls, and memory pressure
never leaves level 1.

The one apparent outlier was a cold download, not leakage. provisioning.test.ts
ran 1.98x windows-x86 at position 10 of 12 — exactly where a leakage story
would want it — but 619 s of its 992 s elapsed with zero QEMU processes,
downloading the pinned 7.20.7/7.20.8 images with two retries each. Excluding
that leaves 373 s of VM work, 0.74x, in line with every other file. That single
file is a locally measured demonstration of the #104/B3 confound and direct
support for B3 landing before B11 collects timing samples.

This rules out a deterministic local Intel/HVF full-suite defect. It does not
rule out a hosted-runner limit, and the laptop's 8 cores / 64 GiB do not
characterize the runner's — the pressure result must not be carried across.
The ~30 minutes unaccounted for on CI remain unaccounted for; that is B8.

Refs #76, #110.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 04:39
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6e1814c7-8127-4f25-855a-82151c9c6d6d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a macOS lab that runs the full integration suite, samples host resources, analyzes per-file and suite-wide trends, and documents a B7 HVF report. Added sampler-related terms to the project dictionary.

Changes

Full-suite resource trend lab

Layer / File(s) Summary
CI-shaped execution and resource collection
test/lab/full-suite-resource-trend/run.sh, test/lab/full-suite-resource-trend/sample.sh
The runner executes test files sequentially and records outputs, timings, metadata, and completion status. The macOS sampler emits NDJSON records for system and QEMU resources.
Resource and timing analysis
test/lab/full-suite-resource-trend/analyze.ts
The analyzer parses run artifacts, aggregates per-file metrics, calculates suite-wide slopes, detects QEMU process accumulation, and compares local timing with CI references.
Lab documentation and B7 findings
test/lab/README.md, test/lab/full-suite-resource-trend/README.md, test/lab/full-suite-resource-trend/REPORT.md, project-words.txt
The documentation describes the lab and its interpretation limits. The report records a successful 12/12 B7 run and its findings. Resource-related terms were added to the project dictionary.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant run.sh
  participant sample.sh
  participant IntegrationTests
  participant analyze.ts
  run.sh->>sample.sh: Start resource sampling
  run.sh->>IntegrationTests: Run integration files sequentially
  sample.sh-->>run.sh: Write NDJSON resource samples
  run.sh->>sample.sh: Stop sampling after the suite
  analyze.ts->>analyze.ts: Parse timings and samples
  analyze.ts-->>run.sh: Emit resource and timing reports
Loading

Possibly related issues

  • Issue 76: The lab implements the proposed CI-shaped HVF full-suite resource-trend experiment and per-file monitoring.

Possibly related PRs

  • tikoci/quickchr#10: Both changes address orphaned qemu-system-x86_64 processes in macOS test runs.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the local HVF full-suite resource trend experiment and its main finding.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lab/76-full-suite-resource-trend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ement

The CHR Image Caching section explained cache key mechanics but not that a
miss is large enough to dominate a per-file timing comparison. B7 (#110)
measured it: provisioning.test.ts ran 1.98x its windows-x86 CI cost purely
because two pinned images were uncached, and 62% of the file elapsed with no
QEMU running at all. At position 10 of 12 that is exactly where a cumulative-
leakage story would want its evidence, so the discriminator — was any
qemu-system process alive? — is worth stating where the caching rules live.

Refs #76, #104, #110.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new lab under test/lab/ to reproduce CI’s per-file integration-suite execution shape locally (Intel macOS/HVF) while sampling host resource metrics, producing an evidence report for CI issue #76 / umbrella #110.

Changes:

  • Add a new full-suite-resource-trend/ lab with a per-file runner, NDJSON sampler, analyzer script, and written report.
  • Document the new lab in test/lab/README.md.
  • Extend the cSpell project dictionary with lab/metrics vocabulary.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/lab/README.md Adds the new lab entry and explains the exception to the usual *.test.ts lab shape.
test/lab/full-suite-resource-trend/sample.sh macOS-only resource sampler emitting NDJSON records keyed by the currently-running file marker.
test/lab/full-suite-resource-trend/run.sh CI-shape per-file integration runner (one bun test per file), wiring the sampler and output artifacts.
test/lab/full-suite-resource-trend/analyze.ts Reads timing + samples and produces trend tables (position-vs-cost and monotonic resource climb).
test/lab/full-suite-resource-trend/README.md Usage and interpretation documentation for running and reading the lab output.
test/lab/full-suite-resource-trend/REPORT.md Captures the measured results and caveats for the referenced run.
project-words.txt Adds cSpell dictionary words used by the new lab/report.

Comment thread project-words.txt Outdated
Comment thread test/lab/full-suite-resource-trend/sample.sh
Two review findings on #115.

project-words.txt is lowercase throughout — APFS was the only uppercase entry
in 479 words, and cspell is case-insensitive here, so the entry works lowercase
and no longer risks a case-variant duplicate.

sample.sh interpolated the marker's contents into a JSON string unescaped. The
marker only ever holds a test filename or a "(...)" sentinel today, so nothing
was broken, but analyze.ts drops unparseable lines silently — an invalid sample
would be indistinguishable from a missing one, which is precisely the failure
mode this lab exists to avoid. Now escapes backslash and double-quote and
strips control characters; verified round-tripping a marker containing both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@project-words.txt`:
- Line 24: Replace the uppercase APFS entry with lowercase apfs in the project
words dictionary, preserving its existing sorted position and the file’s
lowercase acronym convention.

In `@test/lab/full-suite-resource-trend/README.md`:
- Around line 26-34: Update the `current-file` artifact description in the
README to include the `(after <file>)` marker between each completed file and
the next file, preserving the existing `(startup)` and `(done)` states and
clarifying that this intermediate value may appear in `samples.ndjson`.

In `@test/lab/full-suite-resource-trend/sample.sh`:
- Around line 34-39: Replace the eval-based assignment in the vm_stat parsing
block with direct variable assignment: have awk emit the four calculated values
as space-separated fields, then read them into VM_FREE, VM_INACTIVE, VM_WIRED,
and VM_COMPRESSED using read. Preserve the existing page-size conversion and
variable ordering without evaluating generated shell code.

In `@test/lab/README.md`:
- Line 21: Update the documentation link in the full-suite-resource-trend row of
test/lab/README.md to reference the existing
.github/instructions/ci.instructions.md document instead of the incorrect
relative ci.instructions.md path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5541282a-47c3-4285-a89f-ddc0076291f8

📥 Commits

Reviewing files that changed from the base of the PR and between 21ac47f and f62d6f9.

📒 Files selected for processing (7)
  • project-words.txt
  • test/lab/README.md
  • test/lab/full-suite-resource-trend/README.md
  • test/lab/full-suite-resource-trend/REPORT.md
  • test/lab/full-suite-resource-trend/analyze.ts
  • test/lab/full-suite-resource-trend/run.sh
  • test/lab/full-suite-resource-trend/sample.sh
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: Unit Tests (windows-latest)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

In Bun-based TypeScript code, use Bun.spawn(), Bun.write(), Bun.sleep(), bun:test, and ESM imports with .ts extensions.

Files:

  • test/lab/full-suite-resource-trend/analyze.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not turn a red integration test green by broadening timeouts, skipping it, or platform-gating it before reproducing and root-causing the failure.

Files:

  • test/lab/full-suite-resource-trend/analyze.ts
**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.ts: Use Bun APIs and tooling rather than Node.js equivalents: Bun.spawn(), Bun.write(), Bun.sleep(), bun test, and bun:test. Use ESM with .ts extensions in imports; do not use CommonJS.
For ARM64 virt machines, never use if=virtio for drives; use an explicit -device virtio-blk-pci,drive=drive0 configuration.
When using HVF acceleration, use -cpu host, not cortex-a710.
For arm64 guests on macOS, automatically select TCG with -cpu cortex-a710; HVF cannot run the CHR image's 32-bit ARM userspace on Apple Silicon. --accel and QUICKCHR_ACCEL must override this selection for testing.
UEFI pflash code and vars units must be identical in size.
QGA is x86-only; do not assume the guest agent starts for arm64 CHR.
Use tabs for indentation.
Do not add unnecessary comments to obvious code.
Errors must be thrown as QuickCHRError(code, message, installHint?).
Preserve the documented public API types and behavior: QuickCHR.start(opts) returns ChrInstance; ChrInstance provides stop(), remove(), rest(), monitor(), serial(), and qga(); and MachineState represents persisted machine.json state.

Files:

  • test/lab/full-suite-resource-trend/analyze.ts
🪛 ast-grep (0.45.0)
test/lab/full-suite-resource-trend/sample.sh

[error] 33-38: eval is invoked on a variable, parameter expansion, or command-substitution result, which re-parses the value as shell code. If any part of that value is attacker-controlled (arguments, environment, file contents, network output), it allows arbitrary command execution. Do not eval dynamic data: invoke the command directly with proper quoting (e.g. "$cmd" "$arg"), use arrays for argument lists (cmd=(prog --flag "$value"); "${cmd[@]}"), or restrict input to a validated allowlist before running it.
Context: eval "$(vm_stat | awk -v p="$PAGE" '
/Pages free/ {gsub(/./,"",$3); printf "VM_FREE=%d\n", $3p/1048576}
/Pages inactive/ {gsub(/./,"",$3); printf "VM_INACTIVE=%d\n", $3
p/1048576}
/Pages wired down/ {gsub(/./,"",$4); printf "VM_WIRED=%d\n", $4p/1048576}
/occupied by compressor/ {gsub(/./,"",$5); printf "VM_COMPRESSED=%d\n",$5
p/1048576}
')"
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(eval-on-variable-bash)

🔇 Additional comments (7)
test/lab/full-suite-resource-trend/run.sh (1)

1-87: LGTM!

test/lab/full-suite-resource-trend/sample.sh (1)

1-33: LGTM!

Also applies to: 40-58

test/lab/full-suite-resource-trend/analyze.ts (1)

1-347: LGTM!

test/lab/README.md (1)

22-31: LGTM!

test/lab/full-suite-resource-trend/README.md (1)

1-25: LGTM!

Also applies to: 35-77

test/lab/full-suite-resource-trend/REPORT.md (1)

1-137: LGTM!

project-words.txt (1)

210-210: LGTM!

Also applies to: 232-233, 249-249, 293-293, 309-313, 401-401

Comment thread project-words.txt Outdated
Comment thread test/lab/full-suite-resource-trend/README.md Outdated
Comment thread test/lab/full-suite-resource-trend/sample.sh Outdated
Comment thread test/lab/README.md
Two more review findings on #115.

sample.sh built shell assignments in awk and fed them to eval. vm_stat's output
is not attacker-controlled so this was not exploitable, but static analysis
flags it as CWE-78 and the pattern becomes an injection point the moment it is
copied onto a source that is. awk now emits four plain numbers consumed by
`read`. Verified against an independent vm_stat call: inactive and compressed
match exactly, free and wired differ under 1% from being sampled a moment apart.

The lab README listed the marker sequence as (startup) -> file -> (done), but
run.sh also parks it at "(after <file>)" between files — the fix that closes the
"QEMU at exit" blind spot. A reader grouping samples.ndjson by `file` needs to
expect that value, so both the marker row and the samples row now name it.

Declined: the SKILL Reference column in test/lab/README.md was flagged for
naming `ci.instructions.md` without its .github/instructions/ path. That column
holds bare code spans, not links, and every existing row uses a bare filename —
including qemu.instructions.md, which lives in that same directory. Changing
only the new row would make the table inconsistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mobileskyfi

Copy link
Copy Markdown
Contributor Author

Addressed in d679a21.

  • APFSapfs — already fixed in 60b4804 before this review landed; line 24 reads apfs and the dictionary now has zero uppercase entries.
  • (after <file>) marker state — valid, fixed. Both the current-file row and the samples.ndjson row now name it, since anyone grouping samples by file has to expect that value.
  • eval in sample.sh (CWE-78) — fixed. vm_stat output is not attacker-controlled so it was not exploitable, but the objection about copying the pattern onto a source that is stands. awk now emits four plain numbers consumed by read. Verified against an independent vm_stat call: inactive and compressed match exactly, free/wired differ under 1% from being sampled a moment apart.

Declined — ci.instructions.md path in test/lab/README.md. That column holds bare code spans, not links, so nothing resolves or 404s. Every existing row already uses a bare filename, and qemu.instructions.md (rows for slirp-hostfwd/ and www-abort-damage/) lives in the very same .github/instructions/ directory as the file flagged here. Pathing only the new row would make it the odd one out; if the table should carry full paths, that is a change to all ten rows and belongs in its own commit rather than riding along with a lab.

@mobileskyfi
mobileskyfi merged commit 1164b7f into main Jul 31, 2026
12 checks passed
@mobileskyfi
mobileskyfi deleted the lab/76-full-suite-resource-trend branch July 31, 2026 04:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants