mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 15:09:30 +00:00
Validate on #762 failed once on longTask.editorSeries.maxSingleMs (135/129/163 against 150 ms) and passed on the re-run (127/132/132) without a change. The report carried only the task's maximum and the physical preflight time, so nobody could tell whether the extra time was the preflight, the rest of the resize step, or something else on the runner. The investigation found no measurement defect. The resize part's single Long Task is the task of the one accepted pointer move: ResizeController.move with the live physical preflight inside project, publish and the live labels. No Lit update, forced layout or paint is in it (Long Animation Frames and a Chrome trace show the live paint and the frame's layout in the next rendering step); pointerdown stays in an earlier task. Across 202 CI samples (14 Full Performance runs and both Validate attempts) the task minus the preflight is 20-47 ms with a within-report sigma of at most 2.2 ms; the failed sample had a normal preflight and +25 ms elsewhere, plus extra Long Tasks in its load and space-switch windows: one slow sample of the whole page on top of a level that sits 3-25 % under the ceiling. Each interaction row now reports interactionDiagnostics.resizeLongTask: the judged task split into preflight, projection, publish, labels, the rest of the move, Lit updates and other time, plus the following frame's render and any forced style/layout from Long Animation Frames. The split is computed by a self-contained function from the entries of the very window the gate judges, and the wrappers and observer are installed before the runner's yield, so nothing new runs inside the measured task. A base without the ResizeController.move callback shape reports supported: false with a reason instead of zero shares; the contract declares _resize.move as an optional method. The runner prints one line per sample for the job log. No budget, window or product code changes; the README records the series, the method and how to read the next failure. Issue: #778 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
102 lines
4.2 KiB
JavaScript
102 lines
4.2 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { attributeResizeLongTask } from '../demo/performance/resize-attribution.mjs';
|
|
|
|
// #778: the shape of a real CI sample (Validate 36910217188, attempt 1,
|
|
// sample 0: 135 ms, physical preflight 94.6 ms) on a page clock starting at 1000.
|
|
const span = (name, startMs, endMs) => ({ name, startMs, endMs });
|
|
const moveSpans = (offset = 0) => [
|
|
span('move', 1000.6 + offset, 1134.4 + offset),
|
|
span('project', 1000.8 + offset, 1100.0 + offset),
|
|
span('preflight', 1001.2 + offset, 1095.8 + offset),
|
|
span('publish', 1100.0 + offset, 1101.5 + offset),
|
|
span('labels', 1101.5 + offset, 1134.0 + offset),
|
|
];
|
|
const frame = (duration, renderStart, scripts) => ({ startTime: 1000, duration, renderStart, scripts });
|
|
const partsSum = (result) => ['preflightMs', 'projectOtherMs', 'publishMs', 'labelsMs',
|
|
'moveOtherMs', 'updateMs', 'otherMs'].reduce((sum, key) => sum + result[key], 0);
|
|
|
|
test('the judged move task is split into its phases and the parts add up (#778)', () => {
|
|
const result = attributeResizeLongTask({
|
|
longTasks: [{ startTime: 1000, duration: 135 }],
|
|
spans: [
|
|
// pointerdown's work finished before the window: not this task.
|
|
span('preflight', 990, 995),
|
|
...moveSpans(),
|
|
// the cancel's settled render lands in a later, short task.
|
|
span('update', 1300, 1305),
|
|
],
|
|
frames: [frame(141, 1135.2, [
|
|
{ startTime: 1000, duration: 135, forcedStyleAndLayoutDuration: 0 },
|
|
{ startTime: 1135.3, duration: 4.5, forcedStyleAndLayoutDuration: 0.8 },
|
|
])],
|
|
});
|
|
assert.deepEqual(result, {
|
|
supported: true,
|
|
longTaskMs: 135,
|
|
geometryMoves: 1,
|
|
preflightMs: 94.6,
|
|
projectOtherMs: 4.6,
|
|
publishMs: 1.5,
|
|
labelsMs: 32.5,
|
|
moveOtherMs: 0.6,
|
|
updateMs: 0,
|
|
otherMs: 1.2,
|
|
frameRenderMs: 5.8,
|
|
forcedStyleLayoutMs: 0,
|
|
});
|
|
assert.equal(Number(partsSum(result).toFixed(1)), result.longTaskMs);
|
|
});
|
|
|
|
test('a judged task without the move never borrows the move phases (#778)', () => {
|
|
// Negative witness: the move task is 120 ms, but a later 160 ms task (here
|
|
// a settled render) is the longest one, so it is the task the gate judged.
|
|
const result = attributeResizeLongTask({
|
|
longTasks: [{ startTime: 1000, duration: 120 }, { startTime: 2000, duration: 160 }],
|
|
spans: [...moveSpans(), span('update', 2001, 2150)],
|
|
frames: null,
|
|
});
|
|
assert.equal(result.longTaskMs, 160);
|
|
assert.equal(result.geometryMoves, 0);
|
|
assert.equal(result.preflightMs, 0);
|
|
assert.equal(result.labelsMs, 0);
|
|
assert.equal(result.updateMs, 149);
|
|
assert.equal(result.otherMs, 11);
|
|
assert.equal(result.frameRenderMs, null);
|
|
assert.equal(result.forcedStyleLayoutMs, null);
|
|
});
|
|
|
|
test('a stall outside the timed calls is reported as other, not as product work (#778)', () => {
|
|
// The 163 ms CI sample had a normal preflight and +25 ms elsewhere; this is
|
|
// how such a task reads when the extra time is outside every timed call.
|
|
const result = attributeResizeLongTask({
|
|
longTasks: [{ startTime: 1000, duration: 160 }],
|
|
spans: moveSpans(25),
|
|
frames: [],
|
|
});
|
|
assert.equal(result.geometryMoves, 1);
|
|
assert.equal(result.preflightMs, 94.6);
|
|
assert.equal(result.labelsMs, 32.5);
|
|
assert.equal(result.otherMs, 26.2);
|
|
assert.equal(result.frameRenderMs, null, 'no frame holds the task: unknown, not zero');
|
|
assert.equal(Number(partsSum(result).toFixed(1)), result.longTaskMs);
|
|
});
|
|
|
|
test('missing browser support is reported instead of zero shares (#778)', () => {
|
|
assert.deepEqual(
|
|
attributeResizeLongTask({ longTasks: null, spans: moveSpans() }),
|
|
{ supported: false, reason: 'no Long Task entries' },
|
|
);
|
|
assert.deepEqual(
|
|
attributeResizeLongTask({ longTasks: [], spans: null }),
|
|
{ supported: false, reason: 'no resize phase spans' },
|
|
);
|
|
// A fast runner without any Long Task has nothing to split: the phases are
|
|
// unknown (null), not an invented zero split of a task that does not exist.
|
|
const quiet = attributeResizeLongTask({ longTasks: [], spans: moveSpans() });
|
|
assert.equal(quiet.supported, true);
|
|
assert.equal(quiet.longTaskMs, 0);
|
|
assert.equal(quiet.preflightMs, null);
|
|
assert.equal(quiet.otherMs, null);
|
|
});
|