Skip to content

Commit e945e4f

Browse files
committed
fix(studio): the timeline scroll gate catches dropped frames in the virtualized view
1 parent c20934a commit e945e4f

5 files changed

Lines changed: 83 additions & 6 deletions

File tree

‎packages/studio/src/player/lib/timelineViewportBudgets.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ describe("timeline viewport budgets", () => {
1919
interactionP95Ms: 50,
2020
constrainedInteractionP95Ms: 75,
2121
constrainedFrameIntervalP95Ms: 75,
22+
ciVirtualizedInteractionP95Ms: 41.7,
23+
ciVirtualizedFrameIntervalP95Ms: 25,
2224
longTaskLimitMs: 50,
2325
constrainedLongTaskLimitMs: 300,
2426
posterCoverageRatio: 0.9,

‎packages/studio/src/player/lib/timelineViewportBudgets.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export interface TimelineViewportBudgets {
2828
frameIntervalP95Ms: number;
2929
constrainedInteractionP95Ms: number;
3030
constrainedFrameIntervalP95Ms: number;
31+
ciVirtualizedInteractionP95Ms: number;
32+
ciVirtualizedFrameIntervalP95Ms: number;
3133
longTaskLimitMs: number;
3234
constrainedLongTaskLimitMs: number;
3335
memoryReturnToleranceRatio: number;
@@ -86,6 +88,8 @@ export const TIMELINE_VIEWPORT_BUDGETS: Readonly<TimelineViewportBudgets> = Obje
8688
frameIntervalP95Ms: 33.3,
8789
constrainedInteractionP95Ms: 75,
8890
constrainedFrameIntervalP95Ms: 75,
91+
ciVirtualizedInteractionP95Ms: 41.7,
92+
ciVirtualizedFrameIntervalP95Ms: 25,
8993
longTaskLimitMs: 50,
9094
constrainedLongTaskLimitMs: 300,
9195
memoryReturnToleranceRatio: 0.15,

‎packages/studio/tests/e2e/timeline-viewport-verdict.mjs‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ export function percentile(values, ratio) {
99
return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * ratio) - 1)];
1010
}
1111

12+
/** The p95 pair a tier is held to; the CI virtualized arm has its own, tighter one. */
13+
export function responsivenessLimits(budgets, tier, rowVirtualization) {
14+
if (tier === "primary")
15+
return {
16+
interactionLimitMs: budgets.interactionP95Ms,
17+
frameIntervalLimitMs: budgets.frameIntervalP95Ms,
18+
};
19+
if (tier === "ci" && rowVirtualization === "on")
20+
return {
21+
interactionLimitMs: budgets.ciVirtualizedInteractionP95Ms,
22+
frameIntervalLimitMs: budgets.ciVirtualizedFrameIntervalP95Ms,
23+
};
24+
return {
25+
interactionLimitMs: budgets.constrainedInteractionP95Ms,
26+
frameIntervalLimitMs: budgets.constrainedFrameIntervalP95Ms,
27+
};
28+
}
29+
1230
function assertSampleCount(expected, interactions, frameIntervals) {
1331
if (expected > 0 && interactions.length === expected && frameIntervals.length === expected)
1432
return;

‎packages/studio/tests/e2e/timeline-viewport-verdict.test.mjs‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { describe, expect, it } from "vitest";
2-
import { gatePassed, judgeResponsiveness, percentile } from "./timeline-viewport-verdict.mjs";
2+
import { TIMELINE_VIEWPORT_BUDGETS } from "../../src/player/lib/timelineViewportBudgets";
3+
import {
4+
gatePassed,
5+
judgeResponsiveness,
6+
percentile,
7+
responsivenessLimits,
8+
} from "./timeline-viewport-verdict.mjs";
39

410
const LIMITS = { samplesPerRun: 63, interactionLimitMs: 75, frameIntervalLimitMs: 75 };
511
const FAST = 49;
@@ -69,6 +75,48 @@ describe("judgeResponsiveness", () => {
6975
});
7076
});
7177

