Track the existing (untracked) stress matrix, extend the signal-size axis into the multi-fragment regime now that the UDPSClient reassembly cap is 1 MiB, and wire stress results into the E2E PDF report via an opt-in --stress flag (table + per-axis scaling curves + regression vs prior run). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.2 KiB
Stress Suite Report Integration — Design
Date: 2026-06-26
Context
A capacity/stress suite already exists in Test/E2E/chain/ but is untracked in
git and not wired into the report:
stress.py— declarative matrix, 27 cases across 7 load axes, all valid.stress_run.py— orchestrator: runs each case, captures cpu/peakRSS viaproc_perf.snapshot(), zoom p50/p95 latency, gates survival+liveness (hard) and RSS/zoom-p95 (soft), writesstress_results.json.run_stress.sh— standalone wrapper (build + env + invokestress_run.py).client/main.go—stressmode (liveness + zoom-latency recorder).
The suite is functional (smoke-tested ds_size_1000 → PASS end-to-end). The report
pipeline (run_chain_e2e.sh, collect.py, report_build.py, E2E_Report.typ) has
zero stress references.
This work covers the user's three requested stress axes — signal size, client
count, source count — which already map onto existing matrix axes
({ds,hub}_signal_elements, hub_ws_clients / ds_subscriber_hubs,
hub_source_count), plus the report integration.
Separately, the UDPSClient reassembly cap was just raised from 64 KB to 1 MiB
(UDPS_CLIENT_MAX_PACKET_BYTES = 1048576), with exact assembledBytes delivery.
The stress matrix still enforces a now-outdated sub-64 KB single-datagram ceiling.
Goals
- Extend the signal-size axis into the multi-fragment regime (exercise the fixed reassembly path) and lift the outdated 64 KB validation cap.
- Run the stress suite as an opt-in phase of
run_chain_e2e.sh(--stress). - Surface stress results in the PDF: a table, per-axis scaling curves, and
progression/regression vs the previous run (tracked in
history.jsonl).
Non-goals: waveform fidelity under stress (UDP loss is expected; the hub decimates to PushRate), and changing the stress harness's gating semantics.
Design
1. Matrix extension (stress.py)
- Extend
_DS_SIZEand_HUB_SIZEwith multi-fragment cases: add50000,100000,250000elements (≈200 KB, 400 KB, ~1 MB float32 packets). - Large packets get a slower producer to keep bandwidth realistic (mirroring
s51's 100 Hz / 10 ms cycle): set
producer_hz/row_dtper case so a ~1 MB packet is not emitted at 1 kHz (= 1 GB/s).sampling_ratestayselements / row_dtso the FirstSample per-cycle window equals one producer cycle (keeps the hub ring's time axis monotonic — same constraintscenarios.pyenforces). - Lift the single-datagram validation in
validate_case()from 64 KB to the 1 MiB deliverable cap (UDPS_CLIENT_MAX_PACKET_BYTES = 1048576). Update the module docstring (lines 44–47) to reflect multi-fragment support.
2. Report data wiring (run_chain_e2e.sh, report_build.py)
run_chain_e2e.shgains--stress(default off). When set, after the correctness phase and beforecollect.py/report_build.py, it runs the stress phase, reusingrun_stress.sh's env/LD_LIBRARY_PATH setup and thestress_run.pyinvocation, producingstress_results.jsoninBuild/x86-linux/E2E/chain/stress/. Honors--skip-build.report_build.pyreadsstress_results.jsonif present and folds astressblock intoreport_data.json:stress.overall,stress.cases[](id, axis, level, status, survival, marte_cpu_s/rss, hub_cpu_s/rss, zoom p50/p95, fails).stress.by_axis— cases grouped by axis, sorted by level (for the curves).
- When stress was not run,
report_data.jsonhas nostressblock and the PDF omits the section (no stale data).
3. PDF section + scaling plots (report_build.py, E2E_Report.typ)
- New
= Stress Testssection inE2E_Report.typ, after the Performance section. - Table — one row per case: axis, level, status, marteCPU, marteRSS, hubRSS, zoom p50/p95 (matching the existing Performance table style).
- Scaling curves —
report_build.pygenerates one PNG per axis (reusing its matplotlib trend-plot helper): load level (x) vs the relevant metric(s) — RSS & CPU vs level for size/count/sources, zoom-p95 vs reqrate, frames/RSS vs ws_clients. PNGs land alongside the existing trend plots and are embedded in the Typst section.
4. Regression + history (report_build.py)
- Append a
stresssummary to eachhistory.jsonlrun record: per-case key metrics (marte/hub RSS & CPU, zoom-p95, min_frames). - Extend the
_DIRECTIONmap: RSS/CPU/zoom-p95 → lower-is-better; min_frames → higher-is-better. Surface stress regressions in the existing headline/delta mechanism (▲/▼ vs last run), consistent with E2E metrics. - First-run guard: when no prior stress history exists, show baseline values with no
delta (existing
is_first_runhandling).
5. --stress flag plumbing (run_chain_e2e.sh)
- Add
--stressto the arg parser; default off (correctness-only stays the fast default). - Update the
run_chain_e2e.shheader comment and theCLAUDE.mdE2E paragraph to document--stress.
Full flow
run_chain_e2e.sh [--stress]
├─ correctness phase → results.json + perf JSON (unchanged)
├─ [if --stress] stress phase → stress_results.json
├─ collect.py → unit tests + coverage (unchanged)
└─ report_build.py → report_data.json (+stress block, +history/regression)
└─ E2E_Report.typ → PDF (Stress Tests: table + per-axis scaling curves)
Verification
python3 stress.pyvalidates the extended matrix (multi-fragment cases pass the lifted cap).- A multi-fragment size case (e.g.
ds_size_100000) runs PASS viarun_stress.sh --only ds_size_100000(exercises reassembly). run_chain_e2e.sh --stress --skip-buildproduces a PDF with the Stress Tests section (table + curves); a run without--stressomits it.- Second
--stressrun shows progression/regression deltas vs the first.
Risks
- Bandwidth at large element counts — mitigated by the slower producer; verify the hub keeps up (survival gate) and doesn't OOM (RSS gate; ~1 MB/slot ×4 slots ×N sources).
- Runtime — 27+ cases at ~6–8 s each adds minutes; mitigated by opt-in
--stress. - history.jsonl already polluted by prior single-scenario runs — stress history starts fresh (new key); no rewrite of existing E2E history.