feat(phase 1.4): setActive load driver — 100 calls in 60s + p50/p95/p99 latency #22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/phase-1.4-load-driver"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes the script half of Phase 1.4 from
.omc/plans/stream-a-thon-2026-06-13.md. PROD-HOST execution remains operator-driven — this PR ships the driver only.What it does
GET ${webui}/api/streams→ live stream pool.--screens(default: all 7SCREEN_POSITIONS).--calls(default 100)POST /api/setActiveevenly across--duration-ms(default 60000) — one start every ~600 ms; calls overlap if any takes longer than the interval (intentional — bursts are part of what we want to load-test).ok/validation/db_lock/http_error/network_error.db_lockis a dedicated bucket because the Phase 1.4 plan acceptance specifically calls out "no SQLite 'database is locked' errors" as a pass criterion.docs/phase-1.4-load-report.<ISO>.json.0if Phase 1.4 SLO passes (p95 ≤ 2000 mswarm ANDdb_lock=0),1if SLO fails,2on pre-flight error.Decomposition (Phase 1.3 style — small testable modules behind a thin CLI)
loadDriver/stats.tsloadDriver/schedule.tsloadDriver/classifyResult.tsdb_lockdetectorloadDriver/picker.tssetActivegroup-name derivationloadDriver/runDriver.tsfetch/now/setTimeout/loggerfor unit testability)loadDriver/reporter.tsevaluateSLO(p95 + db_lock gate)loadDriver.tsTest plan
scripts/loadDriver/__tests__/(suite total 172/26)npm run audit:sqlite-opensclean (HTTP-only script, no new sqlite sites)--dry-runexits 0 without firing calls (verified)Non-goals (intentional, per
scripts/loadDriver/README.md§"What it does NOT do")setActiveowns. Plugin-poll-side delay (≤1000 ms per G2) is constant and additive — folded into the Phase 4.2 SLO, not this one.npm run supervisor)./api/streams.Usage
```sh
npm run load:setactive -- --webui-url http://192.168.13.21:3000
or
npx tsx scripts/loadDriver.ts --calls 100 --duration-ms 60000
```
Full options in
scripts/loadDriver/README.md.Depends on
/api/streamspre-flight.Closes the script half of Phase 1.4 from the stream-a-thon 2026-06-13 plan. PROD-HOST execution remains operator-driven (this commit ships the driver only — the operator runs it on event-day rehearsal night against the production webui). What it does 1. GET ${webui}/api/streams → live stream pool. 2. Round-robin (stream × screen) picker over --screens (default: all 7 SCREEN_POSITIONS). 3. Schedules --calls (default 100) POST /api/setActive across --duration-ms (default 60000) — one start every ~600 ms; calls overlap if any takes longer than the interval (intentional — bursts are part of what we want to load-test). 4. Classifies each response: ok / validation / db_lock / http_error / network_error. db_lock is a dedicated bucket because the Phase 1.4 plan acceptance specifically calls out "no SQLite 'database is locked' errors" as a pass criterion. 5. Records per-call HTTP latency; reports p50 / p95 / p99 / min / max. 6. Writes a JSON report to docs/phase-1.4-load-report.<ISO>.json. 7. Exits 0 if Phase 1.4 SLO passes (p95 ≤ 2000 ms warm AND db_lock=0), 1 if SLO fails, 2 on pre-flight error. Decomposition (Phase 1.3 style — small testable modules behind a thin CLI): - stats.ts: percentile + computeStats (pure) - schedule.ts: scheduleFireOffsets (evenly-spaced offsets) - classifyResult.ts: bucket assignment incl. db_lock detector - picker.ts: round-robin (stream × screen) → setActive payload + expectedGroupName mirroring app/api/setActive/route.ts:46-49 - runDriver.ts: orchestrator. Deps (fetch, now, setTimeout, logger) are injected for unit testability — runDriver tests verify call payloads, bucket counts, network-error handling, and latency capture without spinning a real webui. - reporter.ts: console + evaluateSLO (p95 + db_lock gate). - loadDriver.ts: CLI entry with arg parsing, stream-pool fetch, JSON output, exit codes. - README.md: usage, options, operator runbook, explicit non-goals. Tests: 42 new tests across 6 suites in scripts/loadDriver/__tests__/. Suite total: 172/26 (was 130/20 on main). Type-check + lint clean; sqlite-opens audit unchanged (this script makes HTTP calls only). Smoke test (local, against fresh seeded DB, 15 calls in 1.5 s, screens=large,left): 15/15 ok, p50=15 ms, p95=144 ms, p99=144 ms, SLO PASS, exit 0. Unreachable-webui case exits 2. Non-goals (intentional, per README §"What it does NOT do"): - Does NOT connect to OBS WebSocket. Click→program-change e2e latency is the Phase 4.2 dress-rehearsal gate; this script measures only the HTTP latency that setActive owns. Plugin-poll-side delay (up to 1000 ms per G2) is constant and additive — folded into the Phase 4.2 SLO, not this one. - Does NOT run Streamlink. The "7 concurrent Streamlinks for 30 min" half of Phase 1.4 is operator-driven via the existing supervisor entrypoint (npm run supervisor). - Does NOT mutate the database. Read-only against /api/streams.b6810ece01573cc8d02e