78+
describe("the CI virtualized arm's limits", () => {
79+
const limits = {
80+
samplesPerRun: 63,
81+
...responsivenessLimits(TIMELINE_VIEWPORT_BUDGETS, "ci", "on"),
82+
};
83+
// Two frames is a normal step, three a dropped frame; one frame is a normal interval, two a dropped one.
84+
const steps = (count, normal, slow) =>
85+
Array.from({ length: 5 }, (_, run) =>
86+
Array.from({ length: 63 }, (_, step) => (run * 63 + step < count ? slow : normal)),
87+
);
88+
const interactionRuns = (count) =>
89+
steps(count, 33.3, 50).map((interactions) => ({
90+
interactions,
91+
frameIntervals: interactions.map(() => 16.7),
92+
}));
93+
const frameRuns = (count) =>
94+
steps(count, 16.7, 33.3).map((frameIntervals) => ({
95+
interactions: frameIntervals.map(() => 33.3),
96+
frameIntervals,
97+
}));
98+
99+
it("fails 16 of 315 steps that drop a frame and passes 15", () => {
100+
expect(judgeResponsiveness(interactionRuns(16), limits).passed).toBe(false);
101+
expect(judgeResponsiveness(interactionRuns(15), limits).passed).toBe(true);
102+
});
103+
104+
it("fails 16 of 315 frame intervals that drop a frame and passes 15", () => {
105+
expect(judgeResponsiveness(frameRuns(16), limits).passed).toBe(false);
106+
expect(judgeResponsiveness(frameRuns(15), limits).passed).toBe(true);
107+
});
108+
109+
it("leaves the unvirtualized arm and the other constrained tiers at 75 ms", () => {
110+
const loose = { interactionLimitMs: 75, frameIntervalLimitMs: 75 };
111+
expect(responsivenessLimits(TIMELINE_VIEWPORT_BUDGETS, "ci", "off")).toEqual(loose);
112+
expect(responsivenessLimits(TIMELINE_VIEWPORT_BUDGETS, "low-resource", "on")).toEqual(loose);
113+
expect(responsivenessLimits(TIMELINE_VIEWPORT_BUDGETS, "primary", "on")).toEqual({
114+
interactionLimitMs: 50,
115+
frameIntervalLimitMs: 33.3,
116+
});
117+
});
118+
});
119+
72120
describe("gatePassed", () => {
73121
const passing = {
74122
directScrollApproved: true,

‎packages/studio/tests/e2e/timeline-virtualization.mjs‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
*/
2424
import { platform, arch } from "node:os";
2525
import { launchStudioChrome } from "./chrome-executable.mjs";
26-
import { gatePassed, judgeResponsiveness } from "./timeline-viewport-verdict.mjs";
26+
import {
27+
gatePassed,
28+
judgeResponsiveness,
29+
responsivenessLimits,
30+
} from "./timeline-viewport-verdict.mjs";
2731

2832
const STUDIO_URL = process.env.STUDIO_URL;
2933
const PROFILE = process.env.TIMELINE_PROFILE || "dense-short";
@@ -291,10 +295,11 @@ try {
291295
}
292296

293297
const runs = [];
294-
const interactionLimitMs =
295-
TIER === "primary" ? budgets.interactionP95Ms : budgets.constrainedInteractionP95Ms;
296-
const frameIntervalLimitMs =
297-
TIER === "primary" ? budgets.frameIntervalP95Ms : budgets.constrainedFrameIntervalP95Ms;
298+
const { interactionLimitMs, frameIntervalLimitMs } = responsivenessLimits(
299+
budgets,
300+
TIER,
301+
ROW_VIRTUALIZATION,
302+
);
298303
for (let index = 0; index < budgets.warmupRuns + budgets.measuredRuns; index += 1) {
299304
const run = await collectRun(page);
300305
if (index >= budgets.warmupRuns) runs.push(run);

0 commit comments

Comments
 (0)