test(acceptance): measure what a destination costs, and whether N cost N times it - #702
Conversation
…t N times it #380's concurrency gap. README.md:59 and docs/COMPARISON.md:268 both publish "roughly 4% of one core per destination" and nothing tested it -- a reader sizes a box with that number. WHAT IT MEASURES, through the real server rather than raw ffmpeg: one destination, then six, sampling cumulative CPU per child out of the process table. `time` rather than `%cpu`, for the reason the ladder driver already records -- %cpu averages over a process's whole lifetime, so a child up for a minute barely moves it. THE FALSE RESULT IT REFUSES, which is why liveness is asserted before cost everywhere. Measuring this by hand against raw ffmpeg produced: 1 destination: 3.80% of a core 2 destinations: 2.23% each 4 destinations: 1.11% each 8 destinations: 0.55% each Per-destination cost halving with every doubling: exactly the result anyone measuring this wants, and an artefact. Every run had ONE surviving process -- several ffmpeg readers on one UDP unicast socket compete for packets and all but one die, so the total was one survivor's CPU divided by N. It was caught only because that harness happened to print a liveness count. The product does not have the problem: internal/relay.Hub gives each destination its own subscription port, which this run confirms with six alive out of six. AND THE FIGURE IT CORRECTS IS MY OWN. That standalone harness also reported 8.8% of a core for one destination, which I posted to #380 as "about double the documented 4%". Measured through the product instead: 2.69% for one, 3.44% each for six. The published ~4% is corroborated, not contradicted, and the 8.8% was an artefact of measuring something that was not a destination -- different args, file output, no relay. The correction is going on the issue too. WHAT IT ASSERTS IS THE SHAPE, NOT A PERCENTAGE. "4% of a core" is a property of a machine; a suite pinning a number would fail on hardware rather than on regressions. The three that hold anywhere: - every destination asked for is still running at the end - every one burned measurable CPU (alive and costing nothing is not delivering, which is why that is a separate check) - the Nth costs about what the first did, within 0.4x-2.5x The absolute figure is PRINTED every run, with the note that this machine is not the six-core VPS the README's number came from, so a difference is not by itself a defect in either. VALIDATED BY WATCHING IT FAIL, and the first attempt to do that did not work. SIGKILLing the destination children was not enough: supervisor.Spec carries AutoRestart, so they came back before the sample and the suite passed a run it should have failed. Stopping them through the API instead RETIRES them, and then it fails correctly -- naming the count, and printing the "below the band" branch that says work which appears to vanish as load grows usually has not been done. A guard nobody has watched fail is a guard nobody should trust, and this one needed two goes. Not wired into CI in this change. It spawns seven ffmpeg processes and measures for a minute; where it belongs in the matrix is a separate decision from whether it works. Refs #380. Claude-Session: https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL
There was a problem hiding this comment.
🟡 Changes recommended
The new driver/harness can mis-measure or fail to surface driver errors due to facts.env quoting and incomplete validation of the measured PID set across the sampling window.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a new acceptance harness to measure per-destination CPU cost and validate that cost scales roughly linearly with destination count, closing the untested “~4% of a core per destination” concurrency claim referenced in #380.
Changes:
- Introduces
scripts/acceptance-concurrency.shto run the product, drive destination creation via the API, and assert liveness/work/linearity shape. - Adds a Go “driver” (
scripts/acceptance_concurrency_driver.go, run viago run) that creates destinations, samples per-process cumulative CPU fromps, and writes afacts.envfile for the shell harness.
File summaries
| File | Description |
|---|---|
| scripts/acceptance-concurrency.sh | New shell entrypoint that boots the server, runs the driver, and performs assertions (liveness, non-zero work, linearity band). |
| scripts/acceptance_concurrency_driver.go | New Go driver that provisions destinations and measures CPU consumption via process-table sampling, emitting results to facts.env. |
Review details
Suppressed comments (1)
scripts/acceptance_concurrency_driver.go:216
- The test’s safety property depends on measuring only destinations that were present for the entire window; if a destination dies/restarts mid-window (new PID),
alivemay still equalwantbut the PID intersection will shrink and CPU will be understated. Treat a PID-set change during the window as a hard failure so the suite can’t produce the known-false "too good" linearity result.
var pids []string
for pid := range after {
if _, ok := before[pid]; ok {
pids = append(pids, pid)
}
}
- Files reviewed: 2/2 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # able to read it off a CI log. | ||
| # | ||
| # Usage: ./scripts/acceptance-concurrency.sh [workdir] | ||
| set -u |
| before := destCPU() | ||
| if len(before) < want { | ||
| fmt.Printf(" %-16s FAILED to start: %d of %d destination children exist\n", | ||
| label, len(before), want) | ||
| } | ||
| const window = 20 * time.Second |
| for _, k := range keys { | ||
| fmt.Fprintf(&b, "%s=%s\n", k, facts[k]) | ||
| } |
|



Refs #380 — the concurrency gap.
README.md:59anddocs/COMPARISON.md:268publish "roughly 4% of one core per destination" and nothing tested it. A
reader sizes a box with that number.
It measures through the product, and that matters
One destination, then six, sampling cumulative CPU per child from the process
table —
timerather than%cpu, for the reason the ladder driver alreadyrecords (
%cpuaverages over a process's whole lifetime).The false result it refuses
Measuring this by hand against raw ffmpeg produced:
Per-destination cost halving with every doubling — exactly the result anyone
measuring this wants, and a complete artefact. Every run had one surviving
process. Several ffmpeg readers on one UDP unicast socket compete for packets
and all but one die, so the "total" was one survivor's CPU divided by N. It was
caught only because that harness happened to print a liveness count.
So this suite asserts liveness before cost, everywhere. The product doesn't
have the problem —
internal/relay.Hubgives each destination its ownsubscription port, which the run above confirms with six alive of six.
It also corrects a number I published
That same standalone harness reported 8.8% of a core, which I posted to #380
as "about double the documented 4%". Measured through the product: 2.69% for
one, 3.44% each for six. The published ~4% is corroborated, not
contradicted — the 8.8% was an artefact of measuring something that wasn't a
destination (different args, file output, no relay). Correction is going on the
issue too.
What it asserts is the shape, not a percentage
"4% of a core" is a property of a machine; a suite pinning a number would fail on
hardware rather than on regressions. The three that hold anywhere:
which is why that's a separate check
The absolute figure is printed every run, noting that this machine isn't the
six-core VPS the README's number came from.
Validated by watching it fail — which took two goes
First attempt: SIGKILL the destination children. The suite passed a run it
should have failed —
supervisor.SpeccarriesAutoRestart, so they cameback before the sample. Stopping them through the API instead retires them,
and then it fails correctly:
A guard nobody has watched fail is a guard nobody should trust.
Not wired into CI here. It spawns seven ffmpeg processes and measures for a
minute; where it belongs in the matrix is a separate decision from whether it
works.
https://claude.ai/code/session_01A8N3W5ct9SZtHK9sCDD9cL