next.js
e08d99ac - Turbopack: omit Exit+Enter trace row pairs with (almost) no gap (#99780)

Commit
34 days ago
Turbopack: omit Exit+Enter trace row pairs with (almost) no gap (#99780) ## Summary When a span is exited and then entered again on the same thread almost immediately, the writer drops both rows. Async spans do this on every poll. It cuts the number of Enter/Exit rows almost in half. A pair is only omitted when all of these hold: - same span, same thread, and the `Exit` is still the last row in the thread-local buffer (not sent to the writer thread yet) - nothing was (de)allocated between the Exit and the Enter (otherwise the Enter would need to report it for whatever ran in between) - the gap is **≤ 1µs**, or **≤ 0.1%** of the time between the last `Enter` row written on this thread (of any span; omitted `Enter` rows don't count) and the Exit. For nested spans the last `Enter` can be a nested span's, which only shrinks the allowance. Memory samples are still taken when the `Enter` row is omitted. Allocations reported with the dropped Exit aren't lost. The baseline is rolled back, so the next row of the thread reports them, while the span is on top of the stack again. The viewer then shows the span as entered during the gap. Its self time can be slightly too large, by at most 1µs or 0.1% per gap. This is documented in `contributing/turbopack/tracing.md`. Implementation: - `TraceWriter`'s `WriteGuard` can mark exactly what a callback writes (`mark(marker, |guard| ...)`) and remove that row again with `remove_last_row(marker)`, as long as it still ends the thread local buffer. Any other write, or sending the buffer, invalidates it. `Exit` rows are marked with the span id. - `RawTraceLayer` keeps per-thread state (last Enter timestamp, last exit, allocation baseline). It is reset when the thread id changes. The allowed gap is only computed in `on_enter`, after the cheaper checks pass. Allocations made by this bookkeeping are excluded from the counters. - The clock and the allocation counters can be injected in tests. Stack (on top of #99765): 1. #99777: `no-memory` flag 2. #99778: Merge allocation counters into Enter/Exit rows 3. #99779: Delta-encode allocation counters, skip unchanged ones 4. #99780: **This PR**: omit Exit+Enter pairs with (almost) no gap 5. #99781: Bump the trace header to `TRACEv1` ## Measurements `next build` of `bench/heavy-npm-deps`, release native binding built from source: | | trace size | Enter rows | | --- | --- | --- | | PR 3 | 31.6 MiB | 1,138,850 | | same-timestamp pairs only | 27.2 MiB | 875,258 | | this PR (≤ 1µs or ≤ 0.1%) | 22.9 MiB | 626,956 | A dev build of the binding has almost no same-µs pairs (it's slower), so measure this with a release build. ## Verification - Unit tests for each condition: same/different span, thread and timestamp; allocations in between; an intervening row; a sent buffer; thread slot reuse; the 1µs fast path; exactly at and just over the 0.1% bound; chained omissions; allocation totals preserved - `TraceWriter` tests for marking and removing rows - Captured a trace with a locally built release binding and loaded it in the trace server <!-- NEXT_JS_LLM --> <!-- fleet 4a52842b-f1a2-43cb-9779-3655caada6a0 --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Author
Parents
Loading