Files

5.9 KiB

Performance campaign tools

These tools begin P0-1 in the campaign. They collect diagnostic evidence, not an automatic optimization acceptance verdict. See the experiment ledger for verified local artifacts and limitations.

Scoped host budget

Use a native Windows Python, not an MSYS path shim. For example, from the campaign worktree in PowerShell:

$python = 'C:\Users\Matthew\AppData\Local\Programs\Python\Python312\python.exe'
& $python tools/perf_host.py --core-percent 50 --affinity 0x1 --timeout 300 --report .local/host.json -- C:\path\ApeEscapeRecomp.exe --renderer opengl --no-launcher --debug-port 4680

Prepare isolated writable settings, saves, and memory cards before launch. The launcher inherits its working directory; run from the isolated game directory with an absolute script path when that is where the game expects its assets. Preserve the title's required DLL search path and BIOS/disc configuration.

--core-percent 50 budgets the entire job at half of one logical CPU equivalent; --cpu-percent 50 budgets half of the whole machine. Neither changes guest clocks. Affinity is a separate control; holding it at one CPU also removes parallelism. Compare identical affinity across runs, and separately test normal affinity. Omit both percentage options for unrestricted execution. On Windows, the child starts suspended, receives the job cap/affinity, then resumes hidden. Timeout/error cleanup kills only the launched job. Report JSON includes applied and read-back limits; stdout/stderr are separate adjacent log files.

Calibrate with a sustained CPU-time workload on each host. Short bursts can escape effective throttling. A cap does not reproduce older GPU, cache, memory, or power behavior. Keep real low-end machines in the acceptance matrix.

Diagnostic route capture

Start a compatible Release build with PSX_DEBUG_TOOLS=ON and a local debug port. Use the same effective settings, prepared overlays, BIOS, and isolated checkpoint across samples. Do not expose the debug server to untrusted hosts.

& $python tools/perf_campaign.py sample --port 4680 --route tools/perf_routes/ape_crabby_water_idle.json --restore-slot 1 --state-file .local/crabby-baseline/saves/scph1001/state_800A3660_slot01.pst --timeout 240 --out .local/A1.json --label A1 --provenance .local/target-A.json

The committed route references a local v5 state by SHA256; no game data is distributed. It holds neutral digital input for 7,200 native route ticks. This is the stationary water-facing workload, not the analog approach to the water. The runner rejects analog axes because this runtime's native route protocol does not support them. --timeout bounds route polling, not the entire tool invocation; individual TCP calls, snapshots, and restore have additional time.

--state-file hashes an operator-supplied file; it does not upload it or change the runtime's save directory. Verify that slot 1 actually resolves to that file. The runner waits for a successful completed restore generation, not just the staging acknowledgement. Without restore options, starting state is an operator precondition. Guest execution continues during sequential setup/snapshots, so this tool does not yet guarantee identical checkpoint-to-route guest work.

Samples retain raw before/after counters, route completion, phase attribution, and partial failure evidence. Disabled or missing GL timer data is unavailable, never zero. summary.route_wall_s includes route setup/status-poll overhead; the counter observation window is longer. Neither is exclusive CPU busy time. Paced route time alone will not reveal spare CPU headroom at full speed.

Target provenance and comparison

--provenance takes a JSON object with these required fields:

Field Meaning
artifact_sha256 SHA256 of the actual running executable
settings_sha256 SHA256 of a retained manifest covering all effective config, overrides, and mods
bios_sha256 SHA256 of the BIOS input
overlay_inventory_sha256 SHA256 of a retained, sorted inventory including generated/cache artifact hashes
host_id Stable host identity; retain OS, driver, actual GL device, power and display details alongside it
cpu_budget {"unrestricted": true} or applied {"cap": 313, "denominator": 10000, "affinity": "0x1"}
instrumentation Nonempty object recording build flags, profiler and diagnostic environment settings

Record affinity even for unrestricted runs. Optional variant_selector names the faithful/candidate selector. These declarations are operator evidence, not attestation of the running process. Keep build/source/toolchain manifests and host launcher reports with them. Samples without target provenance can be collected but cannot be compared by the A/B command.

Collect at least three nonoverlapping pairs in A1, B1, A2, B2, A3, B3 order:

& $python tools/perf_campaign.py ab --a .local/A1.json --b .local/B1.json --a .local/A2.json --b .local/B2.json --a .local/A3.json --b .local/B3.json --out .local/AB.json

Declare the primary metric and direction at capture time. Comparison requires matching routes, metrics, and environments, stable artifacts/selectors within each side, successful samples, and timestamp evidence of interleaving. It reports raw values, best values, and medians. --discard 2:compiler-activity removes the entire original second pair and retains its artifact references and reason; at least three pairs must remain. Never discard merely slow samples.

An accepted pair only means comparison eligibility. Correctness checkpoints, candidate hit witnesses, noise characterization, profiling-off production timing, stutter/audio measurements, and low-end hardware qualification remain separate required campaign gates.

Tests

& $python -m unittest tools.tests.test_perf_campaign tools.tests.test_perf_host tools.tests.test_stall_report