# euv runtime performance suite
This is the publishable **`euv-performance`** member of the root euv workspace. Package metadata, dependencies and build profiles are inherited from the root `Cargo.toml`; the member manifest declares no direct dependency paths or versions. It exercises workspace sources in a checkout and released framework, engine and chart dependencies when installed from the registry.
Local reports under `performance/results/` and the generated `performance/RESULTS.md` summary are ignored by Git and excluded by the root workspace package rules. They remain available on disk but are not source or release artifacts.
## Run
After publication, install the CLI with `cargo install euv-performance` and
run `euv-performance --suite all --output results.json` from any directory.
For a separate uninstrumented installation, pass `--no-default-features`
and a distinct `--root` to `cargo install`; do not compare modes using an
overwritten executable. The checkout commands are:
```bash
cargo run --release --manifest-path performance/Cargo.toml --offline -- \
--label candidate --output /absolute/path/to/results/candidate-allocations.json
cargo run --release --manifest-path performance/Cargo.toml --no-default-features --offline -- \
--label candidate --output /absolute/path/to/results/candidate-timing.json
```
The default is `--suite all`: **core, engine, chart, and mixed**, in that order. `mixed` invokes all three subsystems within every measured frame; its result is not the sum of three independently measured timings. The runner verifies actual invocation counts and fails when a required subsystem did not run.
The default build measures allocations. The `--no-default-features` build measures CPU timing with the recording allocator completely absent. Run both builds for a complete CPU/heap comparison; each build covers all four suites by default.
A short coverage smoke test:
```bash
cargo run --release --manifest-path performance/Cargo.toml --offline -- \
--ticks 2 --rounds 1 --warmup 1 --label smoke
```
Options:
- `--suite all|core|engine|chart|mixed` (default: `all`).
- `--ticks N`: measured frames per round (default: 100).
- `--rounds N`: repeated measured rounds (default: 7).
- `--warmup N`: unmeasured warmup frames (default: 20).
- `--scale N`: fixture-size multiplier, 1 through 16 (default: 1).
- `--label TEXT`: source/candidate identifier recorded in the report.
- `--output FILE`: JSON report; without this option JSON goes to stdout.
Human-readable round summaries go to stderr. Unknown options and invalid workloads return a nonzero exit code.
## Workloads
| Suite | Work performed per frame at scale 1 |
| --- | --- |
| Core | One real signal update and subscription notification; class and style snapshots combining 32 reactive string segments |
| Engine | Fixed-step broad-phase physics for 128 rigid bodies; update, count, capacity check, and draw-list recording for 2,048 live particles |
| Chart | Data update, axis/series layout, and display-list generation for three 2,048-point series (scalar, tuple, and object data) |
| Mixed | All of the above in the same frame on the same thread |
The fixtures are deterministic. Payloads, scene sizes, compiler profile, counters, warmup, and iteration counts are identical for baseline and candidate. Every sample records observed work counts and an output checksum so the compared runs can be checked for equivalent work.
## Measurement semantics
Initialization, warmup, and steady state are separate phases. Schema version 2 records the build's `mode` explicitly. Allocation mode records allocation/reallocation calls, allocated/freed requested bytes, signed live-byte change, and peak additional live heap bytes; it takes no frame clocks and leaves timing fields `null`. Timing mode records elapsed time, frames per second and nearest-rank P50/P95/P99 frame latency; the recording allocator and thread-local recorder are compiled out, and heap fields are `null`. A missing metric is never represented as a measured zero. Raw rounds are retained rather than reporting only a best result.
The allocator is a `System` wrapper with const-initialized **per-thread** counters. A successful reallocation counts its full new requested size as allocation traffic and its old size as freed traffic; it does not infer allocator-internal peak residency. A failed allocation is not counted as successful. Sample timing vectors are allocated before fixture measurement, and percentile sorting happens after the allocation snapshot.
`peak_extra_bytes` is the requested heap high-water mark **above the bytes already live at the start of that phase**. It is not the total heap size. `retained_after_drop_bytes` exposes bytes remaining after fixture drop; reactive signal slots intentionally retain their payloads until thread exit to keep stale `Copy` handles valid. This number is not automatically classified as a leak.
These are **native CPU/heap benchmarks**, not browser or GPU benchmarks. Chart text uses `TextMeasurer::approximate()`. No browser DOM patching, GPU upload/submission, GPU memory, process RSS, or end-to-end FPS is measured. CPU timings never come from the allocation-instrumented binary, because instrumentation overhead scales with allocation count and would distort the comparison. Isolated suites run serially to avoid cross-workload interference; mixed measures their shared-frame contention, not multi-core parallelism.
## Reproducible baseline comparison
Keep the baseline framework source unchanged and use this **same** harness and root build configuration in both snapshots. The root-manifest overlay below registers the benchmark workspace member, centralizes its dependencies and keeps compiler profiles and package-version metadata identical; it does not overwrite baseline Rust source. Replace the two absolute paths and the baseline reference below:
```bash
BASE=/absolute/path/to/euv-baseline
OUT=/absolute/path/to/results
mkdir -p "$BASE" "$OUT"
git archive <baseline-ref> | tar -x -C "$BASE"
cp -R performance "$BASE/performance"
cp -R automation "$BASE/automation"
cp Cargo.toml "$BASE/Cargo.toml"
cp Cargo.lock "$BASE/Cargo.lock"
CARGO_TARGET_DIR="$OUT/targets/baseline-allocations" cargo run --release --manifest-path "$BASE/performance/Cargo.toml" --offline -- \
--label baseline --output "$OUT/baseline-allocations.json"
CARGO_TARGET_DIR="$OUT/targets/candidate-allocations" cargo run --release --manifest-path performance/Cargo.toml --offline -- \
--label candidate --output "$OUT/candidate-allocations.json"
CARGO_TARGET_DIR="$OUT/targets/baseline-timing" cargo run --release --manifest-path "$BASE/performance/Cargo.toml" --no-default-features --offline -- \
--label baseline --output "$OUT/baseline-timing.json"
CARGO_TARGET_DIR="$OUT/targets/candidate-timing" cargo run --release --manifest-path performance/Cargo.toml --no-default-features --offline -- \
--label candidate --output "$OUT/candidate-timing.json"
```
Copying the root lockfile preserves third-party resolutions. Confirm that registry package versions/checksums and dependency edges match before comparing, and record the shared manifest overlay separately from the original source revision. Use the same machine, power/thermal state, scale, warmup, ticks, rounds, and compiler. Do not benchmark while compilation, tests, or other stress workloads are running. Repeat runs in alternating baseline/candidate order before interpreting small timing changes. Check all four suite names, configuration, work counts, and output checksums before comparing round medians. Label the exact source revision and preserve raw JSON locally with the report.
Use a distinct target directory for every source revision and build mode.
A shared target can retain a fresh Cargo fingerprint while another checkout
has overwritten the common executable path. Verify the executable hash, build
features and report's embedded `source_root` against the intended checkout;
a zero build exit alone does not establish which code was measured.
## Verification
The private-fixture tests are included in the library through `tests/mod.rs`.
`autotests = false` prevents Cargo from also compiling that module as an
independent integration-test crate; library unit tests remain enabled and run
in both benchmark build modes.
```bash
cargo test --manifest-path performance/Cargo.toml --all-targets --all-features --offline
cargo test --manifest-path performance/Cargo.toml --all-targets --no-default-features --offline
cargo clippy --manifest-path performance/Cargo.toml --all-targets --all-features --offline -- -D warnings
cargo clippy --manifest-path performance/Cargo.toml --all-targets --no-default-features --offline -- -D warnings
cargo fmt --manifest-path performance/Cargo.toml -- --check
```
Tests execute real mixed frames, verify default coverage and missing-subsystem rejection, check allocator accounting, and assert that warmed particle counting/capacity checks/rendering do not allocate an extra particle vector. A Rust-AST check also rejects direct field access in benchmark business code, including accesses inside formatting macros. Existing CI covers the benchmark through the workspace checks and additionally tests and lints its uninstrumented build.