-
Notifications
You must be signed in to change notification settings - Fork 496
/
Copy pathUserTimingsHandler.ts
206 lines (188 loc) · 6.64 KB
/
UserTimingsHandler.ts
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Helpers from '../helpers/helpers.js';
import * as Types from '../types/types.js';
import {HandlerState} from './types.js';
/**
* IMPORTANT!
* See UserTimings.md in this directory for some handy documentation on
* UserTimings and the trace events we parse currently.
**/
let syntheticEvents: Types.Events.SyntheticEventPair<Types.Events.PairableAsync>[] = [];
const performanceMeasureEvents: Types.Events.PerformanceMeasure[] = [];
const performanceMarkEvents: Types.Events.PerformanceMark[] = [];
const performanceAttributionEvents: Types.Events.PerformanceAttribution[] = [];
const consoleTimings: (Types.Events.ConsoleTimeBegin|Types.Events.ConsoleTimeEnd)[] = [];
const timestampEvents: Types.Events.TimeStamp[] = [];
export interface UserTimingsData {
/**
* Events triggered with the performance.measure() API.
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure
*/
performanceMeasures: readonly Types.Events.SyntheticUserTimingPair[];
/**
* Events triggered with the performance.mark() API.
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark
*/
performanceMarks: readonly Types.Events.PerformanceMark[];
/**
* Events triggered with the console.time(), console.timeEnd() and
* console.timeLog() API.
* https://developer.mozilla.org/en-US/docs/Web/API/console/time
*/
consoleTimings: readonly Types.Events.SyntheticConsoleTimingPair[];
/**
* Events triggered with the console.timeStamp() API
* https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp
*/
timestampEvents: readonly Types.Events.TimeStamp[];
/**
* Attribution events triggered with the performance.mark() API
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/attribution
*/
performanceAttributions: readonly Types.Events.PerformanceAttribution[];
}
let handlerState = HandlerState.UNINITIALIZED;
export function reset(): void {
syntheticEvents.length = 0;
performanceMeasureEvents.length = 0;
performanceMarkEvents.length = 0;
performanceAttributionEvents.length = 0;
consoleTimings.length = 0;
timestampEvents.length = 0;
handlerState = HandlerState.INITIALIZED;
}
const resourceTimingNames = [
'workerStart',
'redirectStart',
'redirectEnd',
'fetchStart',
'domainLookupStart',
'domainLookupEnd',
'connectStart',
'connectEnd',
'secureConnectionStart',
'requestStart',
'responseStart',
'responseEnd',
];
const navTimingNames = [
'navigationStart',
'unloadEventStart',
'unloadEventEnd',
'redirectStart',
'redirectEnd',
'fetchStart',
'commitNavigationEnd',
'domainLookupStart',
'domainLookupEnd',
'connectStart',
'connectEnd',
'secureConnectionStart',
'requestStart',
'responseStart',
'responseEnd',
'domLoading',
'domInteractive',
'domContentLoadedEventStart',
'domContentLoadedEventEnd',
'domComplete',
'loadEventStart',
'loadEventEnd',
];
// These are events dispatched under the blink.user_timing category
// but that the user didn't add. Filter them out so that they do not
// Appear in the timings track (they still appear in the main thread
// flame chart).
const ignoredNames = [...resourceTimingNames, ...navTimingNames];
/**
* Similar to the default {@see Helpers.Trace.eventTimeComparator}
* but with a twist:
* In case of equal start and end times, always put the second event
* first.
*
* Explanation:
* User timing entries come as trace events dispatched when
* performance.measure/mark is called. The trace events buffered in
* devtools frontend are sorted by the start time. If their start time
* is the same, then the event for the first call will appear first.
*
* When entries are meant to be stacked, the corresponding
* performance.measure calls usually are done in bottom-up direction:
* calls for children first and for parent later (because the call
* is usually done when the measured task is over). This means that
* when two user timing events have the start and end time, usually the
* second event is the parent of the first. Hence the switch.
*
*/
function userTimingComparator(
a: Helpers.Trace.TimeSpan, b: Helpers.Trace.TimeSpan, originalArray: Helpers.Trace.TimeSpan[]): number {
const aBeginTime = a.ts;
const bBeginTime = b.ts;
if (aBeginTime < bBeginTime) {
return -1;
}
if (aBeginTime > bBeginTime) {
return 1;
}
const aDuration = a.dur ?? 0;
const bDuration = b.dur ?? 0;
const aEndTime = aBeginTime + aDuration;
const bEndTime = bBeginTime + bDuration;
if (aEndTime > bEndTime) {
return -1;
}
if (aEndTime < bEndTime) {
return 1;
}
// Prefer the event located in a further position in the original array.
return originalArray.indexOf(b) - originalArray.indexOf(a);
}
export function handleEvent(event: Types.Events.Event): void {
if (handlerState !== HandlerState.INITIALIZED) {
throw new Error('UserTimings handler is not initialized');
}
if (ignoredNames.includes(event.name)) {
return;
}
if (Types.Events.isPerformanceMeasure(event)) {
performanceMeasureEvents.push(event);
return;
}
if (Types.Events.isPerformanceMark(event)) {
performanceMarkEvents.push(event);
if (Types.Events.isPerformanceAttribution(event)) {
performanceAttributionEvents.push(event);
}
}
if (Types.Events.isConsoleTime(event)) {
consoleTimings.push(event);
}
if (Types.Events.isTimeStamp(event)) {
timestampEvents.push(event);
}
}
export async function finalize(): Promise<void> {
if (handlerState !== HandlerState.INITIALIZED) {
throw new Error('UserTimings handler is not initialized');
}
const asyncEvents = [...performanceMeasureEvents, ...consoleTimings];
syntheticEvents = Helpers.Trace.createMatchedSortedSyntheticEvents(asyncEvents);
syntheticEvents = syntheticEvents.sort((a, b) => userTimingComparator(a, b, [...syntheticEvents]));
handlerState = HandlerState.FINALIZED;
}
export function data(): UserTimingsData {
if (handlerState !== HandlerState.FINALIZED) {
throw new Error('UserTimings handler is not finalized');
}
return {
performanceMeasures: syntheticEvents.filter(e => e.cat === 'blink.user_timing') as
Types.Events.SyntheticUserTimingPair[],
consoleTimings: syntheticEvents.filter(e => e.cat === 'blink.console') as Types.Events.SyntheticConsoleTimingPair[],
// TODO(crbug/41484172): UserTimingsHandler.test.ts fails if this is not copied.
performanceMarks: [...performanceMarkEvents],
timestampEvents: [...timestampEvents],
performanceAttributions: [...performanceAttributionEvents],
};
}