-
Notifications
You must be signed in to change notification settings - Fork 412
/
Copy pathmarker-timing.js
309 lines (286 loc) · 9.76 KB
/
marker-timing.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
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @flow
import type {
CategoryList,
Marker,
MarkerIndex,
MarkerTiming,
MarkerTimingAndBuckets,
} from 'firefox-profiler/types';
// Arbitrarily set an upper limit for adding marker depths, avoiding very long
// overlapping marker timings.
const MAX_STACKING_DEPTH = 300;
/**
* This function computes the timing information for laying out the markers in the
* MarkerChart component. Each marker is put into a single row based on its name. In
* addition they are grouped by "buckets", which is based off of their category.
* This structure is a simple array, as it makes it very easy to loop through the
* fixed height rows in the canvas, and draw only what is in view.
*
* e.g. An array of 20 markers named either "A", "B", or "C" would be translated into
* something that looks like:
*
* [
* {
* name: "A",
* start: [0, 23, 35, 65, 75],
* end: [1, 25, 37, 67, 77],
* index: [0, 2, 5, 6, 8],
* bucket: "DOM",
* instantOnly: false,
* length: 5,
* },
* { // First line of B markers
* name: "B",
* start: [1, 28, 39, 69, 70],
* end: [2, 29, 49, 70, 77],
* index: [1, 3, 7, 9, 10],
* bucket: "DOM",
* instantOnly: false,
* length: 5,
* },
* { // Second line of B markers
* name: "B",
* start: [1, 28, 39, 69, 70],
* end: [2, 29, 49, 70, 77],
* index: [1, 3, 7, 9, 10],
* bucket: "DOM",
* instantOnly: false,
* length: 5,
* },
* {
* name: "C",
* start: [10, 33, 45, 75, 85],
* end: [11, 35, 47, 77, 87],
* index: [4, 11, 12, 13, 14],
* bucket: "Other",
* instantOnly: false,
* length: 5,
* },
* ]
*
* If a marker of a name has timings that overlap in a single row, then it is broken
* out into multiple rows, with the overlapping timings going in the next rows. The
* getMarkerTiming tests show the behavior of how this works in practice.
*
* This structure allows the markers to easily be laid out like this example below:
* ____________________________________________
* | | GC/CC | <- Bucket, represented as a `string`
* | GCMajor | *---------------------* | <- MarkerTimingRow
* | GCMinor | *--* *--* *--* | <- MarkerTimingRow
* | | DOM | <- Bucket
* | User Timings | *----------------* | <- MarkerTimingRow
* | User Timings | *------------* | <- MarkerTimingRow
* | User Timings | *--* *---* | <- MarkerTimingRow
* |______________|_____________________________|
*
* Note that the bucket names aren't present as a result of this function,
* they're inserted in `getMarkerTimingAndBuckets`.
*/
export function getMarkerTiming(
getMarker: (MarkerIndex) => Marker,
markerIndexes: MarkerIndex[],
// Categories can be null for things like Network Markers, where we don't care to
// break things up by category.
categories: ?CategoryList
): MarkerTiming[] {
// Each marker type will have it's own timing information, later collapse these into
// a single array.
const intervalMarkerTimingsMap: Map<string, MarkerTiming[]> = new Map();
// Instant markers are on separate lines.
const instantMarkerTimingsMap: Map<string, MarkerTiming> = new Map();
// Go through all of the markers.
for (const markerIndex of markerIndexes) {
const marker = getMarker(markerIndex);
const addCurrentMarkerToMarkerTiming = (markerTiming: MarkerTiming) => {
markerTiming.start.push(marker.start);
markerTiming.end.push(
// If this is an instant marker, the start time and end time will match.
// The chart will then be responsible for drawing this differently.
marker.end === null ? marker.start : marker.end
);
markerTiming.index.push(markerIndex);
markerTiming.length++;
};
const bucketName = categories ? categories[marker.category].name : 'None';
// We want to group all network requests in the same line. Indeed they all
// have different names and they'd end up with one single request in each
// line without this special handling.
const markerLineName =
marker.data && marker.data.type === 'Network'
? 'Network Requests'
: marker.name;
const emptyTiming = ({ instantOnly }): MarkerTiming => ({
start: [],
end: [],
index: [],
name: markerLineName,
bucket: bucketName,
instantOnly,
length: 0,
});
if (marker.end === null) {
// This is an instant marker.
let instantMarkerTiming = instantMarkerTimingsMap.get(markerLineName);
if (!instantMarkerTiming) {
instantMarkerTiming = emptyTiming({ instantOnly: true });
instantMarkerTimingsMap.set(markerLineName, instantMarkerTiming);
}
addCurrentMarkerToMarkerTiming(instantMarkerTiming);
continue;
}
// This is an interval marker.
let markerTimingsForName = intervalMarkerTimingsMap.get(markerLineName);
if (markerTimingsForName === undefined) {
markerTimingsForName = [];
intervalMarkerTimingsMap.set(markerLineName, markerTimingsForName);
}
// Find the first row where the new marker fits.
// Since the markers are sorted, look at the last added marker in this row. If
// the new marker fits, go ahead and insert it.
const foundMarkerTiming = markerTimingsForName.find(
(markerTiming) =>
markerTiming.end[markerTiming.length - 1] <= marker.start
);
if (foundMarkerTiming) {
addCurrentMarkerToMarkerTiming(foundMarkerTiming);
continue;
}
if (markerTimingsForName.length >= MAX_STACKING_DEPTH) {
// There are too many markers stacked around the same time already, let's
// ignore this marker.
continue;
}
// Otherwise, let's add a new row!
const newTiming = emptyTiming({ instantOnly: false });
addCurrentMarkerToMarkerTiming(newTiming);
markerTimingsForName.push(newTiming);
continue;
}
// Flatten out the maps into a single array.
// One item in this array is one line in the drawn canvas.
const allMarkerTimings = [...instantMarkerTimingsMap.values()].concat(
...intervalMarkerTimingsMap.values()
);
// Sort all the marker timings in place, first by the bucket, then by their names.
allMarkerTimings.sort((a, b) => {
if (a.bucket !== b.bucket) {
// Sort by buckets first.
// Show the 'Test' category first. Test markers are almost guaranteed to
// be the most relevant when they exist.
if (a.bucket === 'Test') {
return -1;
}
if (b.bucket === 'Test') {
return 1;
}
// Put the 'Other' category last.
if (a.bucket === 'Other') {
return 1;
}
if (b.bucket === 'Other') {
return -1;
}
// Sort alphabetically for the remaining categories.
return a.bucket > b.bucket ? 1 : -1;
}
if (a.name === b.name) {
// These 2 lines are for the same marker name.
// The instant markers need to come first.
if (a.instantOnly) {
return -1;
}
if (b.instantOnly) {
return 1;
}
// Otherwise keep the original ordering.
return 0;
}
// Put network requests at the end of the Network category.
if (a.bucket === 'Network') {
if (a.name === 'Network Requests') {
return 1;
}
if (b.name === 'Network Requests') {
return -1;
}
}
// Sort by names second
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
});
return allMarkerTimings;
}
/**
* This function builds on `getMarkerTiming` above, inserting the bucket names
* that are plain strings as items in the result array.
*
* Reusing the previous example of an array of 20 markers named either "A", "B",
* or "C", this is the result we'd get:
*
* [
* "DOM", // The bucket, inserted by this function after `getMarkerTiming`.
* {
* name: "A",
* start: [0, 23, 35, 65, 75],
* end: [1, 25, 37, 67, 77],
* index: [0, 2, 5, 6, 8],
* bucket: "DOM",
* instantOnly: false,
* length: 5,
* },
* {
* name: "B",
* start: [1, 28, 39, 69, 70],
* end: [2, 29, 49, 70, 77],
* index: [1, 3, 7, 9, 10],
* bucket: "DOM",
* instantOnly: false,
* length: 5,
* },
* { // Second line of B markers
* name: "B",
* start: [1, 28, 39, 69, 70],
* end: [2, 29, 49, 70, 77],
* index: [1, 3, 7, 9, 10],
* bucket: "DOM",
* instantOnly: false,
* length: 5,
* },
* "Other", // The bucket.
* {
* name: "C",
* start: [10, 33, 45, 75, 85],
* end: [11, 35, 47, 77, 87],
* index: [4, 11, 12, 13, 14],
* bucket: "Other",
* instantOnly: false,
* length: 5,
* },
* ]
*/
export function getMarkerTimingAndBuckets(
getMarker: (MarkerIndex) => Marker,
markerIndexes: MarkerIndex[],
// Categories can be null for things like Network Markers, where we don't care to
// break things up by category.
categories: ?CategoryList
): MarkerTimingAndBuckets {
const allMarkerTimings = getMarkerTiming(
getMarker,
markerIndexes,
categories
);
// Interleave the bucket names in between the marker timings.
const markerTimingsAndBuckets: MarkerTimingAndBuckets = [];
let prevBucket;
for (const markerTiming of allMarkerTimings) {
if (markerTiming.bucket !== prevBucket) {
markerTimingsAndBuckets.push(markerTiming.bucket);
prevBucket = markerTiming.bucket;
}
markerTimingsAndBuckets.push(markerTiming);
}
return markerTimingsAndBuckets;
}