mirror of
https://github.com/block/buzz.git
synced 2026-10-29 21:41:33 +00:00
Blind-review round 4 (Opus xhigh) findings: - A not-pending frame landing past the wait deadline is rAF starvation (system suspend, App Nap — no visibilitychange), not render time: a fast settle followed by a stall recorded up to 34s as a clean switch under the click-anchored 35s age guard. Such frames now drop. - Visibility accounting now spans the whole trace: one module-level visibilitychange watcher timestamps transitions, and any transition since the click (not just during the settle wait) drops the trace — a window minimized while the fetch was in flight no longer records its absence as switch time. Replaces the per-settle listener. - begin() now clears the previous switch's settled mark and measure too, so a consumer polling the User Timing buffer mid-switch can never read the prior switch's entries as the current one's. - The goChannel comment no longer claims every channel navigation funnels through it: Pulse startDm navigates to the channel route directly and is untraced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Max Lampert <maxwell@squareup.com>
420 lines
15 KiB
JavaScript
420 lines
15 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
shouldAttributeFetch,
|
|
buildSwitchPerfLogRecord,
|
|
resolveSettleAction,
|
|
resolveSettleWait,
|
|
summarizeChannelSwitchTrace,
|
|
} from "./channelSwitchPerf.ts";
|
|
|
|
function trace(overrides = {}) {
|
|
return {
|
|
channelId: "abcdef1234567890",
|
|
startedAt: 1_000,
|
|
routeCommitAt: null,
|
|
windowFetch: null,
|
|
membersFetch: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
test("summary reports total and cache-served fetches", () => {
|
|
const summary = summarizeChannelSwitchTrace(trace(), 1_412.4);
|
|
assert.equal(
|
|
summary,
|
|
"[switch-perf] channel=abcdef12 total=412ms commit=? window=cache members=cache",
|
|
);
|
|
});
|
|
|
|
test("summary includes route commit offset and fetch timings", () => {
|
|
const summary = summarizeChannelSwitchTrace(
|
|
trace({
|
|
routeCommitAt: 1_038,
|
|
windowFetch: { durationMs: 180.6, eventCount: 250 },
|
|
membersFetch: { durationMs: 320.2, memberCount: 10_000 },
|
|
}),
|
|
1_912,
|
|
);
|
|
assert.equal(
|
|
summary,
|
|
"[switch-perf] channel=abcdef12 total=912ms commit=+38ms " +
|
|
"window=250 events in 181ms members=10000 members in 320ms",
|
|
);
|
|
});
|
|
|
|
test("log record carries rounded stage timings and fetch attributions", () => {
|
|
const record = buildSwitchPerfLogRecord(
|
|
trace({
|
|
routeCommitAt: 1_038.4,
|
|
windowFetch: { durationMs: 180.6, eventCount: 250 },
|
|
membersFetch: { durationMs: 320.2, memberCount: 10_000 },
|
|
}),
|
|
1_912.3,
|
|
);
|
|
assert.equal(record.channelId, "abcdef1234567890");
|
|
assert.equal(record.totalMs, 912);
|
|
assert.equal(record.commitOffsetMs, 38);
|
|
assert.deepEqual(record.windowFetch, { durationMs: 181, eventCount: 250 });
|
|
assert.deepEqual(record.membersFetch, {
|
|
durationMs: 320,
|
|
memberCount: 10_000,
|
|
});
|
|
assert.equal(typeof record.ts, "string");
|
|
});
|
|
|
|
test("log record marks cache-served fetches and missing commit as null", () => {
|
|
const record = buildSwitchPerfLogRecord(trace(), 1_412);
|
|
assert.equal(record.commitOffsetMs, null);
|
|
assert.equal(record.windowFetch, null);
|
|
assert.equal(record.membersFetch, null);
|
|
});
|
|
|
|
test("settle resolves only the trace for the settled channel", () => {
|
|
const active = trace();
|
|
assert.deepEqual(resolveSettleAction(active, "abcdef1234567890", 2_000), {
|
|
settledTrace: active,
|
|
clearActive: true,
|
|
});
|
|
assert.deepEqual(resolveSettleAction(null, "abcdef1234567890", 2_000), {
|
|
settledTrace: null,
|
|
clearActive: false,
|
|
});
|
|
});
|
|
|
|
test("a mismatched settle never clobbers a newer switch's trace", () => {
|
|
// Channel A settles after the user already clicked channel B: B's trace
|
|
// must survive so B still gets measured.
|
|
const nextSwitch = trace({ channelId: "bbbb0000bbbb0000" });
|
|
assert.deepEqual(resolveSettleAction(nextSwitch, "abcdef1234567890", 2_000), {
|
|
settledTrace: null,
|
|
clearActive: false,
|
|
});
|
|
});
|
|
|
|
test("settle drops a trace that has timed out", () => {
|
|
const stale = trace({ startedAt: 1_000 });
|
|
assert.deepEqual(resolveSettleAction(stale, "abcdef1234567890", 31_001), {
|
|
settledTrace: null,
|
|
clearActive: true,
|
|
});
|
|
assert.deepEqual(
|
|
resolveSettleAction(stale, "abcdef1234567890", 11_000).settledTrace,
|
|
stale,
|
|
);
|
|
});
|
|
|
|
test("the settle wait records truncated — never as an honest settle — at deadline", () => {
|
|
// Still pending, before the deadline: keep waiting.
|
|
assert.equal(resolveSettleWait(4_999, 5_000, true, 0), "wait");
|
|
// Render caught up: record cleanly.
|
|
assert.deepEqual(resolveSettleWait(1_000, 5_000, false, 0), {
|
|
settleWaitTruncated: false,
|
|
});
|
|
// Deadline expired while still pending: the record must say so — a >5s
|
|
// switch reported as an ordinary settle would hide exactly the tail this
|
|
// tracer exists to expose.
|
|
assert.deepEqual(resolveSettleWait(5_000, 5_000, true, 0), {
|
|
settleWaitTruncated: true,
|
|
});
|
|
});
|
|
|
|
test("a frame-starved trace is dropped, not recorded as a clean settle", () => {
|
|
// rAF suspends in hidden windows, so a queued settle can fire minutes
|
|
// after the click with renderPending long since false — the absence must
|
|
// not be charged to the switch. Nothing legitimate can be older than the
|
|
// 30s settle-entry timeout plus the 5s render wait.
|
|
assert.equal(resolveSettleWait(35_001, 40_000, false, 0), "drop");
|
|
assert.equal(resolveSettleWait(35_001, 40_000, true, 0), "drop");
|
|
// At the bound (a 29.9s settle plus a truncated 5s wait) records survive.
|
|
assert.deepEqual(resolveSettleWait(35_000, 34_900, true, 0), {
|
|
settleWaitTruncated: true,
|
|
});
|
|
});
|
|
|
|
test("a not-pending frame landing past the wait deadline is starvation, not a settle", () => {
|
|
// Settle entered at t=1s (deadline 6s), render caught up, then frames
|
|
// stalled (system suspend, App Nap — no visibilitychange): the next frame
|
|
// lands at t=20s with nothing pending. The gap is starvation; recording
|
|
// it would fabricate a clean 20s switch well under the 35s age guard.
|
|
assert.equal(resolveSettleWait(20_000, 6_000, false, 0), "drop");
|
|
// Still-pending arrivals past the deadline remain truncated records: the
|
|
// render genuinely wasn't done, which is the tail the tracer must keep.
|
|
assert.deepEqual(resolveSettleWait(6_001, 6_000, true, 0), {
|
|
settleWaitTruncated: true,
|
|
});
|
|
});
|
|
|
|
test("a truncated settle is flagged in the summary and the log record", () => {
|
|
const summary = summarizeChannelSwitchTrace(trace(), 1_412, true);
|
|
assert.ok(summary.endsWith(" settle=truncated"), summary);
|
|
const record = buildSwitchPerfLogRecord(trace(), 1_412, true);
|
|
assert.equal(record.settleWaitTruncated, true);
|
|
// Clean settles keep the field out of the line entirely.
|
|
assert.ok(
|
|
!("settleWaitTruncated" in buildSwitchPerfLogRecord(trace(), 1_412)),
|
|
);
|
|
});
|
|
|
|
test("fetches attribute only when started after the switch began", () => {
|
|
const active = trace({ channelId: "abcdef1234567890", startedAt: 1_000 });
|
|
// Started before the switch (stale A→B→A leg): not attributable.
|
|
assert.equal(shouldAttributeFetch(active, "abcdef1234567890", 999), false);
|
|
// Started at/after the switch: attributable.
|
|
assert.equal(shouldAttributeFetch(active, "abcdef1234567890", 1_000), true);
|
|
assert.equal(shouldAttributeFetch(active, "abcdef1234567890", 1_500), true);
|
|
// Other channel or no trace: never.
|
|
assert.equal(shouldAttributeFetch(active, "bbbb0000bbbb0000", 1_500), false);
|
|
assert.equal(shouldAttributeFetch(null, "abcdef1234567890", 1_500), false);
|
|
});
|
|
|
|
// --- Settle lifecycle: rapid switches and community resets ----------------
|
|
|
|
async function withSettleHarness(run, documentOverrides = {}) {
|
|
const frames = [];
|
|
const originalWindow = globalThis.window;
|
|
const originalDocument = globalThis.document;
|
|
globalThis.window = {
|
|
requestAnimationFrame: (cb) => frames.push(cb) && frames.length,
|
|
cancelAnimationFrame: () => {},
|
|
};
|
|
globalThis.document = {
|
|
addEventListener: () => {},
|
|
querySelector: () => null,
|
|
removeEventListener: () => {},
|
|
visibilityState: "visible",
|
|
...documentOverrides,
|
|
};
|
|
const {
|
|
abandonChannelSwitchTrace,
|
|
beginChannelSwitchTrace,
|
|
cancelRouteExitAbandon,
|
|
scheduleRouteExitAbandon,
|
|
settleChannelSwitchTrace,
|
|
resetChannelSwitchTrace,
|
|
CHANNEL_SWITCH_MEASURE,
|
|
} = await import("./channelSwitchPerf.ts");
|
|
performance.clearMeasures?.(CHANNEL_SWITCH_MEASURE);
|
|
const flush = () => {
|
|
// Drain chained rAFs until quiescent.
|
|
for (let i = 0; i < 20 && frames.length > 0; i += 1) {
|
|
for (const cb of frames.splice(0, frames.length)) cb();
|
|
}
|
|
};
|
|
const measures = () =>
|
|
performance
|
|
.getEntriesByName(CHANNEL_SWITCH_MEASURE)
|
|
.map((entry) => entry.detail?.channelId);
|
|
try {
|
|
await run({
|
|
abandon: abandonChannelSwitchTrace,
|
|
begin: beginChannelSwitchTrace,
|
|
cancelAbandon: cancelRouteExitAbandon,
|
|
scheduleAbandon: scheduleRouteExitAbandon,
|
|
settle: settleChannelSwitchTrace,
|
|
reset: resetChannelSwitchTrace,
|
|
flush,
|
|
measures,
|
|
});
|
|
} finally {
|
|
resetChannelSwitchTrace();
|
|
performance.clearMeasures?.(CHANNEL_SWITCH_MEASURE);
|
|
if (originalWindow === undefined) delete globalThis.window;
|
|
else globalThis.window = originalWindow;
|
|
if (originalDocument === undefined) delete globalThis.document;
|
|
else globalThis.document = originalDocument;
|
|
}
|
|
}
|
|
|
|
test("a switch begun during A's deferred wait drops A's record (no clock theft)", async () => {
|
|
await withSettleHarness(async ({ begin, settle, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
settle("aaaa1111aaaa1111"); // A's deferred-paint wait is now queued
|
|
begin("bbbb2222bbbb2222"); // rapid follow-up switch replaces the trace
|
|
flush();
|
|
// A must NOT be recorded: its settledAt would be sampled from B's
|
|
// timeline, charging B's delay to A.
|
|
assert.deepEqual(measures(), []);
|
|
settle("bbbb2222bbbb2222");
|
|
flush();
|
|
assert.deepEqual(measures(), ["bbbb2222bbbb2222"]);
|
|
});
|
|
});
|
|
|
|
test("a community reset during the deferred wait drops the record", async () => {
|
|
await withSettleHarness(async ({ begin, settle, reset, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
settle("aaaa1111aaaa1111");
|
|
reset();
|
|
flush();
|
|
assert.deepEqual(measures(), []);
|
|
});
|
|
});
|
|
|
|
test("an undisturbed settle records exactly one measure", async () => {
|
|
await withSettleHarness(async ({ begin, settle, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), ["aaaa1111aaaa1111"]);
|
|
});
|
|
});
|
|
|
|
test("beginning a switch clears the previous switch's settled mark and measure", async () => {
|
|
await withSettleHarness(async ({ begin, settle, flush, measures }) => {
|
|
const { CHANNEL_SWITCH_SETTLED_MARK } = await import(
|
|
"./channelSwitchPerf.ts"
|
|
);
|
|
begin("aaaa1111aaaa1111");
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), ["aaaa1111aaaa1111"]);
|
|
// A consumer polling the buffer mid-switch (the Playwright specs, the
|
|
// Performance panel) must never read the PREVIOUS switch's entries as
|
|
// the current one's.
|
|
begin("bbbb2222bbbb2222");
|
|
assert.deepEqual(measures(), []);
|
|
assert.equal(
|
|
performance.getEntriesByName(CHANNEL_SWITCH_SETTLED_MARK).length,
|
|
0,
|
|
);
|
|
});
|
|
});
|
|
|
|
test("a window hidden between click and settle entry drops the trace", async () => {
|
|
const visibilityListeners = [];
|
|
await withSettleHarness(
|
|
async ({ begin, settle, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
assert.ok(visibilityListeners.length >= 1, "watcher armed at begin");
|
|
// The window hides while the fetch is in flight (cmd-H / minimize),
|
|
// then the user returns and the settle runs with the window visible
|
|
// again: the absence sits inside totalMs, so the trace must drop.
|
|
globalThis.document.visibilityState = "hidden";
|
|
for (const listener of visibilityListeners) listener();
|
|
globalThis.document.visibilityState = "visible";
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), []);
|
|
},
|
|
{
|
|
addEventListener: (type, listener) => {
|
|
if (type === "visibilitychange") visibilityListeners.push(listener);
|
|
},
|
|
},
|
|
);
|
|
});
|
|
|
|
test("abandoned switches never accumulate start marks", async () => {
|
|
await withSettleHarness(async ({ abandon, begin }) => {
|
|
const { CHANNEL_SWITCH_START_MARK } = await import(
|
|
"./channelSwitchPerf.ts"
|
|
);
|
|
performance.clearMarks?.(CHANNEL_SWITCH_START_MARK);
|
|
// Traces that die without recording (forum visits, route exits, drops)
|
|
// never reach record()'s buffer clearing — begin() must bound the
|
|
// buffer itself or weeks-long sessions accumulate a mark per abandon.
|
|
for (const channelId of ["aaaa", "bbbb", "cccc", "dddd"]) {
|
|
begin(channelId);
|
|
abandon(channelId);
|
|
}
|
|
assert.equal(
|
|
performance.getEntriesByName(CHANNEL_SWITCH_START_MARK).length,
|
|
1,
|
|
);
|
|
performance.clearMarks?.(CHANNEL_SWITCH_START_MARK);
|
|
});
|
|
});
|
|
|
|
test("a settle in a hidden window drops the trace instead of recording", async () => {
|
|
await withSettleHarness(
|
|
async ({ begin, settle, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
// rAF is suspended while hidden; the queued chain would only fire when
|
|
// the user returns, charging the whole absence to the switch.
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), []);
|
|
// The trace was released, not wedged: a later stale settle is a no-op.
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), []);
|
|
},
|
|
{ visibilityState: "hidden" },
|
|
);
|
|
});
|
|
|
|
test("a window hidden during the settle wait drops the record", async () => {
|
|
const visibilityListeners = [];
|
|
await withSettleHarness(
|
|
async ({ begin, settle, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
settle("aaaa1111aaaa1111");
|
|
assert.equal(visibilityListeners.length, 1, "wait registers a listener");
|
|
// The user cmd-tabs away mid-wait; frames resume only on return.
|
|
visibilityListeners[0]();
|
|
flush();
|
|
assert.deepEqual(measures(), []);
|
|
},
|
|
{
|
|
addEventListener: (type, listener) => {
|
|
if (type === "visibilitychange") visibilityListeners.push(listener);
|
|
},
|
|
},
|
|
);
|
|
});
|
|
|
|
test("a scheduled route-exit abandon canceled in the same task keeps the trace", async () => {
|
|
await withSettleHarness(
|
|
async ({
|
|
begin,
|
|
cancelAbandon,
|
|
scheduleAbandon,
|
|
settle,
|
|
flush,
|
|
measures,
|
|
}) => {
|
|
begin("aaaa1111aaaa1111");
|
|
// StrictMode's dev-only effect replay: cleanup schedules the abandon,
|
|
// the synchronous re-setup cancels it before the microtask runs.
|
|
scheduleAbandon("aaaa1111aaaa1111");
|
|
cancelAbandon("aaaa1111aaaa1111");
|
|
await Promise.resolve();
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), ["aaaa1111aaaa1111"]);
|
|
},
|
|
);
|
|
});
|
|
|
|
test("an uncanceled route-exit abandon drops the trace before any frame fires", async () => {
|
|
await withSettleHarness(
|
|
async ({ begin, scheduleAbandon, settle, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
scheduleAbandon("aaaa1111aaaa1111");
|
|
await Promise.resolve();
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), []);
|
|
},
|
|
);
|
|
});
|
|
|
|
test("leaving the channel surface abandons the trace; history-back records nothing", async () => {
|
|
await withSettleHarness(
|
|
async ({ abandon, begin, settle, flush, measures }) => {
|
|
begin("aaaa1111aaaa1111");
|
|
// Route exit (Projects/Home): the channel screen unmounts before the
|
|
// trace settled and abandons it.
|
|
abandon("aaaa1111aaaa1111");
|
|
// History-back re-enters the channel without goChannel; its settle must
|
|
// find no trace — otherwise the time spent away would be recorded as
|
|
// switch latency.
|
|
settle("aaaa1111aaaa1111");
|
|
flush();
|
|
assert.deepEqual(measures(), []);
|
|
},
|
|
);
|
|
});
|