Repository navigation
feat(e11): a persistent-connection Go Exec client (#294) - #296
Conversation
Review: Approved, one fix requested before quoting metal numbersI stacked-verified this against Please fix before the metal run: sub-millisecond truncation collapses p95/knee on the Go arm
ms: time.Since(t0).Milliseconds()an
Suggested fix, and it looks cheap: record fractional milliseconds (e.g. Worth a runbook note, not a blocker: sampler cadence vs. PSS-walk frequency
Everything else I went looking for came back clean
Bottom line: land the fractional-ms fix (small, both paths) before quoting any p95/knee number off the metal run. Everything else here is solid enough to run the driver-control comparison as-is for the coresBusy signal. Assisted-By: Claude Code |
Review on rossoctl#296 found the Go exec-driver's per-Exec latency is sub-millisecond, and int64 whole-ms truncation was rounding it to 0 almost every time -- collapsing p95/knee/coldAcquireRate signal on the Go arm. Fix both timing paths together so their output stays byte- identical in format: - bash grpc_exec_record (deploy/microvm/e11-density.sh) now computes microseconds and formats ms to three decimals using only parameter and arithmetic expansion -- no command substitution, preserving the rossoctl#291 item 2 fork-free guarantee (verified against the reviewer's test vectors and re-checked by the detector test). - Go execOutcome (remote-worker/cmd/exec-driver/drive.go) is renamed ms -> us (microseconds) and formatted via integer %d.%03d, no floating point. Downstream consumers (require_numeric, percentile, cold_count) already tolerate decimals and are untouched. Also: tighten the seam test and fixture that assumed integer ms, tighten drive_test.go's format assertion to the three-decimal shape, and add TestDriveRecordsSubMillisecondLatencyRatherThanZero to prove a sub-ms Exec is no longer recorded as exactly 0. Docs: disclose the format change in the E11 comparison runbook and the Go exec client design spec, and add a runbook note on the SAMPLE_LOW_EVERY/cadence tradeoff when chasing 10 sampler ticks on a fast client. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
|
Both points addressed in Fractional milliseconds, both pathsRecorded latency is now
One constraint shaped the bash side: that function is asserted to contain no command substitution frac=$((1000 + us % 1000))
ms="$((us / 1000)).${frac#1}"
You were right that nothing downstream needed changing — I re-verified each: Before/after, driving the real binaries against the real null-responder (8 Execs,
Tests: the seam test now pins the three-decimal shape rather than tolerating it; the Records written before this change carry integer Cadence versus the PSS walkAdded to the runbook rather than changed in code, as you suggested. It now names 🤖 Generated with Claude Code |
Clears PR rossoctl#293's merge conflict (review finding 1, the only must-fix). Merged rather than rebased on purpose: PR rossoctl#296 is STACKED on this branch, so force-pushing a rebase would orphan 83fd348 from rossoctl#296's history and force a second rebase there. A merge keeps the 18 reviewed commits byte-for-byte, needs no force-push, and follows this repo's own precedent (227ebda, "Merge rossoctl/main into E13"). Three conflict hunks, all resolved by keeping both sides: 1. cleanup_on_exit - main added E11_IN_CLEANUP=1 so kill_relay_by_port can tell a trap-driven teardown (log and continue) from a between-arm one (die); this branch added stop_host_sampler. Both are needed and neither displaces the other. The flag is set first, before any stop_*_stack as main's comment requires, and the sampler is stopped before the rm -rf of E11_TMPDIR as this branch's comment requires - the sampler polls for a stop file under that root, so after the rm it would spin forever writing to a deleted path. Verified: kill_relay_by_port reads ${E11_IN_CLEANUP:-} and returns cleanly when it was never set. 2. preflight - this branch made the tool and hardware checks conditional on the configured arm set (rossoctl#291 item 5), and main added require_tool ss for kill_relay_by_port. `ss` is folded INSIDE the container-or-microvm branch rather than above it, because kill_relay_by_port is called from stop_container_stack and stop_microvm_stack only: the driver-control arm runs no relay and tears its responder down by pid. Requiring it unconditionally would re-block exactly the SH_E11_ARMS=driver-control run that item 5 exists to unblock. 3. tests - both sides appended an independent section at the same point (the driver-control arm here, kill-by-port teardown on main). Both kept. Also correcting the record for review finding 4, which a merge cannot fix by rewriting history: commit b652c51's trailer says "Refs rossoctl#291 item 3". Issue rossoctl#291's numbered list has item 3 as the deferred persistent-connection client (now tracked as rossoctl#294); the converge barrier that commit implements is item 6. Design spec section 3 does cover it, so the mislabel is spec-section vs issue-item. b652c51 stays as written - it is public and rossoctl#296 is stacked on it. Gates on the merged tree: e11-density.test.sh 0 failures; shellcheck clean at -S warning and -o check-unassigned-uppercase on both changed shell files; go build, gofmt -l (empty), go vet, go test ./... all clean; make test-deploy 0. EXPERIMENTS.md's §E11 caveat survives alongside main's new E12/E13 sections, blockquote integrity intact. Refs rossoctl#291. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Automated review notesAdds
|
The driver-control arm PR rossoctl#293 added supplied the measurement issue rossoctl#291 deferred item 3 on: on metal the driver alone peaks at c=8, burns 64 of 72 cores at c=64, and carries 753ms of the published microVM arm's 1686ms p95. The remaining per-Exec execve is grpcurl, which re-parses the proto and opens a fresh TCP connection and HTTP/2 session per call. The design: a new remote-worker/cmd/exec-driver holding one grpc.ClientConn for a whole rung and c goroutines in place of c bash subshells, writing the existing `<ms> <status> <cause>` per-slot lines so aggregation, percentiles and the record writer are untouched. Opt-in behind SH_E11_EXEC_CLIENT so the bash path stays the reference until the two are compared on one host. Records one finding beyond the issue: the relay yields ExecEvent.error and then returns a gRPC OK status, so grpcurl exits 0 and an ExecError-failed Exec counts toward throughput and never reaches execErrorsByCause. It cannot affect this issue's comparison (the null-responder only sends End), so the Go path classifies it correctly and discloses the divergence. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Nine tasks, each TDD and independently reviewable: the cause mirror, the rung plan and its refusals, the issuer and binary, client selection and build, write_rung_plan, the phase-2 branch, the execClient record field, end-to-end seam closure, and the docs plus runbook. The grpcurl subshell loop is reproduced verbatim in the branch task and a step asserts it came out byte-identical: it is the reference the Go client is compared against, so tidying it would invalidate the comparison. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…l#294) extract_fn ends a function at the first bare } at column 0 and only skips an embedded python block spelled with a double quote. write_rung_plan uses a single quote, so its python dict's column-0 closing brace truncated the extraction to 7 lines of 40 -- verified empirically against the real helper. Task 5's extractability check and all of Task 8 would have failed. Indenting the closing delimiters costs nothing (Python accepts it) and avoids touching a helper a dozen other sections of that suite depend on. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…#294) Same six causes, same order. A client-side deadline lands in unknown on both paths, as grpcurl's own timeout message does, so execErrorsByCause stays comparable between the two clients. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…l#294) Slot identity travels in the plan rather than being recomputed in Go: the three derivations live in one place each in e11-density.sh so two slots cannot drift into sharing a workspace or a req_id space. validateReqIDRanges refuses touching req_id spaces before any Exec is timed. The relay demultiplexes by req_id and a collision once left a run wedged for 33 minutes, which is a failure mode no measurement survives. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…soctl#294) The connection is brought to READY before any slot starts, so the first Exec of a rung pays no more than the last: grpcurl's per-call TCP and HTTP/2 setup was inside every measured latency. Streams are drained to io.EOF rather than stopped at End, because grpcurl drains them. An in-stream ExecEvent.error is recorded as a FAILED Exec: the relay yields it and then returns a gRPC OK status, so grpcurl exits 0 and the bash path counts a failed Exec toward throughput. The null-responder never sends one, so being right here costs the driver-control comparison nothing. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
dialReady's only non-READY exit was ctx being done, and drive was called with context.Background() from main.go, so a persistently unreachable target (down relay, wrong port, firewalled host) cycled CONNECTING -> TRANSIENT_FAILURE -> CONNECTING forever. grpcurl's -max-time covered dial as well as call, so the bash path this replaces fails fast where this hung -- and run_density_rung waits on this child, so the hang stalled the whole ladder, not one rung. Fix is scoped to the dial: drive derives a dial-only context bounded by the plan's existing CallDeadlineS and passes that to dialReady. The Exec phase after the dial stays on the caller's unbounded context, since a rung-level timeout would cap legitimate slow rungs at high c, which is the regime being measured. TestDriveFailsWhenTheTargetIsUnreachable now calls drive with context.Background(), as main.go does, sets CallDeadlineS=1 so the bounded dial fails in about a second, and asserts on wall-clock elapsed time so the test cannot pass by accident if the internal bound is later removed. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ault (rossoctl#294) Opt-in, because rossoctl#294's acceptance is a comparison: the bash path has to stay the reference until both have run against the null-responder on one host with nothing else changed. An unrecognised value is refused rather than defaulted, since the value is stamped into every rung as execClient and a mislabelled ladder gets compared against the wrong table. build_exec_driver is a no-op unless the Go client was selected, so a grpcurl run cannot fail over a binary it never invokes. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ossoctl#294) Round 1 review found two checks whose names claimed a property they could not detect: - The refusal-message check used 'grpcurl-go' as the invalid value, and die() echoes the bad value back verbatim, so the input itself satisfied the 'names both accepted values' grep regardless of what the message said. Switched to 'xyzzy', which shares no substring with either accepted value, and split the check into one assertion per value plus a non-vacuousness proof that a message quoting only the bad value fails the 'grpcurl' check. - The 'main builds the Exec driver after preflight' check only counted occurrences of build_exec_driver in main's body; it never compared positions, so the two calls in the wrong order still passed. Replaced with a positional comparison (preflight's line number is less than build_exec_driver's) plus a non-vacuousness proof with the operands reversed. Also fixed a stray 'above' in a shellcheck disable comment that should have said 'below', matching the rest of the file's convention. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Everything travels through argv and json.dumps does every escape: bash passes each array element as one argv entry, so a workspace key or path containing a quote, backslash or tab cannot be mis-split, and JSON escaping stays in the one place this driver already puts it. slot_workspace_key is called once per slot and feeds both the pre-escaped form the grpcurl path interpolates, and the raw form the plan carries. Deriving it twice is how the two clients would drift into sending different keys. Also updates two pre-existing test assertions that this change legitimately invalidates: the "req_id base from ONE helper" count (2 -> 3, a new but non-drifting call site in the plan-building loop) and the phase-split converge/exec probe (EXEC_CLIENT is read by run_density_rung and was unset under set -u in that isolated harness, so grpcurl is now passed explicitly there, matching the real script's default). Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ctl#294) Both branches push onto the same pids array and are reaped by the same wait loop, so the guarantee that a rung whose slots were not all measuring the same thing is never recorded holds identically for both. The refusal branches because the failure differs: one Go process drives all c slots, so a non-zero exit means the rung has no trustworthy timings at all, and reporting it as 1 of c slots would understate it. The fork guard now also flags python3 between the wall stamps, so a future edit cannot move write_rung_plan into the timed window. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ossoctl#294) The existing "both paths are waited on through the same pids array" check only counted the shared wait-loop line, which stays at 1 whether or not the Go branch ever pushes its pid onto the array. That check alone could not catch a dropped pids+= in the Go branch: the process would still be forked but never waited on, so wall_t1 would be stamped without blocking on it, corrupting the rung's wall time and throughput, and exec_failures would never see a Go-side failure -- the same defect class this issue exists to remove. Add a count scoped to the timed window (2, not 3: phase 1's converge loop has its own pids+= outside the window), a non-vacuousness pair proving the detector can report either 2 or 1, and a positional check that the Go invocation is immediately followed by its own enqueue, since two enqueues in the window do not by themselves prove they sit in different branches. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Without it a go-driven ladder and a grpcurl-driven one are indistinguishable JSON, and comparing them is the entire reason the second client exists. Same class of defect c953c98 fixed for stale rungs, one level up. proxyLimitations gains execErrorStatus on BOTH paths: a limitation that appears only on the path that does not have it is not a disclosure. On grpcurl it records that an ExecError-failed Exec counts as a success; on go, that it does not, and that the two therefore disagree on the real arms while agreeing exactly on driver-control. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…l#294) The if/else that chose driver_control_note/exec_error_note by EXEC_CLIENT lived inline in run_density_rung, where no test could reach it -- the record-writer harness hardcodes both strings directly into its fixture, bypassing the branch entirely. A swap between the two would have made every go-driven record assert something false about its own trustworthiness (that an ExecError is recorded as ok), with a passing suite. Extract the branch into driver_control_note_for and exec_error_note_for, pure functions of the client argument, matching the exec_client_label idiom this file already documents. Drive both under both clients, and assert path-specific content plus that the two paths actually differ, not just that both are non-empty. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
The real write_rung_plan, the real exec-driver binary, the real null-responder, and the times files read back. e11-density.sh itself needs /proc, cgroups and Linux, so this is the only place the seam can be verified on a development machine -- and it needs none of them. Skips visibly when go is absent. A silent skip is how a seam test stops testing anything. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ssoctl#294) Fix round 1 review findings on the seam-closure test: - The build-failure path of the seam section deleted its own evidence: a compiler error only ever appeared as one line, "FAIL: both binaries build", because $seam_dir (and build.log with it) is removed unconditionally at the end of the section. Dump build.log on failure, mirroring the pattern already used for the driver-failure path. - The background null-responder was only killed on the section's normal exit path. A SIGINT or CI timeout between start and the kill/wait pair left a gRPC listener bound to 127.0.0.1:18447 indefinitely. Trap INT/TERM/EXIT to kill it, clearing the trap after the normal teardown so it cannot fire twice or mask a later signal. No trap previously existed at the top level of this test file to clobber. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…book (rossoctl#294) The §E11 banner already said the driver-control subtraction was what would decide whether the re-run can separate backend from driver. It has decided: an arm with no relay, no worker and no VMM reproduces the published c=8 knee and burns 64 of 72 cores at c=64. Appended rather than rewritten, because the banner's own rule is that the numbers stay as the record of what the broken instrument produced. Section E11 stays under repair until both clients have run against the null-responder on the host that produced the table. The runbook carries the reference numbers, the two invocations, and the checks to make before quoting anything -- including deriving SH_E11_COLD_LATENCY_MS from E10 and confirming hostCpuSamples is not 1. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Replace committed placeholder text with the real measured comparison table (2000 Execs/slot on both arms; Go 49x-61x faster per Exec than grpcurl depending on concurrency). Rewrite the calibration narrative to disclose that the calibrated 18000-per-slot count was abandoned for the paired run because it makes grpcurl the binding cost (18003 / 38.4 Exec/s would need ~470s at c=1), while keeping the four calibration data points as evidence. Correct an overclaimed causal reading of the c=4 dip: grpcurl shows the same non-monotonic shape despite opening a fresh connection per call, so a fixed Go-side startup cost cannot be the explanation; ground the low-c caveat in first principles and disclose n=1. Note that the paired run's 2000/slot was scaled up from the brief's illustrative 200. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Applies all seven review items from the whole-branch review as one consolidated wave (no second wave planned): - Rewrite the runbook's "Before quoting any number" section: sampler cadence (not Exec count) is the knob to fix a low hostCpuSamples count, raising ITERS_PER_SLOT for the Go arm alone is wrong, a cadence that still can't reach 10 ticks gets two labelled Go ladders, "the sampler can't see the Go client's window" is treated as a finding, and the ZERO-host-samples refusal is named by its quoted text. - Note in the spec, EXPERIMENTS.md, main.go, and e11-density.sh's header that the 753ms/1686ms figures come from different drivers at different ITERS_PER_SLOT (200 vs 20), a mismatch that runs in this branch's favor. - Reword e11-density.sh's go-case control note to mark the "tighter"-comparative claim UNMEASURED, grounded in the actual mechanism difference (JSON-to-/dev/null vs stream.Recv()-decode); the grpcurl case is untouched. - Add an EXPERIMENTS.md bullet for the relay's routeExec-yields-in-stream-error-then-OK defect (not yet tracked by an issue) and a runbook sentence sequencing the relay fix before any cross-client comparison on container/microvm. - Split the seam test's trap so INT/TERM re-exit 130 instead of being swallowed by an EXIT-only handler. - Move the Go client's plan file from $E11_TMPDIR to $RESULTS so it survives a refused rung, and add a rung-header line to $RESULTS/e11-exec-driver.log before wall_t0 is stamped. - Four minor fixes: a missing blank blockquote continuation line in EXPERIMENTS.md; reword "replaced it with" to "added ... as an opt-in alternative" (grpcurl stays default); fix drive.go's empty-message ExecEvent.error misclassification with a dedicated sawErr bool plus a new drive_test.go case; extend the disclosure safety check to reject $, backtick, and backslash alongside the apostrophe. Gates: shellcheck -x -S warning on both scripts (exit 0), full bash test suite (Total failures: 0), gofmt/go vet/go test -v on ./cmd/exec-driver/ (16/16 pass), Prettier --write then --check on all touched Markdown (stable, "(unchanged)"), and manual confirmation every EXPERIMENTS.md blockquote line still starts with `>` after Prettier. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…ssoctl#294) The final review asked that the relay defect this branch discloses but does not fix be tracked rather than described. It is now issue rossoctl#295, referenced from the spec's finding, EXPERIMENTS.md's under-repair list, the runbook's sequencing note, and both execErrorStatus disclosure strings -- so a reader holding a rung record can reach the defect that explains it. The two disclosure strings stay free of apostrophes, dollars, backticks and backslashes: they are interpolated through a double-quoted bash string into python source, and the suite now asserts all four characters are absent. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…nsourced figure (rossoctl#294) Three accuracy gaps the fix wave's re-review found in the prose an operator reads while running the comparison. SAMPLE_SLICE_MS is the actual tick floor and the runbook never named it: host_sampler_loop computes slices_per_tick as SAMPLE_INTERVAL_MS/SAMPLE_SLICE_MS floored at 1, so once SAMPLE_INTERVAL_MS reaches SAMPLE_SLICE_MS (default 100ms) lowering it further does nothing. Ten ticks inside a sub-200ms Go window needs both well below 100ms, so the guidance to lower cadence was not actionable as written. The zero-sample refusal still advised raising ITERS_PER_SLOT while the runbook said that is wrong for a paired comparison -- the tool contradicted the doc it points at, at the moment an operator hits it. It now names the cadence knobs and the SAMPLE_SLICE_MS floor, and says raising ITERS_PER_SLOT is fine for a single ladder but not when comparing two clients. driver_control_note_for's go arm carried "roughly 50x smaller" with no derivation anywhere in the tree; it resembled the laptop run's 49-61x throughput ratio, a different metric on different hardware. Struck. The UNMEASURED hedge did not cover a specific number, and this branch's own standard is that an inaccurate disclosure is worse than none. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Review on rossoctl#296 found the Go exec-driver's per-Exec latency is sub-millisecond, and int64 whole-ms truncation was rounding it to 0 almost every time -- collapsing p95/knee/coldAcquireRate signal on the Go arm. Fix both timing paths together so their output stays byte- identical in format: - bash grpc_exec_record (deploy/microvm/e11-density.sh) now computes microseconds and formats ms to three decimals using only parameter and arithmetic expansion -- no command substitution, preserving the rossoctl#291 item 2 fork-free guarantee (verified against the reviewer's test vectors and re-checked by the detector test). - Go execOutcome (remote-worker/cmd/exec-driver/drive.go) is renamed ms -> us (microseconds) and formatted via integer %d.%03d, no floating point. Downstream consumers (require_numeric, percentile, cold_count) already tolerate decimals and are untouched. Also: tighten the seam test and fixture that assumed integer ms, tighten drive_test.go's format assertion to the three-decimal shape, and add TestDriveRecordsSubMillisecondLatencyRatherThanZero to prove a sub-ms Exec is no longer recorded as exactly 0. Docs: disclose the format change in the E11 comparison runbook and the Go exec client design spec, and add a runbook note on the SAMPLE_LOW_EVERY/cadence tradeoff when chasing 10 sampler ticks on a fast client. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…lures (rossoctl#294) Both from PR rossoctl#296's review notes. drive() returned only the lowest-indexed slot's error and silently dropped the rest, so an operator debugging a dead rung had to re-run it once per slot to learn that three failed for three different reasons. errors.Join over all non-nil entries; it returns nil on an empty slice, so the success path is unchanged. runSlot discarded both Close errors. Flush already pushes bufio's buffer into the file, but the write-back can still fail at Close, and a silently truncated times file would then be aggregated as though it were complete. Both closes now report through a named return, and the err file's -- which carries diagnostics only -- is reported only when nothing worse happened, so it cannot mask a real rung error. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
57152f3 to
af1c366
Compare
|
Rebased onto Blocking item resolved
That matches the On the conflict: your diagnosis was exactly right — append/append in I checked one thing before rebasing that would have changed the approach: whether Post-rebase verification on the combined tree, since main's newer driver code ( Both suggestions applied
On the
|
|
CI on
|
huang195
left a comment
There was a problem hiding this comment.
Mamma mia, Paolo — this is a beautiful piece of instrument work. Finding that the published c=8 knee belonged to the driver, and then proving it with an arm that has no backend at all, is the kind of result most people would have shipped as a sweep re-run instead. And the discipline around it is what makes it trustworthy: grpcurl stays the default, the times-file format is the interface rather than a claim, execClient is on every rung so two ladders can't silently mix, and the relay ExecError divergence is disclosed on both paths instead of only on the one that has it. Perfetto.
The seam test earns its keep — real write_rung_plan → real exec-driver → real null-responder, and it needs neither /proc nor Linux. Verified independently while reviewing:
remote-worker/go.modisgo 1.26.0, sodrive's per-iterationi/scapture is sound (that comment is load-bearing — on a pre-1.22 directive it would be a race).cause.go's switch mirrorsgrpc_exec_record's grep chain key-for-key and order-for-order.validateReqIDRanges' off-by-one is right:next == prev+callsreally does collide the next slot's converge with the previous slot's last Exec, andTestLoadPlanReqIDBoundaryIsExactpins it in both directions.- The new fractional-ms field survives everything downstream —
percentilesorts withsort -n,cold_countandthroughputare awk,require_numericalready accepted%.4f. Nothing breaks.
Four non-blocking items inline. Two more for the description rather than the code:
- The base-branch note is stale. #293 merged at 13:17Z today, so GitHub now shows 23 commits / 15 files — all yours. The "read this before reviewing the file list" block can go.
- "the bash path is byte-identical in substance … additions only" no longer holds.
77aaec8replacedgrpc_exec_record'sms=$(( … / 1000 ))with the three-decimal form. That is the right call — both paths must record the same precision or the comparison is against a rounding artifact — but it is a substantive edit to the reference path, and it means published grpcurl ladders carry integerp95Mswhile new ones carry fractional. Worth stating outright rather than leaving the old claim standing.
And one minor, outside the diff so I can't pin it inline: e11-density.sh:540's require_tool grpcurl reason still says "every arm drives its Exec RPCs through grpcurl". On the Go path grpcurl is genuinely still required — converge stays on it — but the justification now misleads whoever debugs a SH_E11_EXEC_CLIENT=go run.
Bellissimo work, and §E11's conclusions staying marked under repair is the honest call. Approving.
Assisted-By: Claude Code
| } | ||
| }() | ||
|
|
||
| w := bufio.NewWriter(times) |
There was a problem hiding this comment.
suggestion — the comment above says writes are "flushed once at the end", but bufio.NewWriter takes the default 4096-byte buffer, so that is true only while a slot's whole output fits in it.
At the documented ITERS_PER_SLOT=200 a slot writes ~203 lines × ~11 B ≈ 2.2 KB — fine. At the 2000/slot this PR's own local-numbers table used it is ~22 KB, so bufio flushes ~5 times inside the timed window: the write syscall the comment says was removed is back, just at 1/370th the rate.
Sizing it from the plan makes the comment true at any count:
w := bufio.NewWriterSize(times, 32*calls+4096)(calls is already computed just below — you'd want to hoist it one line.) Small thing, but the whole binary exists to keep driver cost out of that window.
| // resolution rather than a real signal. This must fail against the pre-fix code (ms field "0") | ||
| // and pass against the fix (a non-zero fractional value). | ||
| func TestDriveRecordsSubMillisecondLatencyRatherThanZero(t *testing.T) { | ||
| subMsShape := regexp.MustCompile(`^0\.[0-9]{3}$`) |
There was a problem hiding this comment.
suggestion — ^0\.[0-9]{3}$ pins the whole-millisecond part to 0, not just the fractional precision. One Exec against the in-process fake crossing 1 ms — a loaded CI runner, a GC pause, a scheduler hiccup — fails this test with "does not have the sub-millisecond shape 0.XXX", which is the opposite of the bug it exists to catch.
The truncation defect is fully caught by the two properties that don't depend on wall-clock luck:
if !msFieldShape.MatchString(msField) { ... } // three decimal digits present
if msField == "0.000" { ... } // and not truncated to zeromsFieldShape is already in this file. Against the pre-fix code the field was the bare integer 0, so it fails the first check — the mutation-test property you were after holds without the flake.
| if _, err := fmt.Fprintln(w, o.line()); err != nil { | ||
| return fmt.Errorf("writing to times file %s: %w", s.TimesFile, err) | ||
| } | ||
| if o.errMsg != "" { |
There was a problem hiding this comment.
nit — gating the err-file write on o.errMsg != "" drops exactly the case the code just went out of its way to classify correctly.
An in-stream ExecEvent.error with an empty message (the one TestDriveClassifiesAnEmptyMessageInStreamErrorAsFailure covers) records err / unknown in the times file and writes nothing to the err file — which is verbatim the state plan.validate's own errFile refusal warns about: "a failing Exec's message would be lost and execErrorsByCause would say unknown with nothing to look at".
Gating on the status instead makes the two agree:
if o.status == "err" {
_, _ = fmt.Fprintf(errFile, "req %d: %s\n", s.ReqBase+1+uint64(i), o.errMsg)
}An empty line under a req id at least tells the operator the relay sent an error with no message, rather than leaving them to infer it from a times file.
| if [ "$seam_rc" -eq 0 ]; then | ||
| # An ephemeral-ish port well away from the driver's defaults (8444/8445), so a stray relay | ||
| # or responder from another run cannot answer this test's Execs. | ||
| seam_port=18447 |
There was a problem hiding this comment.
nit — the fixed port is a small flake surface. Two concurrent runs of this suite on one host, or a listener left behind by a SIGKILL (the new INT/TERM trap covers cancellation, but not -9 or an OOM kill), both surface as the null-responder came up: no — with the stale responder happily answering on 18447 and no hint in the output that the port was the problem.
If null-responder accepts --listen 127.0.0.1:0 and logs the bound address, taking the port out of responder.log alongside the existing serving sandbox.v1.SandboxExec wait would remove the class entirely. If it doesn't, no action needed — worth a look since the wait loop is already parsing that file.
…ctl#294) Five non-blocking items from PR rossoctl#296's second review, all applied: - drive.go: size the times-file bufio.Writer from the plan (32*calls+4096) rather than taking the default 4096B buffer, so the "flushed once at the end" comment holds at any ITERS_PER_SLOT, not only at the documented 200/slot default. At the 2000/slot this PR's own local-numbers table used, the default buffer flushed mid-loop and put a write syscall back inside the timed window. - drive_test.go: TestDriveRecordsSubMillisecondLatencyRatherThanZero no longer pins the whole-millisecond part of the ms field to "0", which could flake on a loaded CI runner, a GC pause, or a scheduler hiccup. It now reuses the package's msFieldShape (three decimal digits present) plus an explicit "not truncated to 0.000" check -- the same two properties, without the wall-clock dependency. - drive.go: gate the err-file write on status == "err" rather than errMsg != "", so an in-stream ExecEvent.error with an empty message (TestDriveClassifiesAnEmptyMessageInStreamErrorAsFailure) still gets a line in the err file instead of being silently dropped -- the exact state plan.validate's errFile refusal warns about. Extended that test to assert the err file gets one line per failing Exec. - e11-density.test.sh: the seam test's null-responder now binds 127.0.0.1:0 instead of a fixed port (18447), removing both the concurrent-run and stale-listener (SIGKILL/OOM) flake classes. The bound address is parsed out of the responder's own log line and used as the plan's target, with a check that parsing did not come back empty. - e11-density.sh: reworded the require_tool grpcurl reason (and the block comment above it) so it says grpcurl is required on every path because converge always uses it directly, not because the timed Exec loop does on the Go path. Gates: gofmt/go vet/go test clean (17/17 pass) in remote-worker/cmd/ exec-driver; shellcheck -S warning clean on both e11-density.sh and its test file; full e11-density.test.sh run reports Total failures: 0 with the seam section's checks all ok:. Proved the sub-millisecond test fix (item 2) still catches the original truncation bug by temporarily reverting drive.go's line() to the old %d-whole-ms form (via a file copy, never git stash/checkout): the test failed as expected, then the file was restored byte-identical to before. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
|
Grazie — and thank you for going after the parts that undercut themselves. All seven items Two of the four inline catches were this branch's own additions contradicting their own purpose, which The buffer wasn't sized for the runs this PR documents. You're right that The sub-millisecond test was a flake pointing the wrong way. The err-file gate dropped exactly the case the code had just fixed. Gating on Ephemeral port. Verified before changing anything that
Both description itemsThe stale base-branch note is removed — #293 merged, and the PR now shows its true 15 files. And you were right to push on the other one: "byte-identical in substance … additions only" no longer Gates after all of it: One housekeeping note: the fix commit initially picked up a stray 🤖 Generated with Claude Code |
CI
|
|
As expected from the 80 local runs (20 on Nothing outstanding from my side — the branch is ready to merge. The authoritative 72-core |
|
Both follow-ups filed:
Neither belongs in this PR. Nothing outstanding here — all 13 checks green on |
…not density The re-run's position reproduces and its instrument repairs hold, but every microVM ladder in this section swept offered concurrency `c` while the worker admitted only 4 concurrent Execs (`session.DefaultConcurrency = 4`, which `e11-density.sh` never overrode). Slots and `c` are confounded throughout, so no conclusion drawn above `c = 4` can separate a property of the design from a property of that constant. The section's own arithmetic exposes it: it derives a ~4.5 VMs/s replenishment supply and offers it to explain a flat ~62 Exec/s plateau, 14x apart. Four slots at the measured 61.4 ms per Exec predicts 65.1 Exec/s against a measured 62.99, and predicts every other rung within a few percent. The p95 column doubling per doubling of `c` is the same fact read as latency — queueing against a fixed-capacity server. Purely additive; no measurement is removed. Separated explicitly: - Survives: the rossoctl#293 sampling and rossoctl#296 Go-client repairs, "it is not the driver's knee" (driver share under 0.4%), "no CPU ceiling was reached" (now explained — 4.81 of 72 cores because only 4 Execs were admitted), and the container arm, which at ~1940 Exec/s is nowhere near a 4-slot cap. - Does not survive: the `coldAcquireRate` argument (#306 — wrong by 13x at c=8, ~250x above), `bound = replenishment` as a genuine crossing, and the `D` sweep as evidence. "Rate, not depth" is independently correct, but with 4 Execs in flight no value of `D` could have changed the outcome, so that sweep is what a slot-capped ladder must produce either way. - Left open: c=8 peaks at 80.62 Exec/s, ~4.9 effective slots, which a 4-slot cap cannot produce. Unexplained. With slots raised to match `c`, the same host reaches 577.55 Exec/s at 64 slots. Resolves rossoctl#291 as investigated with a stronger outcome than "it holds": the knee reproduces and its cause is known. Refs rossoctl#291 rossoctl#305 #306 Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
…the re-run The 2026-09-17 caveat asked a falsifiable question: does E11's c=8 knee stay there once driver cost is removed and host CPU is sampled under load. It does. So the caveat's items are resolved rather than retracted, and this section's conclusion was right while only its evidence was wrong. Purely additive: no published number is removed. This section's own policy is that "the numbers stay ... the record of what the broken instrument produced", and the re-run is defined by comparison against them. Adds `### Issue #291 -- the re-run`, on the same bare-metal host and golden snapshot as the existing E10/E11 tables, with PR #293's repaired sampling and PR #296's Go Exec client: - knee = 8 on BOTH real arms, by the same p95-doubling criterion this section used for its published knee. The microVM knee is sharper than published: throughput now peaks at c=8 and declines, where the published ladder peaked at c=16. The container knee was reproduced twice, at ITERS_PER_SLOT 80 and 2000. - It is not the driver's knee. driver-control's own knee is 4 (Go) or 32 (grpcurl), never 8, and the Go driver is under 0.4% of the microVM arm's p95 at every rung -- against up to 23.7% had it been grpcurl, which quantifies the confound the 2026-09-18 note suspected. - bound = replenishment on the microVM arm is a GENUINE crossing, at the same c as the knee. A D sweep (2/4/8) then shows the knee does not move while standbys go 52 -> 439 and Sigma PSS 0.324 -> 1.443 GB, so the bound is the serialized 200ms replenishment RATE, not pool capacity -- standby-depth sizing is not the lever. - "No CPU or memory ceiling was reached" becomes a real finding instead of a restatement of the sampling bug: the true under-load means are 0.067 and 0.093 against crosses('cpu')'s 0.9, from 20-284 ticks per rung. - Sealed prediction 3 scores `supported` from a threshold derived from the same client that produced the ladder (94ms = go-client microVM c=1 p95 82.0 + E10 rung 3 restore 23.06/2), not from the grpcurl era's 124-133ms. - Issue #295 stays open and is recorded as such; it confounded nothing here. Also records two corrections to this section as published, rather than leaving them to be rediscovered: - The container arm's `bound: "cpu"` is attributeBound's documented fallback, not a crossing. Peak hostCpuFraction is 0.093 against a 0.9 threshold. It means "nothing bound", not CPU. - The driver-control table in the 2026-09-18 note does not reproduce past c=1. Same host, client and settings now give 1910.8 Exec/s at p95 39.26ms at c=64 where that note recorded 231.6 at 753ms on the same ~64 busy cores; c=1 agrees closely. Logged as an open discrepancy, explicitly not as a diagnosis. And the three instrument defects the run found: the dying-VMM PSS refusal that aborted the ladder (fixed in PR #299), that SH_E11_VMM_PROC_PATTERN cannot usefully be scoped and must be left unset, and that the in-rung sampler has no zero-PSS refusal (unfixed, same shape as #291's own item 1). predictions.json is untouched and its hash pin still passes. Every table figure was cross-checked against the committed rung records. Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Paolo Dettori <dettori@us.ibm.com>
Closes #294.
Stacked on #293 (
fix/291-e11-driver-artifact), not onmain— it builds on that PR'sdriver-controlarm and its repaired sampler. Review or merge #293 first.Why
#291 deferred its item 3 — replacing
grpcurlwith a persistent-connection client — on thegrounds that committing before measuring would be a guess. #293's control arm supplied the
measurement, on bare metal, with no relay, no Redis, no worker and no VMM behind it:
The driver alone peaks at
c=8and then declines, burning 64 of 72 cores atc=64with itsown p95 of 753 ms. That is the same knee position and curve shape §E11 published for both real
arms. The published density knee is reproduced by an arm with no backend at all.
What this adds
remote-worker/cmd/exec-driver— one process per rung, onegrpc.ClientConnbrought toREADYbefore any slot starts, and
cgoroutines in place ofcbash subshells. Four small files:cause classification (a deliberate mirror of
grpc_exec_record's ordered grep chain), the rungplan and its refusals, the issuer, and the binary.
It writes the same per-slot
<ms> <status> <cause>lines the bash path wrote, so warmuptrimming,
percentile,execErrorsByCause, the sampler bracket and the record writer areuntouched — not by claim, but because the file format is the interface.
Opt-in.
SH_E11_EXEC_CLIENTdefaults togrpcurl. It stays the reference until both clientshave run on one host, which is #294's own acceptance criterion, and every rung record carries
execClientso two ladders can never be silently mixed.One substantive edit to the reference path, stated plainly. The bash path's driving mechanism
is unchanged — one
grpcurlprocess per Exec, the subshell loop moved into anelsebranch andnothing more. But
grpc_exec_record's latency arithmetic was changed: it recorded wholemilliseconds, and the Go client's per-Exec latency is sub-millisecond, so at the Go client's measured
rates every recorded value truncated to
0andp95Mscollapsed with it. Both paths now recordthree decimal places, computed from integer microseconds with no floating point. They had to move
together: one path fractional and the other integer would make the comparison a rounding artifact.
The consequence worth knowing: grpcurl ladders published before this change carry integer
p95Ms,and new ones carry fractional. The spec and the runbook say so too.
Converge stays on
grpcurlanddrivingModelstaysclosed-loop-per-slot— both statednon-goals, and both asserted by tests rather than left to trust.
What this does NOT include
The authoritative comparison. That belongs on the host that produced the table above, and is
not run here.
docs/notes/e11-go-exec-client-comparison-runbook.mdhas the reference numbers, thetwo invocations, and the checks to make before quoting anything — including that the Go arm needs
its sampler cadence lowered rather than its Exec count raised, because at matched Exec counts its
rungs are ~50x shorter and the driver refuses a rung that produced zero sampler ticks.
§E11's conclusions therefore stay marked under repair. A sweep run today would measure the
driver's knee again.
Indicative local numbers only
Both clients against the null-responder on a 10-core laptop, identical Exec counts (2000/slot):
Not the 72-core comparison the acceptance criteria name: no
/procsampling, so nohostCpuFraction,coresBusyor p95 at all; mix reduced to one command;n=1; ande11-density.shitself never ran. It shows both clients work and the per-Exec gap is large. Itsays nothing about where any knee sits.
Corroboration worth noting for whoever books the rig: grpcurl's rates here land close to the
published metal
driver-controlfigures (56.8 / 171.6 / 241.6) despite 10 cores versus 72 — whichis what you expect if the bound is per-call process spawn rather than the host.
One defect found and not fixed here
packages/sandbox-relay/src/relay.ts'srouteExecyields an in-streamExecEvent.errorand thenreturns a gRPC OK status, so
grpcurlexits 0 and the bash path counts a failed Exec towardthroughput, into thep95distribution, and never intoexecErrorsByCause. Filed as #295.Deliberately left split: the Go client classifies it correctly, the reference path is unchanged,
and on
driver-control— the only arm #294 measures — the divergence is exactly zero, becausethe null-responder never emits an
ExecError. Fixingrelay.tshere would move the reference pathmid-comparison. Every rung record discloses which behaviour produced it, on both paths, and #295
records that the fix must land before any cross-client comparison on the container or microvm arms.
Verification
gofmt,go vet, 16 Go tests,shellcheck -x -S warningon both shell files, all fourdeploy/microvmsuites,tsc --noEmitand vitest forexperiments— all clean.The seam is proven end to end by a test that drives the real
write_rung_planinto the realexec-driveragainst the realnull-responderand reads the times files back:e11-density.shneeds
/proc, cgroups and Linux, so that test is the only off-rig proof the boundary works, and itneeds none of them.
Several checks were mutation-tested rather than trusted — shortening
runSlot's loop, swapping thetwo disclosure arms, removing the Go branch's
pids+=— each producing exactly the attributablefailures it should. Six vacuous guards were found and fixed along the way, most of them in the
plan's own text.
🤖 Generated with Claude Code