Reduce log decoder allocation on 10k-line workloads #83
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Problem and evidence
At 10,000 source lines/s, the loaded browser trial had frame p99 216.7 ms, 14 long tasks and a 163 ms maximum at load1 24.84. A repeat at load1 8.29 had p99 33.4 ms and two long tasks of 75 and 63 ms. All 100,000 records drained in 10.289 s; 10,000 lines / 990,000 bytes remained, with 6,120,808 bytes JS heap and 477 DOM nodes. CPU samples included 219.9 ms in LogRecordDecoder.feed and 500.4 ms of GC. Current feed constructs a DataView for every record and reads each timestamp as BigInt before converting safe values (packages/web/src/lib/log-stream.ts:37-63).
The browser CPU trace is browser-busy-loaded-timeline.json; repeat trace: browser-timeline.json.
Recommendation
Reuse a decoder DataView and avoid per-record BigInt conversion for timestamps exactly representable as JavaScript numbers, retaining a precise fallback for values outside the safe range. Profile render/layout and allocation batches next; decoder savings alone cannot explain every scheduling stall.
Payoff, effort and risk
Expected gain: lower allocation and GC cost, eliminate tasks over 50 ms where possible, and move frame p99 toward one refresh interval.
Quick win vs deeper change: decoder reuse is estimated at ≤½ day; render/layout investigation and batching is a deeper 1–2 day change.
Risk: medium; preserve uint64 precision, split-record handling, UTF-8 boundaries and reactive rendering semantics.
Priority: Now (≤1 day for decoder reuse).
2026-10-04 owner decisions: Matches the 10,000-lines/s terminal/log requirement, phone-first UX and code/tests-as-spec. Keep logs virtualized and bounded at 10,000 lines / 4 MiB. Related: #20 (logs view) and #60 (stream boundary).
Report: perf-audit report
Source: perf-audit 2026-10-04
Started perf-logs-windows, sole writer in /mnt/hdd/infraternal/wt-lp/perf-logs-windows. Branch perf-logs-windows; base/head
173525d835. Scope: log decoder allocations and measured bounded receive-window policy; preserve #77 batched credit and revocation deadlines. Will run targeted stream/decoder tests, throughput and 32 MiB remote-upload regression, then INFRATERNAL_VERIFY_RUN_ID=perf-logs-windows scripts/verify fast; no merge gate or push. Reading audit scripts and searching for missing perf-test-vm workflow; dev-VM diagnostics will be explicitly distinguished from perf-test-VM certification.Decoder implementation reuses its fixed header DataView and reads uint64 timestamps as two uint32 words. High word <=0x1fffff is exactly the 53-bit safe range; zero and unsafe uint64 values retain null behavior without rounding. Added tests cover safe/unsafe boundaries, every header split, every UTF-8 byte split and reset. Targeted Vitest: 1 file / 8 tests passed.
Chromium CDP HeapProfiler measures JS allocation (native heaptrack/dhat do not attribute V8 objects). Three alternating base/candidate 100,000-record trials at 10,000 source lines/s, 99-byte bodies, 13-byte headers, 100 records/feed; samples include objects collected by both GC generations at 1024-byte interval. Initial base/candidate estimates ~32.7 MB/~24.0 MB; exact final medians will follow. Always 10,000 retained lines / 990,000 bytes. Load1 ~3.5–4.3. This is same-host dev-VM diagnostic A/B, not perf-test-VM certification. Method: https://chromedevtools.github.io/devtools-protocol/tot/HeapProfiler/#method-startSampling ; timestamp semantics: https://tc39.es/ecma262/#sec-number.max_safe_integer and https://tc39.es/ecma262/#sec-dataview.prototype.getuint32 .
Perf-test-VM certification is unavailable in this job: .claude/skills/perf-test-vm/SKILL.md is missing, and the skill was not found in the local Codex/Claude skill locations. No remote perf-test VM workflow or connection contract is available. Continuing the authorized implementation, real-service regressions and explicitly labeled Debian dev-VM diagnostics; no absolute performance certification or threshold relaxation is claimed. The integration lead must run the perf-test-VM A/B and independent review before merging.
perf-logs-windows — #83 / #80
Head:
79b211653663437edae7968aaa82a4507d14648a. Branch:perf-logs-windows. Base:173525d8358a2576e681576605a7a7c5f1b9ebdd. Three logical commits; no push, merge gate, issue closure, other AI agents or systemd units.Changes
The log decoder reuses its 13-byte header DataView. Two uint32 words replace per-record BigInts; only nonzero exact 53-bit timestamps become numbers, with unsafe uint64 values still null. Split headers/bodies and UTF-8 behavior remain bounded and covered by tests.
TypeScript declares 1 MiB output receive windows for exec/SSH and keeps logs/events/stats at 256 KiB. OPEN always carries the same size reserved locally; Rust omitted-field defaults and native input windows remain 256 KiB. The six-ms connection-wide CREDIT scheduler, #77 native input batching, signing, audit, authorization and Rust stream paths are unchanged. The simultaneous benchmark now uses actual product defaults rather than a hardcoded 4 MiB log window.
Decoder allocation A/B
Chromium sampled decoder allocations: 32,656,644 → 23,933,944 bytes, 26.71% lower, medians of three alternating trials. Each trial: 100,000 records at 10,000 lines/s, 99-byte bodies + 13-byte headers, 100 records/feed, about 10 seconds. All retain exactly 10,000 lines / 990,000 body bytes and zero partial records. Load1 3.56–4.77.
The profiler samples at 1,024 bytes and includes objects collected by both major and minor GC; numbers are estimates, not exact allocation counts. JavaScript objects live inside V8 and cannot be attributed by native dhat/heaptrack. This uses the appropriate Chromium heap profiler instead. No browser rendering/frame-time improvement is claimed.
Reproduce:
bun e2e/remote/log-decoder-bench.ts BASE_SOURCE packages/web/src/lib/log-stream.ts; extract BASE_SOURCE withgit show 173525d:packages/web/src/lib/log-stream.ts. Raw:decoder-ab.jsonl. Method: Chromium HeapProfiler; timestamp semantics: ECMAScript safe integers and uint32 DataView reads.PTY capacity A/B
Three trials per path/window, 32 MiB batched native PTY output, six-ms credit return; exact byte counts and exit status checked in all 36 trials. MiB/s below measures first-to-last DATA, excluding OPEN and the source’s final one-second sleep. Server/native Rust is identical to the base. Load1 3.68–5.14. These are protocol capacity diagnostics, not xterm ingestion certification.
Raw:
pty-batched.json,pty.log,pty-batched.tsadapted from the audit with this worktree’s harness, release binary, owned label and 512 KiB comparison. Batched source code is/mnt/hdd/infraternal/jobs/perf-audit/terminal-source.c.Paced simultaneous workload and policy choice
The source targets 55.490 MiB/s (four 16,001-byte lines per 1,100 us), with no catch-up bursts; every successful trial includes at least 500 browser echo samples. It uses the existing browser workload through the real release server and enrolled mTLS infraternal agent, with the current TypeScript client bundled for the browser. Actual source throughput was also measured. Real rootful Docker and rootless Podman; fixture log-file cap 1 MiB matches the existing paced benchmark.
The controlled window experiment completed nine embedded Docker and three remote Docker trials before a between-trial
host_offlinerefusal. An earlier run stopped on a newly created fixture’snot_found; its higher-load results are excluded from the controlled table. Neither partial run is presented as a complete provider matrix. Inventory-publication/load admission fences were added to the diagnostic; final policy timing trials additionally assert no tunnel-generation replacement.Controlled window load1 2.17–7.66. Larger log windows improve throughput but systematically worsen echo. They are not adopted. Standalone PTY gains justify the 1 MiB terminal window; larger terminal credit does not add source batching or relax admission.
The complete chosen-policy A/B has three alternating trials per setting/path: old explicit 256 KiB for both channels versus product defaults (256 KiB logs / 1 MiB terminals). All 24 timing trials preserve the tunnel generation. Actual source emitted 38.961–55.165 MiB/s; load1 1.84–9.96.
Peak sampled server RSS (10-ms sampling) was 38.273 MiB. After warming, eight real simultaneous 1 MiB log overrides showed server RSS 38.273 → 38.273 MiB; the ninth OPEN was refused, all CLOSE acknowledgments arrived and a replacement OPEN succeeded. This is a warm RSS observation, not zero allocated bytes or an exact unsampled peak.
Performance certification remains blocked: logs are below 50 MiB/s, multiple echo paths exceed both the 5-ms absolute and 10-ms dev criteria, and final remote echo medians are higher than baseline. Shared-load dev results do not establish an acceptable echo regression or justify relaxing thresholds. The rootless source did not sustain the nominal target in every trial. This job does not certify the simultaneous budget or recommend merging on the fast gate alone. Run the exact candidate/base A/B on the perf-test VM before merge; the required
perf-test-vmskill/connection workflow is unavailable locally. Rendering-frame and xterm-ingest budgets were not measured.Raw controlled experiment:
windows-controlled-partial.json; final matrix:final-policy/windows.json; runnable diagnostic:e2e/remote/window-bench.ts.STREAM_WINDOWS=262144,0reproduces the old/default comparison (0 selects per-kind defaults). The default script setting compares 256 KiB / 512 KiB / 1 MiB explicitly. SetINFRATERNAL_E2E_BINARYto the built release binary andINFRATERNAL_ARTIFACT_DIRto a private artifact directory.Memory math at 500 hosts / 20,000 containers
Correctness and verification
forbiddenCLOSE received in 3.426 / 93.112 / 86.855 / 46.989 ms, all within C+850 ms. Owned physical tunnel stop: auditedhost_offlinein 2.699 ms. Raw:upload-regression.log; driver is the existing binary-stream regression adapted only for label/output directory, 32 MiB selection and 1 MiB output declaration.infraternal.test=perf-logs-windows; final Docker/Podman label queries return no IDs. No other fixtures removed.The first fast-gate attempt failed the package-import declaration check because the allocation script was placed under packages/web. Moving it to the root-owned e2e directory uses the existing Playwright declaration without adding a dependency. Final fast result follows.
Final gate
PASS (exit 0):
INFRATERNAL_VERIFY_RUN_ID=perf-logs-windows scripts/verify fast. Never ran verify merge.Verbatim gate output: