-
Notifications
You must be signed in to change notification settings - Fork 1
/
measure5Breakdowns.js
107 lines (91 loc) · 3.2 KB
/
measure5Breakdowns.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Queue of LoAF entries. Event Timings "lag" behind in reporting.
const loafs = [];
// LoAF Observer
new PerformanceObserver(list => {
for (let entry of list.getEntries()) {
loafs.push(entry);
}
}).observe({
type: 'long-animation-frame',
buffered: true
});
// Event Timing Observer
new PerformanceObserver(list => {
const eventEntries = Array.from(list.getEntries()).sort((a,b) => {
return a.processingStart - b.processingStart;
});
// Optional: Filter down just to frames with "interactions"
const interactionFramesData = splitByFrame(eventEntries)
.filter(data => data.events.some(entry => entry.interactionId > 0));
for (let frameData of interactionFramesData) {
// frameData is: { loaf, events: [] }
visualizeFrameData(frameData);
}
}).observe({
type: 'event',
durationThreshold: 0,
buffered: true
});
// Use LoAF entries to group event timing entries by frame
function splitByFrame(eventEntries) {
const framesByStartTime = {};
for (let entry of eventEntries) {
// Process the LoAF queue one at a time
// Once we find the right loaf entry, we stop iterating
for (let loaf; loaf = loafs[0]; loafs.shift()) {
const renderEnd = loaf.startTime + loaf.duration;
// This event is obviously before the current loaf entry
// This shouldn't happen, except when using buffered:true
if (entry.processingEnd < loaf.startTime) break;
// This event is for a future frame
if (entry.processingStart > renderEnd) continue;
// Assert: loaf.startTime <= entry.processingStart
// Assert: renderEnd >= entry.processingEnd
framesByStartTime[loaf.startTime] ??= { loaf, events: [] };
framesByStartTime[loaf.startTime].events.push(entry);
break;
}
}
return Object.values(framesByStartTime);
}
function visualizeFrameData({ loaf, events }) {
let maxPresentationTime = 0;
let totalProcessingTime = 0;
let prevEnd = 0;
for (let { startTime, processingStart, processingEnd, duration } of events) {
maxPresentationTime = Math.max(maxPresentationTime, processingEnd, startTime + duration);
totalProcessingTime += processingEnd - Math.max(processingStart, prevEnd);
prevEnd = processingEnd;
}
const processingStart = events[0].processingStart;
const processingEnd = events.at(-1).processingEnd;
const percent = totalProcessingTime / (processingEnd - processingStart) * 100;
const renderStart = Math.max(loaf.renderStart, processingEnd);
const renderEnd = loaf.startTime + loaf.duration;
// Both event presentation times and loaf renderEnd are rounded, so sometimes one laps the other slightly...
const interactionEndTime = Math.max(maxPresentationTime, renderEnd);
performance.measure(`Interaction`, {
start: events[0].startTime,
end: interactionEndTime
});
performance.measure(`Interaction.InputDelay`, {
start: events[0].startTime,
end: processingStart
});
performance.measure(`Interaction.Processing [${percent.toFixed(1)}%]`, {
start: processingStart,
end: processingEnd
});
performance.measure(`Interaction.RenderingDelay`, {
start: processingEnd,
end: renderStart
});
performance.measure(`Interaction.Rendering`, {
start: renderStart,
end: renderEnd,
});
performance.measure(`Interaction.PresentationDelay`, {
start: renderEnd,
end: interactionEndTime
});
}