test(perf): refuse a J1 idle window that opened late after the settle point

Addresses review: the idle window opens in the settle timer's callback while
the settle point is that timer's deadline, so a late callback left ticks
uncounted between the two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-01 06:27:24 +02:00
1 parent 454c1fe10a
commit 7e383ca675
3 files changed
+44 -8

No files matched your search

@@ -31,8 +31,8 @@ function measurement(
firstCardPaintEpochMs: 2_650,
idle: {
domMutations: 12,
endEpochMs: 36_000.04,
startEpochMs: 6_000,
endEpochMs: 33_200.04,
startEpochMs: 3_200,
status: 'done',
ticks: 31,
},
@@ -198,7 +198,7 @@ test('maps the probe, IPC capture and main counters to exact counters and spawn-
assert.deepEqual(record.evidence['idle'], {
domMutations: 12,
durationMs: 30_000,
settledToIdleStartMs: 2_819.9,
settledToIdleStartMs: 19.9,
});
});
@@ -278,14 +278,32 @@ test('refuses a launch without a complete, on-time idle window after the settle
),
/idle-before-settle/
);
// The settle point is 3_180.06: a window opened 120 ms after it left
// ticks uncounted in between.
assert.throws(
() =>
toLaunchIterationRecord(0, false, withIdle({ endEpochMs: 35_000 })),
toLaunchIterationRecord(
0,
false,
withIdle({ endEpochMs: 33_300.1, startEpochMs: 3_300.1 })
),
/idle-start-late/
);
assert.doesNotThrow(() =>
toLaunchIterationRecord(
0,
false,
withIdle({ endEpochMs: 33_250, startEpochMs: 3_250 })
)
);
assert.throws(
() =>
toLaunchIterationRecord(0, false, withIdle({ endEpochMs: 33_000 })),
/idle-window-short/
);
assert.throws(
() =>
toLaunchIterationRecord(0, false, withIdle({ endEpochMs: 37_500 })),
toLaunchIterationRecord(0, false, withIdle({ endEpochMs: 34_500 })),
/idle-window-late/
);
assert.equal(
@@ -332,7 +350,7 @@ test('refuses a launch whose settle window did not end after the first-card cuto
const capped = toLaunchIterationRecord(
0,
false,
withSettle({ epochMs: 5_650, status: 'cap' })
withSettle({ epochMs: 3_150, status: 'cap' })
);
assert.equal(
(capped.evidence['settle'] as { reason: string }).reason,
@@ -50,6 +50,14 @@ export const LAUNCH_JOURNEY_UNAVAILABLE_COUNTERS: Readonly<
*/
export const LAUNCH_JOURNEY_IDLE_LATE_TOLERANCE_MS = 1_000;
/**
* The idle window opens in the settle timer's callback, but the settle point
* is that timer's deadline. A callback that ran later than this left ticks
* between the two outside both windows, and means the page was still busy
* at the settle point, so the iteration is refused rather than undercounted.
*/
export const LAUNCH_JOURNEY_IDLE_START_TOLERANCE_MS = 100;
export interface LaunchJourneyMeasurement {
readonly electronVersion: string;
readonly gate: JourneyRendererGateState;
@@ -230,6 +238,12 @@ function assertLaunchIdleWindow(
if (idle.startEpochMs < settledEpochMs) {
throw new Error('launch-journey-record-idle-before-settle');
}
if (
idle.startEpochMs - settledEpochMs >
LAUNCH_JOURNEY_IDLE_START_TOLERANCE_MS
) {
throw new Error('launch-journey-record-idle-start-late');
}
const durationMs = idle.endEpochMs - idle.startEpochMs;
// Timers may fire up to a millisecond early after clamping.
if (durationMs < JOURNEY_IDLE_WINDOW_MS - 1) {
+6 -2
View File
@@ -201,9 +201,13 @@ zoneless change detection removes.
The window opens when the settle window closes, so startup data still
landing is not idle work, and it is timed by a renderer `setTimeout`. The
record refuses an iteration whose window opened before the settle point or
more than 100 ms after it (`launch-journey-record-idle-start-late`), or
lasted less than 30 s, or more than 1 s longer
(`launch-journey-record-idle-window-late`): a timer that late means the
page was busy, not idle. `evidence.idle` keeps the measured `durationMs` and
(`launch-journey-record-idle-window-late`). The window opens in the settle
timer's callback while the settle point is that timer's deadline, so a late
callback would leave ticks between the two outside both windows; either late
timer means the page was busy, not idle. Locally the window opened 1-4 ms
after the settle point. `evidence.idle` keeps the measured `durationMs` and
`settledToIdleStartMs`. The main-process counters and the IPC capture are
read after the window, which does not move them: they are frozen earlier.
J2's launches skip the window (`runLaunchJourney` with `idleWindowMs: null`),