Scheduling profiler: Added lane labels and durations to React measures (#22029)
Brian Vaughn committed
Aug 5, 2021 at 13:50 UTC
a8725a3e628e8e75d408101e5565b2d2af3902c3
16 files changed
+1297
-778
packages/react-devtools-scheduling-profiler/src/EventTooltip.js
+11
-2
@@ -105,6 +105,7 @@ export default function EventTooltip({
105
} else if (schedulingEvent !== null) {
106
return (
107
<TooltipSchedulingEvent
108
+ data={data}
109
schedulingEvent={schedulingEvent}
110
tooltipRef={tooltipRef}
111
/>
@@ -234,9 +235,11 @@ const TooltipNativeEvent = ({
235
};
236
237
const TooltipSchedulingEvent = ({
238
+ data,
239
schedulingEvent,
240
tooltipRef,
241
}: {
242
+ data: ReactProfilerData,
243
schedulingEvent: SchedulingEvent,
244
tooltipRef: Return<typeof useRef>,
245
}) => {
@@ -257,8 +260,10 @@ const TooltipSchedulingEvent = ({
260
case 'schedule-render':
261
case 'schedule-state-update':
262
case 'schedule-force-update':
260
- laneLabels = schedulingEvent.laneLabels;
263
lanes = schedulingEvent.lanes;
264
+ laneLabels = lanes.map(
265
+ lane => ((data.laneToLabelMap.get(lane): any): string),
266
+ );
267
break;
268
}
269
@@ -366,9 +371,13 @@ const TooltipReactMeasure = ({
371
return null;
372
}
373
369
- const {batchUID, duration, timestamp, lanes, laneLabels} = measure;
374
+ const {batchUID, duration, timestamp, lanes} = measure;
375
const [startTime, stopTime] = getBatchRange(batchUID, data);
376
377
+ const laneLabels = lanes.map(
378
+ lane => ((data.laneToLabelMap.get(lane): any): string),
379
+ );
380
+
381
return (
382
<div className={styles.Tooltip} ref={tooltipRef}>
383
<div className={styles.TooltipSection}>
packages/react-devtools-scheduling-profiler/src/constants.js
+3
@@ -13,3 +13,6 @@ export {
13
} from 'react-devtools-shared/src/constants.js';
14
15
export const REACT_TOTAL_NUM_LANES = 31;
16
+
17
+// Increment this number any time a backwards breaking change is made to the profiler metadata.
18
+export const SCHEDULING_PROFILER_VERSION = 1;
packages/react-devtools-scheduling-profiler/src/content-views/ComponentMeasuresView.js
+1
-2
@@ -161,8 +161,7 @@ export class ComponentMeasuresView extends View {
161
context,
162
visibleArea,
163
visibleArea,
164
- 'center',
165
- COLORS.TEXT_DIM_COLOR,
164
+ {fillStyle: COLORS.TEXT_DIM_COLOR, textAlign: 'center'},
165
);
166
}
167
packages/react-devtools-scheduling-profiler/src/content-views/ReactMeasuresView.js
+54
-2
@@ -16,6 +16,8 @@ import type {
16
ViewRefs,
17
} from '../view-base';
18
19
+import {formatDuration} from '../utils/formatting';
20
+import {drawText} from './utils/text';
21
import {
22
durationToWidth,
23
positioningScaleFactor,
@@ -102,17 +104,19 @@ export class ReactMeasuresView extends View {
104
context: CanvasRenderingContext2D,
105
rect: Rect,
106
measure: ReactMeasure,
107
+ nextMeasure: ReactMeasure | null,
108
baseY: number,
109
scaleFactor: number,
110
showGroupHighlight: boolean,
111
showHoverHighlight: boolean,
112
) {
110
- const {frame} = this;
113
+ const {frame, visibleArea} = this;
114
const {timestamp, type, duration} = measure;
115
116
let fillStyle = null;
117
let hoveredFillStyle = null;
118
let groupSelectedFillStyle = null;
119
+ let textFillStyle = null;
120
121
// We could change the max to 0 and just skip over rendering anything that small,
122
// but this has the effect of making the chart look very empty when zoomed out.
@@ -131,11 +135,29 @@ export class ReactMeasuresView extends View {
135
return; // Not in view
136
}
137
138
+ const drawableRect = intersectionOfRects(measureRect, rect);
139
+ let textRect = measureRect;
140
+
141
switch (type) {
142
case 'commit':
143
fillStyle = COLORS.REACT_COMMIT;
144
hoveredFillStyle = COLORS.REACT_COMMIT_HOVER;
145
groupSelectedFillStyle = COLORS.REACT_COMMIT_HOVER;
146
+ textFillStyle = COLORS.REACT_COMMIT_TEXT;
147
+
148
+ // Commit phase rects are overlapped by layout and passive rects,
149
+ // and it looks bad if text flows underneath/behind these overlayed rects.
150
+ if (nextMeasure != null) {
151
+ textRect = {
152
+ ...measureRect,
153
+ size: {
154
+ width:
155
+ timestampToPosition(nextMeasure.timestamp, scaleFactor, frame) -
156
+ x,
157
+ height: REACT_MEASURE_HEIGHT,
158
+ },
159
+ };
160
+ }
161
break;
162
case 'render-idle':
163
// We could render idle time as diagonal hashes.
@@ -149,22 +171,24 @@ export class ReactMeasuresView extends View {
171
fillStyle = COLORS.REACT_RENDER;
172
hoveredFillStyle = COLORS.REACT_RENDER_HOVER;
173
groupSelectedFillStyle = COLORS.REACT_RENDER_HOVER;
174
+ textFillStyle = COLORS.REACT_RENDER_TEXT;
175
break;
176
case 'layout-effects':
177
fillStyle = COLORS.REACT_LAYOUT_EFFECTS;
178
hoveredFillStyle = COLORS.REACT_LAYOUT_EFFECTS_HOVER;
179
groupSelectedFillStyle = COLORS.REACT_LAYOUT_EFFECTS_HOVER;
180
+ textFillStyle = COLORS.REACT_LAYOUT_EFFECTS_TEXT;
181
break;
182
case 'passive-effects':
183
fillStyle = COLORS.REACT_PASSIVE_EFFECTS;
184
hoveredFillStyle = COLORS.REACT_PASSIVE_EFFECTS_HOVER;
185
groupSelectedFillStyle = COLORS.REACT_PASSIVE_EFFECTS_HOVER;
186
+ textFillStyle = COLORS.REACT_PASSIVE_EFFECTS_TEXT;
187
break;
188
default:
189
throw new Error(`Unexpected measure type "${type}"`);
190
}
191
167
- const drawableRect = intersectionOfRects(measureRect, rect);
192
context.fillStyle = showHoverHighlight
193
? hoveredFillStyle
194
: showGroupHighlight
@@ -176,6 +200,12 @@ export class ReactMeasuresView extends View {
200
drawableRect.size.width,
201
drawableRect.size.height,
202
);
203
+
204
+ if (textFillStyle !== null) {
205
+ drawText(formatDuration(duration), context, textRect, visibleArea, {
206
+ fillStyle: textFillStyle,
207
+ });
208
+ }
209
}
210
211
draw(context: CanvasRenderingContext2D) {
@@ -211,6 +241,27 @@ export class ReactMeasuresView extends View {
241
);
242
}
243
244
+ // Render lane labels
245
+ const label = this._profilerData.laneToLabelMap.get(lane);
246
+ if (label == null) {
247
+ console.warn(`Could not find label for lane ${lane}.`);
248
+ } else {
249
+ const labelRect = {
250
+ origin: {
251
+ x: visibleArea.origin.x,
252
+ y: baseY,
253
+ },
254
+ size: {
255
+ width: visibleArea.size.width,
256
+ height: REACT_LANE_HEIGHT,
257
+ },
258
+ };
259
+
260
+ drawText(label, context, labelRect, visibleArea, {
261
+ fillStyle: COLORS.TEXT_DIM_COLOR,
262
+ });
263
+ }
264
+
265
// Draw measures
266
for (let j = 0; j < measuresForLane.length; j++) {
267
const measure = measuresForLane[j];
@@ -222,6 +273,7 @@ export class ReactMeasuresView extends View {
273
context,
274
visibleArea,
275
measure,
276
+ measuresForLane[j + 1] || null,
277
baseY,
278
scaleFactor,
279
showGroupHighlight,
packages/react-devtools-scheduling-profiler/src/content-views/constants.js
+17
-1
@@ -19,7 +19,7 @@ export const SUSPENSE_EVENT_HEIGHT = 14;
19
export const PENDING_SUSPENSE_EVENT_SIZE = 8;
20
export const REACT_EVENT_DIAMETER = 6;
21
export const USER_TIMING_MARK_SIZE = 8;
22
-export const REACT_MEASURE_HEIGHT = 9;
22
+export const REACT_MEASURE_HEIGHT = 14;
23
export const BORDER_SIZE = 1;
24
export const FLAMECHART_FRAME_HEIGHT = 14;
25
export const TEXT_PADDING = 3;
@@ -56,12 +56,16 @@ export let COLORS = {
56
REACT_IDLE_HOVER: '',
57
REACT_RENDER: '',
58
REACT_RENDER_HOVER: '',
59
+ REACT_RENDER_TEXT: '',
60
REACT_COMMIT: '',
61
REACT_COMMIT_HOVER: '',
62
+ REACT_COMMIT_TEXT: '',
63
REACT_LAYOUT_EFFECTS: '',
64
REACT_LAYOUT_EFFECTS_HOVER: '',
65
+ REACT_LAYOUT_EFFECTS_TEXT: '',
66
REACT_PASSIVE_EFFECTS: '',
67
REACT_PASSIVE_EFFECTS_HOVER: '',
68
+ REACT_PASSIVE_EFFECTS_TEXT: '',
69
REACT_RESIZE_BAR: '',
70
REACT_RESIZE_BAR_ACTIVE: '',
71
REACT_RESIZE_BAR_BORDER: '',
@@ -132,24 +136,36 @@ export function updateColorsToMatchTheme(element: Element): boolean {
136
REACT_RENDER_HOVER: computedStyle.getPropertyValue(
137
'--color-scheduling-profiler-react-render-hover',
138
),
139
+ REACT_RENDER_TEXT: computedStyle.getPropertyValue(
140
+ '--color-scheduling-profiler-react-render-text',
141
+ ),
142
REACT_COMMIT: computedStyle.getPropertyValue(
143
'--color-scheduling-profiler-react-commit',
144
),
145
REACT_COMMIT_HOVER: computedStyle.getPropertyValue(
146
'--color-scheduling-profiler-react-commit-hover',
147
),
148
+ REACT_COMMIT_TEXT: computedStyle.getPropertyValue(
149
+ '--color-scheduling-profiler-react-commit-text',
150
+ ),
151
REACT_LAYOUT_EFFECTS: computedStyle.getPropertyValue(
152
'--color-scheduling-profiler-react-layout-effects',
153
),
154
REACT_LAYOUT_EFFECTS_HOVER: computedStyle.getPropertyValue(
155
'--color-scheduling-profiler-react-layout-effects-hover',
156
),
157
+ REACT_LAYOUT_EFFECTS_TEXT: computedStyle.getPropertyValue(
158
+ '--color-scheduling-profiler-react-layout-effects-text',
159
+ ),
160
REACT_PASSIVE_EFFECTS: computedStyle.getPropertyValue(
161
'--color-scheduling-profiler-react-passive-effects',
162
),
163
REACT_PASSIVE_EFFECTS_HOVER: computedStyle.getPropertyValue(
164
'--color-scheduling-profiler-react-passive-effects-hover',
165
),
166
+ REACT_PASSIVE_EFFECTS_TEXT: computedStyle.getPropertyValue(
167
+ '--color-scheduling-profiler-react-passive-effects-text',
168
+ ),
169
REACT_RESIZE_BAR: computedStyle.getPropertyValue('--color-resize-bar'),
170
REACT_RESIZE_BAR_ACTIVE: computedStyle.getPropertyValue(
171
'--color-resize-bar-active',
packages/react-devtools-scheduling-profiler/src/content-views/utils/text.js
+28
-11
@@ -14,6 +14,19 @@ import {COLORS, FONT_SIZE, TEXT_PADDING} from '../constants';
14
15
const cachedTextWidths = new Map();
16
17
+export function getTextWidth(
18
+ context: CanvasRenderingContext2D,
19
+ text: string,
20
+): number {
21
+ let measuredWidth = cachedTextWidths.get(text);
22
+ if (measuredWidth == null) {
23
+ measuredWidth = context.measureText(text).width;
24
+ cachedTextWidths.set(text, measuredWidth);
25
+ }
26
+
27
+ return ((measuredWidth: any): number);
28
+}
29
+
30
export function trimText(
31
context: CanvasRenderingContext2D,
32
text: string,
@@ -21,14 +34,7 @@ export function trimText(
34
): string | null {
35
for (let i = text.length - 1; i >= 0; i--) {
36
const trimmedText = i === text.length - 1 ? text : text.substr(0, i) + '…';
24
-
25
- let measuredWidth = cachedTextWidths.get(trimmedText);
26
- if (measuredWidth == null) {
27
- measuredWidth = context.measureText(trimmedText).width;
28
- cachedTextWidths.set(trimmedText, measuredWidth);
29
- }
30
-
31
- if (measuredWidth <= width) {
37
+ if (getTextWidth(context, trimmedText) <= width) {
38
return trimmedText;
39
}
40
}
@@ -36,18 +42,29 @@ export function trimText(
42
return null;
43
}
44
45
+type TextConfig = {|
46
+ fillStyle?: string,
47
+ fontSize?: number,
48
+ textAlign?: 'left' | 'center',
49
+|};
50
+
51
export function drawText(
52
text: string,
53
context: CanvasRenderingContext2D,
54
fullRect: Rect,
55
drawableRect: Rect,
44
- textAlign: 'left' | 'center' = 'left',
45
- fillStyle: string = COLORS.TEXT_COLOR,
56
+ config?: TextConfig,
57
): void {
58
+ const {
59
+ fillStyle = COLORS.TEXT_COLOR,
60
+ fontSize = FONT_SIZE,
61
+ textAlign = 'left',
62
+ } = config || {};
63
+
64
if (fullRect.size.width > TEXT_PADDING * 2) {
65
context.textAlign = textAlign;
66
context.textBaseline = 'middle';
50
- context.font = `${FONT_SIZE}px sans-serif`;
67
+ context.font = `${fontSize}px sans-serif`;
68
69
const {x, y} = fullRect.origin;
70
packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js
+510
-299
@@ -7,10 +7,15 @@
7
8
'use strict';
9
10
+import {getLaneLabels} from 'react-reconciler/src/SchedulingProfiler';
11
import preprocessData, {
12
getLanesFromTransportDecimalBitmask,
13
} from '../preprocessData';
13
-import {REACT_TOTAL_NUM_LANES} from '../../constants';
14
+import {
15
+ REACT_TOTAL_NUM_LANES,
16
+ SCHEDULING_PROFILER_VERSION,
17
+} from '../../constants';
18
+import REACT_VERSION from 'shared/ReactVersion';
19
20
describe(getLanesFromTransportDecimalBitmask, () => {
21
it('should return array of lane numbers from bitmask string', () => {
@@ -109,6 +114,35 @@ describe(preprocessData, () => {
114
};
115
}
116
117
+ function createProfilerVersionEntry() {
118
+ return createUserTimingEntry({
119
+ cat: 'blink.user_timing',
120
+ name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
121
+ });
122
+ }
123
+
124
+ function createReactVersionEntry() {
125
+ return createUserTimingEntry({
126
+ cat: 'blink.user_timing',
127
+ name: '--react-version-' + REACT_VERSION,
128
+ });
129
+ }
130
+
131
+ function createLaneLabelsEntry() {
132
+ return createUserTimingEntry({
133
+ cat: 'blink.user_timing',
134
+ name: '--react-lane-labels-' + getLaneLabels().join(','),
135
+ });
136
+ }
137
+
138
+ function createBoilerplateEntries() {
139
+ return [
140
+ createProfilerVersionEntry(),
141
+ createReactVersionEntry(),
142
+ createLaneLabelsEntry(),
143
+ ];
144
+ }
145
+
146
function createUserTimingData(sampleMarks) {
147
const cpuProfilerSample = createUserTimingEntry({
148
args: {data: {startTime: ++startTime}},
@@ -192,7 +226,6 @@ describe(preprocessData, () => {
226
name: 'Profile',
227
ph: 'P',
228
});
195
-
229
const randomSample = createUserTimingEntry({
230
dur: 100,
231
tdur: 200,
@@ -202,20 +235,62 @@ describe(preprocessData, () => {
235
args: {},
236
});
237
205
- expect(preprocessData([cpuProfilerSample, randomSample])).toStrictEqual({
206
- componentMeasures: [],
207
- duration: 0.002,
208
- flamechart: [],
209
- measures: [],
210
- nativeEvents: [],
211
- otherUserTimingMarks: [],
212
- schedulingEvents: [],
213
- startTime: 1,
214
- suspenseEvents: [],
215
- });
238
+ if (gate(flags => flags.enableSchedulingProfiler)) {
239
+ const data = preprocessData([
240
+ ...createBoilerplateEntries(),
241
+ cpuProfilerSample,
242
+ randomSample,
243
+ ]);
244
+ expect(data).toMatchInlineSnapshot(`
245
+ Object {
246
+ "componentMeasures": Array [],
247
+ "duration": 0.005,
248
+ "flamechart": Array [],
249
+ "laneToLabelMap": Map {
250
+ 0 => "Sync",
251
+ 1 => "InputContinuousHydration",
252
+ 2 => "InputContinuous",
253
+ 3 => "DefaultHydration",
254
+ 4 => "Default",
255
+ 5 => "TransitionHydration",
256
+ 6 => "Transition",
257
+ 7 => "Transition",
258
+ 8 => "Transition",
259
+ 9 => "Transition",
260
+ 10 => "Transition",
261
+ 11 => "Transition",
262
+ 12 => "Transition",
263
+ 13 => "Transition",
264
+ 14 => "Transition",
265
+ 15 => "Transition",
266
+ 16 => "Transition",
267
+ 17 => "Transition",
268
+ 18 => "Transition",
269
+ 19 => "Transition",
270
+ 20 => "Transition",
271
+ 21 => "Transition",
272
+ 22 => "Retry",
273
+ 23 => "Retry",
274
+ 24 => "Retry",
275
+ 25 => "Retry",
276
+ 26 => "Retry",
277
+ 27 => "SelectiveHydration",
278
+ 28 => "IdleHydration",
279
+ 29 => "Idle",
280
+ 30 => "Offscreen",
281
+ },
282
+ "measures": Array [],
283
+ "nativeEvents": Array [],
284
+ "otherUserTimingMarks": Array [],
285
+ "reactVersion": "17.0.3",
286
+ "schedulingEvents": Array [],
287
+ "startTime": 1,
288
+ "suspenseEvents": Array [],
289
+ }
290
+ `);
291
+ }
292
});
293
218
- // NOTE This test doesn't have to be gated because it has hard-coded profiler samples.
294
it('should process legacy data format (before lane labels were added)', () => {
295
const cpuProfilerSample = createUserTimingEntry({
296
args: {data: {startTime: ++startTime}},
@@ -225,10 +300,11 @@ describe(preprocessData, () => {
300
ph: 'P',
301
});
302
228
- expect(
303
+ if (gate(flags => flags.enableSchedulingProfiler)) {
304
// Data below is hard-coded based on an older profile sample.
305
// Should be fine since this is explicitly a legacy-format test.
231
- preprocessData([
306
+ const data = preprocessData([
307
+ ...createBoilerplateEntries(),
308
cpuProfilerSample,
309
createUserTimingEntry({
310
cat: 'blink.user_timing',
@@ -258,140 +334,220 @@ describe(preprocessData, () => {
334
cat: 'blink.user_timing',
335
name: '--commit-stop',
336
}),
261
- ]),
262
- ).toStrictEqual({
263
- componentMeasures: [],
264
- duration: 0.008,
265
- flamechart: [],
266
- measures: [
267
- {
268
- batchUID: 0,
269
- depth: 0,
270
- duration: 0.005,
271
- laneLabels: [],
272
- lanes: [9],
273
- timestamp: 0.003,
274
- type: 'render-idle',
275
- },
276
- {
277
- batchUID: 0,
278
- depth: 0,
279
- duration: 0.001,
280
- laneLabels: [],
281
- lanes: [9],
282
- timestamp: 0.003,
283
- type: 'render',
284
- },
285
- {
286
- batchUID: 0,
287
- depth: 0,
288
- duration: 0.003,
289
- laneLabels: [],
290
- lanes: [9],
291
- timestamp: 0.005,
292
- type: 'commit',
337
+ ]);
338
+ expect(data).toMatchInlineSnapshot(`
339
+ Object {
340
+ "componentMeasures": Array [],
341
+ "duration": 0.011,
342
+ "flamechart": Array [],
343
+ "laneToLabelMap": Map {
344
+ 0 => "Sync",
345
+ 1 => "InputContinuousHydration",
346
+ 2 => "InputContinuous",
347
+ 3 => "DefaultHydration",
348
+ 4 => "Default",
349
+ 5 => "TransitionHydration",
350
+ 6 => "Transition",
351
+ 7 => "Transition",
352
+ 8 => "Transition",
353
+ 9 => "Transition",
354
+ 10 => "Transition",
355
+ 11 => "Transition",
356
+ 12 => "Transition",
357
+ 13 => "Transition",
358
+ 14 => "Transition",
359
+ 15 => "Transition",
360
+ 16 => "Transition",
361
+ 17 => "Transition",
362
+ 18 => "Transition",
363
+ 19 => "Transition",
364
+ 20 => "Transition",
365
+ 21 => "Transition",
366
+ 22 => "Retry",
367
+ 23 => "Retry",
368
+ 24 => "Retry",
369
+ 25 => "Retry",
370
+ 26 => "Retry",
371
+ 27 => "SelectiveHydration",
372
+ 28 => "IdleHydration",
373
+ 29 => "Idle",
374
+ 30 => "Offscreen",
375
},
294
- {
295
- batchUID: 0,
296
- depth: 1,
297
- duration: 0.001,
298
- laneLabels: [],
299
- lanes: [9],
300
- timestamp: 0.006,
301
- type: 'layout-effects',
302
- },
303
- ],
304
- nativeEvents: [],
305
- otherUserTimingMarks: [],
306
- schedulingEvents: [
307
- {
308
- laneLabels: [],
309
- lanes: [9],
310
- timestamp: 0.002,
311
- type: 'schedule-render',
312
- warning: null,
313
- },
314
- ],
315
- startTime: 1,
316
- suspenseEvents: [],
317
- });
376
+ "measures": Array [
377
+ Object {
378
+ "batchUID": 0,
379
+ "depth": 0,
380
+ "duration": 0.004999999999999999,
381
+ "lanes": Array [
382
+ 9,
383
+ ],
384
+ "timestamp": 0.006,
385
+ "type": "render-idle",
386
+ },
387
+ Object {
388
+ "batchUID": 0,
389
+ "depth": 0,
390
+ "duration": 0.001,
391
+ "lanes": Array [
392
+ 9,
393
+ ],
394
+ "timestamp": 0.006,
395
+ "type": "render",
396
+ },
397
+ Object {
398
+ "batchUID": 0,
399
+ "depth": 0,
400
+ "duration": 0.002999999999999999,
401
+ "lanes": Array [
402
+ 9,
403
+ ],
404
+ "timestamp": 0.008,
405
+ "type": "commit",
406
+ },
407
+ Object {
408
+ "batchUID": 0,
409
+ "depth": 1,
410
+ "duration": 0.0010000000000000009,
411
+ "lanes": Array [
412
+ 9,
413
+ ],
414
+ "timestamp": 0.009,
415
+ "type": "layout-effects",
416
+ },
417
+ ],
418
+ "nativeEvents": Array [],
419
+ "otherUserTimingMarks": Array [],
420
+ "reactVersion": "17.0.3",
421
+ "schedulingEvents": Array [
422
+ Object {
423
+ "lanes": Array [
424
+ 9,
425
+ ],
426
+ "timestamp": 0.005,
427
+ "type": "schedule-render",
428
+ "warning": null,
429
+ },
430
+ ],
431
+ "startTime": 1,
432
+ "suspenseEvents": Array [],
433
+ }
434
+ `);
435
+ }
436
});
437
320
- // @gate enableSchedulingProfiler
438
it('should process a sample legacy render sequence', () => {
439
ReactDOM.render(<div />, document.createElement('div'));
440
324
- const reactVersion = require('shared/ReactVersion').default;
325
-
326
- const userTimingData = createUserTimingData(clearedMarks);
327
- expect(preprocessData(userTimingData)).toStrictEqual({
328
- componentMeasures: [],
329
- duration: 0.011,
330
- flamechart: [],
331
- measures: [
332
- {
333
- batchUID: 0,
334
- depth: 0,
335
- duration: 0.004999999999999999,
336
- laneLabels: ['Sync'],
337
- lanes: [0],
338
- timestamp: 0.006,
339
- type: 'render-idle',
340
- },
341
- {
342
- batchUID: 0,
343
- depth: 0,
344
- duration: 0.001,
345
- laneLabels: ['Sync'],
346
- lanes: [0],
347
- timestamp: 0.006,
348
- type: 'render',
349
- },
350
- {
351
- batchUID: 0,
352
- depth: 0,
353
- duration: 0.002999999999999999,
354
- laneLabels: ['Sync'],
355
- lanes: [0],
356
- timestamp: 0.008,
357
- type: 'commit',
358
- },
359
- {
360
- batchUID: 0,
361
- depth: 1,
362
- duration: 0.0010000000000000009,
363
- laneLabels: ['Sync'],
364
- lanes: [0],
365
- timestamp: 0.009,
366
- type: 'layout-effects',
367
- },
368
- ],
369
- nativeEvents: [],
370
- otherUserTimingMarks: [
371
- {
372
- name: '__v3',
373
- timestamp: 0.003,
374
- },
375
- {
376
- name: `--react-init-${reactVersion}`,
377
- timestamp: 0.004,
441
+ if (gate(flags => flags.enableSchedulingProfiler)) {
442
+ const data = preprocessData([
443
+ ...createBoilerplateEntries(),
444
+ ...createUserTimingData(clearedMarks),
445
+ ]);
446
+ expect(data).toMatchInlineSnapshot(`
447
+ Object {
448
+ "componentMeasures": Array [],
449
+ "duration": 0.013,
450
+ "flamechart": Array [],
451
+ "laneToLabelMap": Map {
452
+ 0 => "Sync",
453
+ 1 => "InputContinuousHydration",
454
+ 2 => "InputContinuous",
455
+ 3 => "DefaultHydration",
456
+ 4 => "Default",
457
+ 5 => "TransitionHydration",
458
+ 6 => "Transition",
459
+ 7 => "Transition",
460
+ 8 => "Transition",
461
+ 9 => "Transition",
462
+ 10 => "Transition",
463
+ 11 => "Transition",
464
+ 12 => "Transition",
465
+ 13 => "Transition",
466
+ 14 => "Transition",
467
+ 15 => "Transition",
468
+ 16 => "Transition",
469
+ 17 => "Transition",
470
+ 18 => "Transition",
471
+ 19 => "Transition",
472
+ 20 => "Transition",
473
+ 21 => "Transition",
474
+ 22 => "Retry",
475
+ 23 => "Retry",
476
+ 24 => "Retry",
477
+ 25 => "Retry",
478
+ 26 => "Retry",
479
+ 27 => "SelectiveHydration",
480
+ 28 => "IdleHydration",
481
+ 29 => "Idle",
482
+ 30 => "Offscreen",
483
},
379
- ],
380
- schedulingEvents: [
381
- {
382
- laneLabels: ['Sync'],
383
- lanes: [0],
384
- timestamp: 0.005,
385
- type: 'schedule-render',
386
- warning: null,
387
- },
388
- ],
389
- startTime: 1,
390
- suspenseEvents: [],
391
- });
484
+ "measures": Array [
485
+ Object {
486
+ "batchUID": 0,
487
+ "depth": 0,
488
+ "duration": 0.008,
489
+ "lanes": Array [
490
+ 0,
491
+ ],
492
+ "timestamp": 0.005,
493
+ "type": "render-idle",
494
+ },
495
+ Object {
496
+ "batchUID": 0,
497
+ "depth": 0,
498
+ "duration": 0.001,
499
+ "lanes": Array [
500
+ 0,
501
+ ],
502
+ "timestamp": 0.005,
503
+ "type": "render",
504
+ },
505
+ Object {
506
+ "batchUID": 0,
507
+ "depth": 0,
508
+ "duration": 0.005999999999999999,
509
+ "lanes": Array [
510
+ 0,
511
+ ],
512
+ "timestamp": 0.007,
513
+ "type": "commit",
514
+ },
515
+ Object {
516
+ "batchUID": 0,
517
+ "depth": 1,
518
+ "duration": 0.0010000000000000009,
519
+ "lanes": Array [
520
+ 0,
521
+ ],
522
+ "timestamp": 0.011,
523
+ "type": "layout-effects",
524
+ },
525
+ ],
526
+ "nativeEvents": Array [],
527
+ "otherUserTimingMarks": Array [
528
+ Object {
529
+ "name": "__v3",
530
+ "timestamp": 0.003,
531
+ },
532
+ ],
533
+ "reactVersion": "17.0.3",
534
+ "schedulingEvents": Array [
535
+ Object {
536
+ "lanes": Array [
537
+ 0,
538
+ ],
539
+ "timestamp": 0.004,
540
+ "type": "schedule-render",
541
+ "warning": null,
542
+ },
543
+ ],
544
+ "startTime": 4,
545
+ "suspenseEvents": Array [],
546
+ }
547
+ `);
548
+ }
549
});
550
394
- // @gate enableSchedulingProfiler
551
it('should process a sample createRoot render sequence', () => {
552
function App() {
553
const [didMount, setDidMount] = React.useState(false);
@@ -403,150 +559,199 @@ describe(preprocessData, () => {
559
return true;
560
}
561
406
- const root = ReactDOM.createRoot(document.createElement('div'));
407
- act(() => root.render(<App />));
408
-
409
- const userTimingData = createUserTimingData(clearedMarks);
410
- expect(preprocessData(userTimingData)).toStrictEqual({
411
- componentMeasures: [
412
- {
413
- componentName: 'App',
414
- duration: 0.001,
415
- timestamp: 0.007,
416
- warning: null,
417
- },
418
- {
419
- componentName: 'App',
420
- duration: 0.0010000000000000009,
421
- timestamp: 0.018,
422
- warning: null,
423
- },
424
- ],
425
- duration: 0.026,
426
- flamechart: [],
427
- measures: [
428
- {
429
- batchUID: 0,
430
- depth: 0,
431
- duration: 0.006999999999999999,
432
- laneLabels: ['Default'],
433
- lanes: [4],
434
- timestamp: 0.006,
435
- type: 'render-idle',
436
- },
437
- {
438
- batchUID: 0,
439
- depth: 0,
440
- duration: 0.002999999999999999,
441
- laneLabels: ['Default'],
442
- lanes: [4],
443
- timestamp: 0.006,
444
- type: 'render',
445
- },
446
- {
447
- batchUID: 0,
448
- depth: 0,
449
- duration: 0.002999999999999999,
450
- laneLabels: ['Default'],
451
- lanes: [4],
452
- timestamp: 0.01,
453
- type: 'commit',
454
- },
455
- {
456
- batchUID: 0,
457
- depth: 1,
458
- duration: 0.0010000000000000009,
459
- laneLabels: ['Default'],
460
- lanes: [4],
461
- timestamp: 0.011,
462
- type: 'layout-effects',
463
- },
464
- {
465
- batchUID: 0,
466
- depth: 0,
467
- duration: 0.002,
468
- laneLabels: ['Default'],
469
- lanes: [4],
470
- timestamp: 0.014,
471
- type: 'passive-effects',
472
- },
473
- {
474
- batchUID: 1,
475
- depth: 0,
476
- duration: 0.006999999999999999,
477
- laneLabels: ['Default'],
478
- lanes: [4],
479
- timestamp: 0.017,
480
- type: 'render-idle',
481
- },
482
- {
483
- batchUID: 1,
484
- depth: 0,
485
- duration: 0.002999999999999999,
486
- laneLabels: ['Default'],
487
- lanes: [4],
488
- timestamp: 0.017,
489
- type: 'render',
490
- },
491
- {
492
- batchUID: 1,
493
- depth: 0,
494
- duration: 0.002999999999999999,
495
- laneLabels: ['Default'],
496
- lanes: [4],
497
- timestamp: 0.021,
498
- type: 'commit',
499
- },
500
- {
501
- batchUID: 1,
502
- depth: 1,
503
- duration: 0.0010000000000000009,
504
- laneLabels: ['Default'],
505
- lanes: [4],
506
- timestamp: 0.022,
507
- type: 'layout-effects',
562
+ if (gate(flags => flags.enableSchedulingProfiler)) {
563
+ const root = ReactDOM.createRoot(document.createElement('div'));
564
+ act(() => root.render(<App />));
565
+
566
+ const data = preprocessData([
567
+ ...createBoilerplateEntries(),
568
+ ...createUserTimingData(clearedMarks),
569
+ ]);
570
+ expect(data).toMatchInlineSnapshot(`
571
+ Object {
572
+ "componentMeasures": Array [
573
+ Object {
574
+ "componentName": "App",
575
+ "duration": 0.001,
576
+ "timestamp": 0.006,
577
+ "warning": null,
578
+ },
579
+ Object {
580
+ "componentName": "App",
581
+ "duration": 0.0010000000000000009,
582
+ "timestamp": 0.02,
583
+ "warning": null,
584
+ },
585
+ ],
586
+ "duration": 0.031,
587
+ "flamechart": Array [],
588
+ "laneToLabelMap": Map {
589
+ 0 => "Sync",
590
+ 1 => "InputContinuousHydration",
591
+ 2 => "InputContinuous",
592
+ 3 => "DefaultHydration",
593
+ 4 => "Default",
594
+ 5 => "TransitionHydration",
595
+ 6 => "Transition",
596
+ 7 => "Transition",
597
+ 8 => "Transition",
598
+ 9 => "Transition",
599
+ 10 => "Transition",
600
+ 11 => "Transition",
601
+ 12 => "Transition",
602
+ 13 => "Transition",
603
+ 14 => "Transition",
604
+ 15 => "Transition",
605
+ 16 => "Transition",
606
+ 17 => "Transition",
607
+ 18 => "Transition",
608
+ 19 => "Transition",
609
+ 20 => "Transition",
610
+ 21 => "Transition",
611
+ 22 => "Retry",
612
+ 23 => "Retry",
613
+ 24 => "Retry",
614
+ 25 => "Retry",
615
+ 26 => "Retry",
616
+ 27 => "SelectiveHydration",
617
+ 28 => "IdleHydration",
618
+ 29 => "Idle",
619
+ 30 => "Offscreen",
620
},
509
- {
510
- batchUID: 1,
511
- depth: 0,
512
- duration: 0.0009999999999999974,
513
- laneLabels: ['Default'],
514
- lanes: [4],
515
- timestamp: 0.025,
516
- type: 'passive-effects',
517
- },
518
- ],
519
- nativeEvents: [],
520
- otherUserTimingMarks: [
521
- {
522
- name: '__v3',
523
- timestamp: 0.003,
524
- },
525
- {
526
- name: '--react-init-17.0.3',
527
- timestamp: 0.004,
528
- },
529
- ],
530
- schedulingEvents: [
531
- {
532
- laneLabels: ['Default'],
533
- lanes: [4],
534
- timestamp: 0.005,
535
- type: 'schedule-render',
536
- warning: null,
537
- },
538
- {
539
- componentName: 'App',
540
- laneLabels: ['Default'],
541
- lanes: [4],
542
- timestamp: 0.015,
543
- type: 'schedule-state-update',
544
- warning: null,
545
- },
546
- ],
547
- startTime: 1,
548
- suspenseEvents: [],
549
- });
621
+ "measures": Array [
622
+ Object {
623
+ "batchUID": 0,
624
+ "depth": 0,
625
+ "duration": 0.009999999999999998,
626
+ "lanes": Array [
627
+ 4,
628
+ ],
629
+ "timestamp": 0.005,
630
+ "type": "render-idle",
631
+ },
632
+ Object {
633
+ "batchUID": 0,
634
+ "depth": 0,
635
+ "duration": 0.003,
636
+ "lanes": Array [
637
+ 4,
638
+ ],
639
+ "timestamp": 0.005,
640
+ "type": "render",
641
+ },
642
+ Object {
643
+ "batchUID": 0,
644
+ "depth": 0,
645
+ "duration": 0.006,
646
+ "lanes": Array [
647
+ 4,
648
+ ],
649
+ "timestamp": 0.009,
650
+ "type": "commit",
651
+ },
652
+ Object {
653
+ "batchUID": 0,
654
+ "depth": 1,
655
+ "duration": 0.0010000000000000009,
656
+ "lanes": Array [
657
+ 4,
658
+ ],
659
+ "timestamp": 0.013,
660
+ "type": "layout-effects",
661
+ },
662
+ Object {
663
+ "batchUID": 0,
664
+ "depth": 0,
665
+ "duration": 0.0019999999999999983,
666
+ "lanes": Array [
667
+ 4,
668
+ ],
669
+ "timestamp": 0.016,
670
+ "type": "passive-effects",
671
+ },
672
+ Object {
673
+ "batchUID": 1,
674
+ "depth": 0,
675
+ "duration": 0.010000000000000002,
676
+ "lanes": Array [
677
+ 4,
678
+ ],
679
+ "timestamp": 0.019,
680
+ "type": "render-idle",
681
+ },
682
+ Object {
683
+ "batchUID": 1,
684
+ "depth": 0,
685
+ "duration": 0.002999999999999999,
686
+ "lanes": Array [
687
+ 4,
688
+ ],
689
+ "timestamp": 0.019,
690
+ "type": "render",
691
+ },
692
+ Object {
693
+ "batchUID": 1,
694
+ "depth": 0,
695
+ "duration": 0.006000000000000002,
696
+ "lanes": Array [
697
+ 4,
698
+ ],
699
+ "timestamp": 0.023,
700
+ "type": "commit",
701
+ },
702
+ Object {
703
+ "batchUID": 1,
704
+ "depth": 1,
705
+ "duration": 0.0010000000000000009,
706
+ "lanes": Array [
707
+ 4,
708
+ ],
709
+ "timestamp": 0.027,
710
+ "type": "layout-effects",
711
+ },
712
+ Object {
713
+ "batchUID": 1,
714
+ "depth": 0,
715
+ "duration": 0.0010000000000000009,
716
+ "lanes": Array [
717
+ 4,
718
+ ],
719
+ "timestamp": 0.03,
720
+ "type": "passive-effects",
721
+ },
722
+ ],
723
+ "nativeEvents": Array [],
724
+ "otherUserTimingMarks": Array [
725
+ Object {
726
+ "name": "__v3",
727
+ "timestamp": 0.003,
728
+ },
729
+ ],
730
+ "reactVersion": "17.0.3",
731
+ "schedulingEvents": Array [
732
+ Object {
733
+ "lanes": Array [
734
+ 4,
735
+ ],
736
+ "timestamp": 0.004,
737
+ "type": "schedule-render",
738
+ "warning": null,
739
+ },
740
+ Object {
741
+ "componentName": "App",
742
+ "lanes": Array [
743
+ 4,
744
+ ],
745
+ "timestamp": 0.017,
746
+ "type": "schedule-state-update",
747
+ "warning": null,
748
+ },
749
+ ],
750
+ "startTime": 4,
751
+ "suspenseEvents": Array [],
752
+ }
753
+ `);
754
+ }
755
});
756
757
// @gate enableSchedulingProfiler
@@ -560,7 +765,7 @@ describe(preprocessData, () => {
765
const invalidUserTimingData = createUserTimingData(invalidMarks);
766
767
const error = spyOnDevAndProd(console, 'error');
563
- preprocessData(invalidUserTimingData);
768
+ preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
769
expect(error).toHaveBeenCalled();
770
});
771
@@ -575,7 +780,7 @@ describe(preprocessData, () => {
780
const invalidUserTimingData = createUserTimingData(invalidMarks);
781
782
const error = spyOnDevAndProd(console, 'error');
578
- preprocessData(invalidUserTimingData);
783
+ preprocessData([...createBoilerplateEntries(), ...invalidUserTimingData]);
784
expect(error).toHaveBeenCalled();
785
});
786
@@ -606,20 +811,26 @@ describe(preprocessData, () => {
811
}),
812
);
813
609
- expect(preprocessData(userTimingData).otherUserTimingMarks).toStrictEqual([
610
- {
611
- name: 'VCWithoutImage: root',
612
- timestamp: 0.003,
613
- },
614
- {
615
- name: '--a-mark-that-looks-like-one-of-ours',
616
- timestamp: 0.004,
617
- },
618
- {
619
- name: 'Some other mark',
620
- timestamp: 0.005,
621
- },
814
+ const data = preprocessData([
815
+ ...createBoilerplateEntries(),
816
+ ...userTimingData,
817
]);
818
+ expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
819
+ Array [
820
+ Object {
821
+ "name": "VCWithoutImage: root",
822
+ "timestamp": 0.003,
823
+ },
824
+ Object {
825
+ "name": "--a-mark-that-looks-like-one-of-ours",
826
+ "timestamp": 0.004,
827
+ },
828
+ Object {
829
+ "name": "Some other mark",
830
+ "timestamp": 0.005,
831
+ },
832
+ ]
833
+ `);
834
});
835
836
// TODO: Add test for flamechart parsing
packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js
+73
-51
@@ -17,6 +17,7 @@ import type {
17
BatchUID,
18
Flamechart,
19
NativeEvent,
20
+ Phase,
21
ReactLane,
22
ReactComponentMeasure,
23
ReactMeasureType,
@@ -24,7 +25,7 @@ import type {
25
SuspenseEvent,
26
} from '../types';
27
27
-import {REACT_TOTAL_NUM_LANES} from '../constants';
28
+import {REACT_TOTAL_NUM_LANES, SCHEDULING_PROFILER_VERSION} from '../constants';
29
import InvalidProfileError from './InvalidProfileError';
30
31
type MeasureStackElement = {|
@@ -52,7 +53,7 @@ const WARNING_STRINGS = {
53
'An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work.',
54
NESTED_UPDATE:
55
'A nested update was scheduled during layout. These updates require React to re-render synchronously before the browser can paint.',
55
- SUSPENDD_DURING_UPATE:
56
+ SUSPEND_DURING_UPATE:
57
'A component suspended during an update which caused a fallback to be shown. ' +
58
"Consider using the Transition API to avoid hiding components after they've been mounted.",
59
};
@@ -80,6 +81,23 @@ export function getLanesFromTransportDecimalBitmask(
81
return lanes;
82
}
83
84
+const laneToLabelMap: Map<number, string> = new Map();
85
+function updateLaneToLabelMap(laneLabelTuplesString: string): void {
86
+ // These marks appear multiple times in the data;
87
+ // We only need to extact them once.
88
+ if (laneToLabelMap.size === 0) {
89
+ const laneLabelTuples = laneLabelTuplesString.split(',');
90
+ for (let laneIndex = 0; laneIndex < laneLabelTuples.length; laneIndex++) {
91
+ // The numeric lane value (e.g. 64) isn't important.
92
+ // The profiler parses and stores the lane's position within the bitmap,
93
+ // (e.g. lane 1 is index 0, lane 16 is index 4).
94
+ laneToLabelMap.set(laneIndex, laneLabelTuples[laneIndex]);
95
+ }
96
+ }
97
+}
98
+
99
+let profilerVersion = null;
100
+
101
function getLastType(stack: $PropertyType<ProcessorState, 'measureStack'>) {
102
if (stack.length > 0) {
103
const {type} = stack[stack.length - 1];
@@ -100,7 +118,6 @@ function markWorkStarted(
118
type: ReactMeasureType,
119
startTime: Milliseconds,
120
lanes: ReactLane[],
103
- laneLabels: Array<string>,
121
currentProfilerData: ReactProfilerData,
122
state: ProcessorState,
123
) {
@@ -115,7 +132,6 @@ function markWorkStarted(
132
batchUID,
133
depth,
134
lanes,
118
- laneLabels,
135
timestamp: startTime,
136
duration: 0,
137
});
@@ -242,7 +258,21 @@ function processTimelineEvent(
258
case 'blink.user_timing':
259
const startTime = (ts - currentProfilerData.startTime) / 1000;
260
245
- if (name.startsWith('--component-render-start-')) {
261
+ if (name.startsWith('--react-version-')) {
262
+ const [reactVersion] = name.substr(16).split('-');
263
+ currentProfilerData.reactVersion = reactVersion;
264
+ } else if (name.startsWith('--profiler-version-')) {
265
+ const [versionString] = name.substr(19).split('-');
266
+ profilerVersion = parseInt(versionString, 10);
267
+ if (profilerVersion !== SCHEDULING_PROFILER_VERSION) {
268
+ throw new InvalidProfileError(
269
+ `This version of profiling data (${versionString}) is not supported by the current profiler.`,
270
+ );
271
+ }
272
+ } else if (name.startsWith('--react-lane-labels-')) {
273
+ const [laneLabelTuplesString] = name.substr(20).split('-');
274
+ updateLaneToLabelMap(laneLabelTuplesString);
275
+ } else if (name.startsWith('--component-render-start-')) {
276
const [componentName] = name.substr(25).split('-');
277
278
if (state.currentReactComponentMeasure !== null) {
@@ -268,48 +298,44 @@ function processTimelineEvent(
298
currentProfilerData.componentMeasures.push(componentMeasure);
299
}
300
} else if (name.startsWith('--schedule-render-')) {
271
- const [laneBitmaskString, laneLabels] = name.substr(18).split('-');
301
+ const [laneBitmaskString] = name.substr(18).split('-');
302
+
303
currentProfilerData.schedulingEvents.push({
304
type: 'schedule-render',
305
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
275
- laneLabels: laneLabels ? laneLabels.split(',') : [],
306
timestamp: startTime,
307
warning: null,
308
});
309
} else if (name.startsWith('--schedule-forced-update-')) {
280
- const [laneBitmaskString, laneLabels, componentName] = name
281
- .substr(25)
282
- .split('-');
310
+ const [laneBitmaskString, componentName] = name.substr(25).split('-');
311
312
let warning = null;
313
if (state.measureStack.find(({type}) => type === 'commit')) {
314
// TODO (scheduling profiler) Only warn if the subsequent update is longer than some threshold.
315
+ // This might be easier to do if we separated warnings into a second pass.
316
warning = WARNING_STRINGS.NESTED_UPDATE;
317
}
318
319
currentProfilerData.schedulingEvents.push({
320
type: 'schedule-force-update',
321
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
293
- laneLabels: laneLabels ? laneLabels.split(',') : [],
322
componentName,
323
timestamp: startTime,
324
warning,
325
});
326
} else if (name.startsWith('--schedule-state-update-')) {
299
- const [laneBitmaskString, laneLabels, componentName] = name
300
- .substr(24)
301
- .split('-');
327
+ const [laneBitmaskString, componentName] = name.substr(24).split('-');
328
329
let warning = null;
330
if (state.measureStack.find(({type}) => type === 'commit')) {
331
// TODO (scheduling profiler) Only warn if the subsequent update is longer than some threshold.
332
+ // This might be easier to do if we separated warnings into a second pass.
333
warning = WARNING_STRINGS.NESTED_UPDATE;
334
}
335
336
currentProfilerData.schedulingEvents.push({
337
type: 'schedule-state-update',
338
lanes: getLanesFromTransportDecimalBitmask(laneBitmaskString),
312
- laneLabels: laneLabels ? laneLabels.split(',') : [],
339
componentName,
340
timestamp: startTime,
341
warning,
@@ -318,25 +344,18 @@ function processTimelineEvent(
344
345
// React Events - suspense
346
else if (name.startsWith('--suspense-suspend-')) {
321
- const [id, componentName, ...rest] = name.substr(19).split('-');
347
+ const [id, componentName, phase, laneBitmaskString] = name
348
+ .substr(19)
349
+ .split('-');
350
+ const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
351
323
- // Older versions of the scheduling profiler data didn't contain phase or lane values.
324
- let phase = null;
352
+ // TODO It's possible we don't have lane-to-label mapping yet (since it's logged during commit phase)
353
+ // We may need to do this sort of error checking in a separate pass.
354
let warning = null;
326
- if (rest.length === 3) {
327
- switch (rest[0]) {
328
- case 'mount':
329
- case 'update':
330
- phase = rest[0];
331
- break;
332
- }
333
-
334
- if (phase === 'update') {
335
- const laneLabels = rest[2];
336
- // HACK This is a bit gross but the numeric lane value might change between render versions.
337
- if (!laneLabels.includes('Transition')) {
338
- warning = WARNING_STRINGS.SUSPENDD_DURING_UPATE;
339
- }
355
+ if (phase === 'update') {
356
+ // HACK This is a bit gross but the numeric lane value might change between render versions.
357
+ if (lanes.some(lane => laneToLabelMap.get(lane) === 'Transition')) {
358
+ warning = WARNING_STRINGS.SUSPEND_DURING_UPATE;
359
}
360
}
361
@@ -365,7 +384,7 @@ function processTimelineEvent(
384
depth,
385
duration: null,
386
id,
368
- phase,
387
+ phase: ((phase: any): Phase),
388
resolution: 'unresolved',
389
resuspendTimestamps: null,
390
timestamp: startTime,
@@ -411,15 +430,14 @@ function processTimelineEvent(
430
state.nextRenderShouldGenerateNewBatchID = false;
431
state.batchUID = ((state.uidCounter++: any): BatchUID);
432
}
414
- const [laneBitmaskString, laneLabels] = name.substr(15).split('-');
415
- const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
433
+ const [laneBitmaskString] = name.substr(15).split('-');
434
+
435
throwIfIncomplete('render', state.measureStack);
436
if (getLastType(state.measureStack) !== 'render-idle') {
437
markWorkStarted(
438
'render-idle',
439
startTime,
421
- lanes,
422
- laneLabels ? laneLabels.split(',') : [],
440
+ getLanesFromTransportDecimalBitmask(laneBitmaskString),
441
currentProfilerData,
442
state,
443
);
@@ -427,8 +445,7 @@ function processTimelineEvent(
445
markWorkStarted(
446
'render',
447
startTime,
430
- lanes,
431
- laneLabels ? laneLabels.split(',') : [],
448
+ getLanesFromTransportDecimalBitmask(laneBitmaskString),
449
currentProfilerData,
450
state,
451
);
@@ -472,13 +489,12 @@ function processTimelineEvent(
489
// React Measures - commits
490
else if (name.startsWith('--commit-start-')) {
491
state.nextRenderShouldGenerateNewBatchID = true;
475
- const [laneBitmaskString, laneLabels] = name.substr(15).split('-');
476
- const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
492
+ const [laneBitmaskString] = name.substr(15).split('-');
493
+
494
markWorkStarted(
495
'commit',
496
startTime,
480
- lanes,
481
- laneLabels ? laneLabels.split(',') : [],
497
+ getLanesFromTransportDecimalBitmask(laneBitmaskString),
498
currentProfilerData,
499
state,
500
);
@@ -499,13 +515,12 @@ function processTimelineEvent(
515
516
// React Measures - layout effects
517
else if (name.startsWith('--layout-effects-start-')) {
502
- const [laneBitmaskString, laneLabels] = name.substr(23).split('-');
503
- const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
518
+ const [laneBitmaskString] = name.substr(23).split('-');
519
+
520
markWorkStarted(
521
'layout-effects',
522
startTime,
507
- lanes,
508
- laneLabels ? laneLabels.split(',') : [],
523
+ getLanesFromTransportDecimalBitmask(laneBitmaskString),
524
currentProfilerData,
525
state,
526
);
@@ -520,13 +535,12 @@ function processTimelineEvent(
535
536
// React Measures - passive effects
537
else if (name.startsWith('--passive-effects-start-')) {
523
- const [laneBitmaskString, laneLabels] = name.substr(24).split('-');
524
- const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
538
+ const [laneBitmaskString] = name.substr(24).split('-');
539
+
540
markWorkStarted(
541
'passive-effects',
542
startTime,
528
- lanes,
529
- laneLabels ? laneLabels.split(',') : [],
543
+ getLanesFromTransportDecimalBitmask(laneBitmaskString),
544
currentProfilerData,
545
state,
546
);
@@ -610,9 +624,11 @@ export default function preprocessData(
624
componentMeasures: [],
625
duration: 0,
626
flamechart,
627
+ laneToLabelMap,
628
measures: [],
629
nativeEvents: [],
630
otherUserTimingMarks: [],
631
+ reactVersion: null,
632
schedulingEvents: [],
633
startTime: 0,
634
suspenseEvents: [],
@@ -656,6 +672,12 @@ export default function preprocessData(
672
673
timeline.forEach(event => processTimelineEvent(event, profilerData, state));
674
675
+ if (profilerVersion === null) {
676
+ throw new InvalidProfileError(
677
+ `This version of profiling data is not supported by the current profiler.`,
678
+ );
679
+ }
680
+
681
// Validate that all events and measures are complete
682
const {measureStack} = state;
683
if (measureStack.length > 0) {
packages/react-devtools-scheduling-profiler/src/types.js
+5
-3
@@ -38,7 +38,6 @@ type BaseReactEvent = {|
38
type BaseReactScheduleEvent = {|
39
...BaseReactEvent,
40
+lanes: ReactLane[],
41
- +laneLabels: string[],
41
|};
42
export type ReactScheduleRenderEvent = {|
43
...BaseReactScheduleEvent,
@@ -53,12 +52,14 @@ export type ReactScheduleForceUpdateEvent = {|
52
+type: 'schedule-force-update',
53
|};
54
55
+export type Phase = 'mount' | 'update';
56
+
57
export type SuspenseEvent = {|
58
...BaseReactEvent,
59
depth: number,
60
duration: number | null,
61
+id: string,
61
- +phase: 'mount' | 'update' | null,
62
+ +phase: Phase | null,
63
resolution: 'rejected' | 'resolved' | 'unresolved',
64
resuspendTimestamps: Array<number> | null,
65
+type: 'suspense',
@@ -84,7 +85,6 @@ export type BatchUID = number;
85
export type ReactMeasure = {|
86
+type: ReactMeasureType,
87
+lanes: ReactLane[],
87
- +laneLabels: string[],
88
+timestamp: Milliseconds,
89
+duration: Milliseconds,
90
+batchUID: BatchUID,
@@ -127,9 +127,11 @@ export type ReactProfilerData = {|
127
componentMeasures: ReactComponentMeasure[],
128
duration: number,
129
flamechart: Flamechart,
130
+ laneToLabelMap: Map<ReactLane, string>,
131
measures: ReactMeasure[],
132
nativeEvents: NativeEvent[],
133
otherUserTimingMarks: UserTimingMark[],
134
+ reactVersion: string | null,
135
schedulingEvents: SchedulingEvent[],
136
startTime: number,
137
suspenseEvents: SuspenseEvent[],
packages/react-devtools-scheduling-profiler/src/view-base/ResizableView.js
+4
-8
@@ -105,14 +105,10 @@ class ResizeBar extends View {
105
106
const drawableRect = intersectionOfRects(labelRect, this.visibleArea);
107
108
- drawText(
109
- this._label,
110
- context,
111
- labelRect,
112
- drawableRect,
113
- 'center',
114
- COLORS.REACT_RESIZE_BAR_DOT,
115
- );
108
+ drawText(this._label, context, labelRect, drawableRect, {
109
+ fillStyle: COLORS.REACT_RESIZE_BAR_DOT,
110
+ textAlign: 'center',
111
+ });
112
} else {
113
// Otherwise draw horizontally centered resize bar dots
114
context.beginPath();
packages/react-devtools-shared/src/constants.js
+8
@@ -149,12 +149,16 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
149
'--color-scheduling-profiler-react-idle-hover': '#c3d9ef',
150
'--color-scheduling-profiler-react-render': '#9fc3f3',
151
'--color-scheduling-profiler-react-render-hover': '#83afe9',
152
+ '--color-scheduling-profiler-react-render-text': '#11365e',
153
'--color-scheduling-profiler-react-commit': '#c88ff0',
154
'--color-scheduling-profiler-react-commit-hover': '#b281d6',
155
+ '--color-scheduling-profiler-react-commit-text': '#3e2c4a',
156
'--color-scheduling-profiler-react-layout-effects': '#b281d6',
157
'--color-scheduling-profiler-react-layout-effects-hover': '#9d71bd',
158
+ '--color-scheduling-profiler-react-layout-effects-text': '#3e2c4a',
159
'--color-scheduling-profiler-react-passive-effects': '#b281d6',
160
'--color-scheduling-profiler-react-passive-effects-hover': '#9d71bd',
161
+ '--color-scheduling-profiler-react-passive-effects-text': '#3e2c4a',
162
'--color-scheduling-profiler-react-schedule': '#9fc3f3',
163
'--color-scheduling-profiler-react-schedule-hover': '#2683E2',
164
'--color-scheduling-profiler-react-suspense-rejected': '#f1cc14',
@@ -280,12 +284,16 @@ export const THEME_STYLES: {[style: Theme | DisplayDensity]: any} = {
284
'--color-scheduling-profiler-react-idle-hover': '#465269',
285
'--color-scheduling-profiler-react-render': '#2683E2',
286
'--color-scheduling-profiler-react-render-hover': '#1a76d4',
287
+ '--color-scheduling-profiler-react-render-text': '#11365e',
288
'--color-scheduling-profiler-react-commit': '#731fad',
289
'--color-scheduling-profiler-react-commit-hover': '#611b94',
290
+ '--color-scheduling-profiler-react-commit-text': '#e5c1ff',
291
'--color-scheduling-profiler-react-layout-effects': '#611b94',
292
'--color-scheduling-profiler-react-layout-effects-hover': '#51167a',
293
+ '--color-scheduling-profiler-react-layout-effects-text': '#e5c1ff',
294
'--color-scheduling-profiler-react-passive-effects': '#611b94',
295
'--color-scheduling-profiler-react-passive-effects-hover': '#51167a',
296
+ '--color-scheduling-profiler-react-passive-effects-text': '#e5c1ff',
297
'--color-scheduling-profiler-react-schedule': '#2683E2',
298
'--color-scheduling-profiler-react-schedule-hover': '#1a76d4',
299
'--color-scheduling-profiler-react-suspense-rejected': '#f1cc14',
packages/react-reconciler/src/ReactFiberLane.new.js
+26
-28
@@ -25,7 +25,7 @@ import {
25
import {isDevToolsPresent} from './ReactFiberDevToolsHook.new';
26
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
27
28
-// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
28
+// Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-scheduling-profiler.
29
// If those values are changed that package should be rebuilt and redeployed.
30
31
export const TotalLanes = 31;
@@ -80,46 +80,44 @@ export const OffscreenLane: Lane = /* */ 0b1000000000000000000
80
81
// This function is used for the experimental scheduling profiler (react-devtools-scheduling-profiler)
82
// It should be kept in sync with the Lanes values above.
83
-export function getLabelsForLanes(lanes: Lanes): Array<string> | void {
83
+export function getLabelForLane(lane: Lane): string | void {
84
if (enableSchedulingProfiler) {
85
- const labels = [];
86
- if (lanes & SyncLane) {
87
- labels.push('Sync');
85
+ if (lane & SyncLane) {
86
+ return 'Sync';
87
}
89
- if (lanes & InputContinuousHydrationLane) {
90
- labels.push('InputContinuousHydration');
88
+ if (lane & InputContinuousHydrationLane) {
89
+ return 'InputContinuousHydration';
90
}
92
- if (lanes & InputContinuousLane) {
93
- labels.push('InputContinuous');
91
+ if (lane & InputContinuousLane) {
92
+ return 'InputContinuous';
93
}
95
- if (lanes & DefaultHydrationLane) {
96
- labels.push('DefaultHydration');
94
+ if (lane & DefaultHydrationLane) {
95
+ return 'DefaultHydration';
96
}
98
- if (lanes & DefaultLane) {
99
- labels.push('Default');
97
+ if (lane & DefaultLane) {
98
+ return 'Default';
99
}
101
- if (lanes & TransitionHydrationLane) {
102
- labels.push('TransitionHydration');
100
+ if (lane & TransitionHydrationLane) {
101
+ return 'TransitionHydration';
102
}
104
- if (lanes & TransitionLanes) {
105
- labels.push('Transition(s)');
103
+ if (lane & TransitionLanes) {
104
+ return 'Transition';
105
}
107
- if (lanes & RetryLanes) {
108
- labels.push('Retry(s)');
106
+ if (lane & RetryLanes) {
107
+ return 'Retry';
108
}
110
- if (lanes & SelectiveHydrationLane) {
111
- labels.push('SelectiveHydration');
109
+ if (lane & SelectiveHydrationLane) {
110
+ return 'SelectiveHydration';
111
}
113
- if (lanes & IdleHydrationLane) {
114
- labels.push('IdleHydration');
112
+ if (lane & IdleHydrationLane) {
113
+ return 'IdleHydration';
114
}
116
- if (lanes & IdleLane) {
117
- labels.push('Idle');
115
+ if (lane & IdleLane) {
116
+ return 'Idle';
117
}
119
- if (lanes & OffscreenLane) {
120
- labels.push('Offscreen');
118
+ if (lane & OffscreenLane) {
119
+ return 'Offscreen';
120
}
122
- return labels;
121
}
122
}
123
packages/react-reconciler/src/ReactFiberLane.old.js
+26
-28
@@ -25,7 +25,7 @@ import {
25
import {isDevToolsPresent} from './ReactFiberDevToolsHook.old';
26
import {ConcurrentUpdatesByDefaultMode, NoMode} from './ReactTypeOfMode';
27
28
-// Lane values below should be kept in sync with getLabelsForLanes(), used by react-devtools-scheduling-profiler.
28
+// Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-scheduling-profiler.
29
// If those values are changed that package should be rebuilt and redeployed.
30
31
export const TotalLanes = 31;
@@ -80,46 +80,44 @@ export const OffscreenLane: Lane = /* */ 0b1000000000000000000
80
81
// This function is used for the experimental scheduling profiler (react-devtools-scheduling-profiler)
82
// It should be kept in sync with the Lanes values above.
83
-export function getLabelsForLanes(lanes: Lanes): Array<string> | void {
83
+export function getLabelForLane(lane: Lane): string | void {
84
if (enableSchedulingProfiler) {
85
- const labels = [];
86
- if (lanes & SyncLane) {
87
- labels.push('Sync');
85
+ if (lane & SyncLane) {
86
+ return 'Sync';
87
}
89
- if (lanes & InputContinuousHydrationLane) {
90
- labels.push('InputContinuousHydration');
88
+ if (lane & InputContinuousHydrationLane) {
89
+ return 'InputContinuousHydration';
90
}
92
- if (lanes & InputContinuousLane) {
93
- labels.push('InputContinuous');
91
+ if (lane & InputContinuousLane) {
92
+ return 'InputContinuous';
93
}
95
- if (lanes & DefaultHydrationLane) {
96
- labels.push('DefaultHydration');
94
+ if (lane & DefaultHydrationLane) {
95
+ return 'DefaultHydration';
96
}
98
- if (lanes & DefaultLane) {
99
- labels.push('Default');
97
+ if (lane & DefaultLane) {
98
+ return 'Default';
99
}
101
- if (lanes & TransitionHydrationLane) {
102
- labels.push('TransitionHydration');
100
+ if (lane & TransitionHydrationLane) {
101
+ return 'TransitionHydration';
102
}
104
- if (lanes & TransitionLanes) {
105
- labels.push('Transition(s)');
103
+ if (lane & TransitionLanes) {
104
+ return 'Transition';
105
}
107
- if (lanes & RetryLanes) {
108
- labels.push('Retry(s)');
106
+ if (lane & RetryLanes) {
107
+ return 'Retry';
108
}
110
- if (lanes & SelectiveHydrationLane) {
111
- labels.push('SelectiveHydration');
109
+ if (lane & SelectiveHydrationLane) {
110
+ return 'SelectiveHydration';
111
}
113
- if (lanes & IdleHydrationLane) {
114
- labels.push('IdleHydration');
112
+ if (lane & IdleHydrationLane) {
113
+ return 'IdleHydration';
114
}
116
- if (lanes & IdleLane) {
117
- labels.push('Idle');
115
+ if (lane & IdleLane) {
116
+ return 'Idle';
117
}
119
- if (lanes & OffscreenLane) {
120
- labels.push('Offscreen');
118
+ if (lane & OffscreenLane) {
119
+ return 'Offscreen';
120
}
122
- return labels;
121
}
122
}
123
packages/react-reconciler/src/SchedulingProfiler.js
+58
-34
@@ -17,13 +17,22 @@ import {
17
} from 'shared/ReactFeatureFlags';
18
import ReactVersion from 'shared/ReactVersion';
19
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
20
+import {SCHEDULING_PROFILER_VERSION} from 'react-devtools-scheduling-profiler/src/constants';
21
21
-import {getLabelsForLanes as getLabelsForLanes_old} from 'react-reconciler/src/ReactFiberLane.old';
22
-import {getLabelsForLanes as getLabelsForLanes_new} from 'react-reconciler/src/ReactFiberLane.new';
22
+import {
23
+ getLabelForLane as getLabelForLane_old,
24
+ TotalLanes as TotalLanes_old,
25
+} from 'react-reconciler/src/ReactFiberLane.old';
26
+import {
27
+ getLabelForLane as getLabelForLane_new,
28
+ TotalLanes as TotalLanes_new,
29
+} from 'react-reconciler/src/ReactFiberLane.new';
30
24
-const getLabelsForLanes = enableNewReconciler
25
- ? getLabelsForLanes_new
26
- : getLabelsForLanes_old;
31
+const getLabelForLane = enableNewReconciler
32
+ ? getLabelForLane_new
33
+ : getLabelForLane_old;
34
+
35
+const TotalLanes = enableNewReconciler ? TotalLanes_new : TotalLanes_old;
36
37
/**
38
* If performance exists and supports the subset of the User Timing API that we
@@ -59,14 +68,24 @@ if (enableSchedulingProfiler) {
68
}
69
}
70
62
-export function formatLanes(laneOrLanes: Lane | Lanes): string {
63
- let labels = getLabelsForLanes(laneOrLanes);
64
- if (labels != null) {
65
- labels = labels.sort().join(',');
66
- } else {
67
- labels = '';
71
+const laneLabels: Array<string> = [];
72
+
73
+export function getLaneLabels(): Array<string> {
74
+ if (laneLabels.length === 0) {
75
+ let lane = 1;
76
+ for (let index = 0; index < TotalLanes; index++) {
77
+ laneLabels.push(((getLabelForLane(lane): any): string));
78
+
79
+ lane *= 2;
80
+ }
81
}
69
- return `${laneOrLanes}-${labels}`;
82
+ return laneLabels;
83
+}
84
+
85
+function markLaneToLabelMetadata() {
86
+ getLaneLabels();
87
+
88
+ markAndClear(`--react-lane-labels-${laneLabels.join(',')}`);
89
}
90
91
function markAndClear(name) {
@@ -74,17 +93,27 @@ function markAndClear(name) {
93
performance.clearMarks(name);
94
}
95
77
-// Create a mark on React initialization
78
-if (enableSchedulingProfiler) {
79
- if (supportsUserTimingV3) {
80
- markAndClear(`--react-init-${ReactVersion}`);
81
- }
96
+function markVersionMetadata() {
97
+ markAndClear(`--react-version-${ReactVersion}`);
98
+ markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
99
}
100
101
export function markCommitStarted(lanes: Lanes): void {
102
if (enableSchedulingProfiler) {
103
if (supportsUserTimingV3) {
87
- markAndClear(`--commit-start-${formatLanes(lanes)}`);
104
+ markAndClear(`--commit-start-${lanes}`);
105
+
106
+ // Certain types of metadata should be logged infrequently.
107
+ // Normally we would log this during module init,
108
+ // but there's no guarantee a user is profiling at that time.
109
+ // Commits happen infrequently (less than renders or state updates)
110
+ // so we log this extra information along with a commit.
111
+ // It will likely be logged more than once but that's okay.
112
+ //
113
+ // TODO Once DevTools supports starting/stopping the profiler,
114
+ // we can log this data only once (when started) and remove the per-commit logging.
115
+ markVersionMetadata();
116
+ markLaneToLabelMetadata();
117
}
118
}
119
}
@@ -101,6 +130,7 @@ export function markComponentRenderStarted(fiber: Fiber): void {
130
if (enableSchedulingProfiler) {
131
if (supportsUserTimingV3) {
132
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
133
+ // TODO (scheduling profiler) Add component stack id
134
markAndClear(`--component-render-start-${componentName}`);
135
}
136
}
@@ -137,11 +167,9 @@ export function markComponentSuspended(
167
const id = getWakeableID(wakeable);
168
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
169
const phase = fiber.alternate === null ? 'mount' : 'update';
140
- // TODO (scheduling profiler) Add component stack id if we re-add component stack info.
170
+ // TODO (scheduling profiler) Add component stack id
171
markAndClear(
142
- `--suspense-${eventType}-${id}-${componentName}-${phase}-${formatLanes(
143
- lanes,
144
- )}`,
172
+ `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}`,
173
);
174
wakeable.then(
175
() => markAndClear(`--suspense-resolved-${id}-${componentName}`),
@@ -154,7 +182,7 @@ export function markComponentSuspended(
182
export function markLayoutEffectsStarted(lanes: Lanes): void {
183
if (enableSchedulingProfiler) {
184
if (supportsUserTimingV3) {
157
- markAndClear(`--layout-effects-start-${formatLanes(lanes)}`);
185
+ markAndClear(`--layout-effects-start-${lanes}`);
186
}
187
}
188
}
@@ -170,7 +198,7 @@ export function markLayoutEffectsStopped(): void {
198
export function markPassiveEffectsStarted(lanes: Lanes): void {
199
if (enableSchedulingProfiler) {
200
if (supportsUserTimingV3) {
173
- markAndClear(`--passive-effects-start-${formatLanes(lanes)}`);
201
+ markAndClear(`--passive-effects-start-${lanes}`);
202
}
203
}
204
}
@@ -186,7 +214,7 @@ export function markPassiveEffectsStopped(): void {
214
export function markRenderStarted(lanes: Lanes): void {
215
if (enableSchedulingProfiler) {
216
if (supportsUserTimingV3) {
189
- markAndClear(`--render-start-${formatLanes(lanes)}`);
217
+ markAndClear(`--render-start-${lanes}`);
218
}
219
}
220
}
@@ -210,7 +238,7 @@ export function markRenderStopped(): void {
238
export function markRenderScheduled(lane: Lane): void {
239
if (enableSchedulingProfiler) {
240
if (supportsUserTimingV3) {
213
- markAndClear(`--schedule-render-${formatLanes(lane)}`);
241
+ markAndClear(`--schedule-render-${lane}`);
242
}
243
}
244
}
@@ -219,10 +247,8 @@ export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
247
if (enableSchedulingProfiler) {
248
if (supportsUserTimingV3) {
249
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
222
- // TODO Add component stack id
223
- markAndClear(
224
- `--schedule-forced-update-${formatLanes(lane)}-${componentName}`,
225
- );
250
+ // TODO (scheduling profiler) Add component stack id
251
+ markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
252
}
253
}
254
}
@@ -231,10 +257,8 @@ export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
257
if (enableSchedulingProfiler) {
258
if (supportsUserTimingV3) {
259
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
234
- // TODO Add component stack id
235
- markAndClear(
236
- `--schedule-state-update-${formatLanes(lane)}-${componentName}`,
237
- );
260
+ // TODO (scheduling profiler) Add component stack id
261
+ markAndClear(`--schedule-state-update-${lane}-${componentName}`);
262
}
263
}
264
}
packages/react-reconciler/src/__tests__/SchedulingProfiler-test.internal.js
+376
-252
@@ -10,17 +10,6 @@
10
11
'use strict';
12
13
-// This test is *.internal so that it can import this shared file.
14
-import ReactVersion from 'shared/ReactVersion';
15
-
16
-// Hard-coding because importing will not work with bundle tests and to
17
-// avoid leaking exports for lanes that are only imported in this test.
18
-const ReactFiberLane = {
19
- SyncLane: /* */ 0b0000000000000000000000000000001,
20
- DefaultLane: /* */ 0b0000000000000000000000000010000,
21
- TransitionLane1: /* */ 0b0000000000000000000000001000000,
22
-};
23
-
13
describe('SchedulingProfiler', () => {
14
let React;
15
let ReactTestRenderer;
@@ -30,7 +19,6 @@ describe('SchedulingProfiler', () => {
19
20
let clearedMarks;
21
let featureDetectionMarkName = null;
33
- let formatLanes;
22
let marks;
23
24
function createUserTimingPolyfill() {
@@ -63,16 +51,10 @@ describe('SchedulingProfiler', () => {
51
clearedMarks.splice(0);
52
}
53
66
- function expectMarksToContain(expectedMarks) {
67
- expect(clearedMarks).toContain(expectedMarks);
68
- }
69
-
70
- function expectMarksToEqual(expectedMarks) {
71
- expect(
72
- clearedMarks[0] === featureDetectionMarkName
73
- ? clearedMarks.slice(1)
74
- : clearedMarks,
75
- ).toEqual(expectedMarks);
54
+ function getMarks() {
55
+ return clearedMarks[0] === featureDetectionMarkName
56
+ ? clearedMarks.slice(1)
57
+ : clearedMarks;
58
}
59
60
beforeEach(() => {
@@ -88,9 +70,6 @@ describe('SchedulingProfiler', () => {
70
71
Scheduler = require('scheduler');
72
act = require('jest-react').act;
91
-
92
- const SchedulingProfiler = require('react-reconciler/src/SchedulingProfiler');
93
- formatLanes = SchedulingProfiler.formatLanes;
73
});
74
75
afterEach(() => {
@@ -103,54 +82,62 @@ describe('SchedulingProfiler', () => {
82
// @gate !enableSchedulingProfiler
83
it('should not mark if enableSchedulingProfiler is false', () => {
84
ReactTestRenderer.create(<div />);
106
- expectMarksToEqual([]);
85
+ expect(getMarks()).toEqual([]);
86
});
87
109
- // @gate enableSchedulingProfiler
110
- it('should log React version on initialization', () => {
111
- expectMarksToEqual([`--react-init-${ReactVersion}`]);
112
- });
113
-
114
- // @gate enableSchedulingProfiler
88
it('should mark sync render without suspends or state updates', () => {
89
ReactTestRenderer.create(<div />);
90
118
- expectMarksToEqual([
119
- `--react-init-${ReactVersion}`,
120
- `--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
121
- `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
122
- '--render-stop',
123
- `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
124
- `--layout-effects-start-${formatLanes(ReactFiberLane.SyncLane)}`,
125
- '--layout-effects-stop',
126
- '--commit-stop',
127
- ]);
91
+ if (gate(flags => flags.enableSchedulingProfiler)) {
92
+ expect(getMarks()).toMatchInlineSnapshot(`
93
+ Array [
94
+ "--schedule-render-1",
95
+ "--render-start-1",
96
+ "--render-stop",
97
+ "--commit-start-1",
98
+ "--react-version-17.0.3",
99
+ "--profiler-version-1",
100
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
101
+ "--layout-effects-start-1",
102
+ "--layout-effects-stop",
103
+ "--commit-stop",
104
+ ]
105
+ `);
106
+ }
107
});
108
130
- // @gate enableSchedulingProfiler
109
it('should mark concurrent render without suspends or state updates', () => {
110
ReactTestRenderer.create(<div />, {unstable_isConcurrent: true});
111
134
- expectMarksToEqual([
135
- `--react-init-${ReactVersion}`,
136
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
137
- ]);
112
+ if (gate(flags => flags.enableSchedulingProfiler)) {
113
+ expect(getMarks()).toMatchInlineSnapshot(`
114
+ Array [
115
+ "--schedule-render-16",
116
+ ]
117
+ `);
118
+ }
119
120
clearPendingMarks();
121
122
expect(Scheduler).toFlushUntilNextPaint([]);
123
143
- expectMarksToEqual([
144
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
145
- '--render-stop',
146
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
147
- `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
148
- '--layout-effects-stop',
149
- '--commit-stop',
150
- ]);
124
+ if (gate(flags => flags.enableSchedulingProfiler)) {
125
+ expect(getMarks()).toMatchInlineSnapshot(`
126
+ Array [
127
+ "--render-start-16",
128
+ "--render-stop",
129
+ "--commit-start-16",
130
+ "--react-version-17.0.3",
131
+ "--profiler-version-1",
132
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
133
+ "--layout-effects-start-16",
134
+ "--layout-effects-stop",
135
+ "--commit-stop",
136
+ ]
137
+ `);
138
+ }
139
});
140
153
- // @gate enableSchedulingProfiler
141
it('should mark render yields', async () => {
142
function Bar() {
143
Scheduler.unstable_yieldValue('Bar');
@@ -170,30 +157,31 @@ describe('SchedulingProfiler', () => {
157
// Do one step of work.
158
expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
159
173
- expectMarksToEqual([
174
- `--react-init-${ReactVersion}`,
175
- `--schedule-render-${formatLanes(ReactFiberLane.TransitionLane1)}`,
176
- `--render-start-${formatLanes(ReactFiberLane.TransitionLane1)}`,
177
- '--component-render-start-Foo',
178
- '--component-render-stop',
179
- '--render-yield',
180
- ]);
160
+ if (gate(flags => flags.enableSchedulingProfiler)) {
161
+ expect(getMarks()).toMatchInlineSnapshot(`
162
+ Array [
163
+ "--schedule-render-64",
164
+ "--render-start-64",
165
+ "--component-render-start-Foo",
166
+ "--component-render-stop",
167
+ "--render-yield",
168
+ ]
169
+ `);
170
+ }
171
} else {
172
ReactNoop.render(<Foo />);
173
174
// Do one step of work.
175
expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
176
187
- expectMarksToEqual([
188
- `--react-init-${ReactVersion}`,
189
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
190
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
191
- '--render-yield',
192
- ]);
177
+ if (gate(flags => flags.enableSchedulingProfiler)) {
178
+ expect(getMarks()).toMatchInlineSnapshot(`
179
+ Array []
180
+ `);
181
+ }
182
}
183
});
184
196
- // @gate enableSchedulingProfiler
185
it('should mark sync render with suspense that resolves', async () => {
186
const fakeSuspensePromise = Promise.resolve(true);
187
function Example() {
@@ -206,27 +194,38 @@ describe('SchedulingProfiler', () => {
194
</React.Suspense>,
195
);
196
209
- expectMarksToEqual([
210
- `--react-init-${ReactVersion}`,
211
- `--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
212
- `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
213
- '--component-render-start-Example',
214
- '--component-render-stop',
215
- '--suspense-suspend-0-Example-mount-1-Sync',
216
- '--render-stop',
217
- `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
218
- `--layout-effects-start-${formatLanes(ReactFiberLane.SyncLane)}`,
219
- '--layout-effects-stop',
220
- '--commit-stop',
221
- ]);
197
+ if (gate(flags => flags.enableSchedulingProfiler)) {
198
+ expect(getMarks()).toMatchInlineSnapshot(`
199
+ Array [
200
+ "--schedule-render-1",
201
+ "--render-start-1",
202
+ "--component-render-start-Example",
203
+ "--component-render-stop",
204
+ "--suspense-suspend-0-Example-mount-1",
205
+ "--render-stop",
206
+ "--commit-start-1",
207
+ "--react-version-17.0.3",
208
+ "--profiler-version-1",
209
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
210
+ "--layout-effects-start-1",
211
+ "--layout-effects-stop",
212
+ "--commit-stop",
213
+ ]
214
+ `);
215
+ }
216
217
clearPendingMarks();
218
219
await fakeSuspensePromise;
226
- expectMarksToEqual(['--suspense-resolved-0-Example']);
220
+ if (gate(flags => flags.enableSchedulingProfiler)) {
221
+ expect(getMarks()).toMatchInlineSnapshot(`
222
+ Array [
223
+ "--suspense-resolved-0-Example",
224
+ ]
225
+ `);
226
+ }
227
});
228
229
- // @gate enableSchedulingProfiler
229
it('should mark sync render with suspense that rejects', async () => {
230
const fakeSuspensePromise = Promise.reject(new Error('error'));
231
function Example() {
@@ -239,27 +238,38 @@ describe('SchedulingProfiler', () => {
238
</React.Suspense>,
239
);
240
242
- expectMarksToEqual([
243
- `--react-init-${ReactVersion}`,
244
- `--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
245
- `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
246
- '--component-render-start-Example',
247
- '--component-render-stop',
248
- '--suspense-suspend-0-Example-mount-1-Sync',
249
- '--render-stop',
250
- `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
251
- `--layout-effects-start-${formatLanes(ReactFiberLane.SyncLane)}`,
252
- '--layout-effects-stop',
253
- '--commit-stop',
254
- ]);
241
+ if (gate(flags => flags.enableSchedulingProfiler)) {
242
+ expect(getMarks()).toMatchInlineSnapshot(`
243
+ Array [
244
+ "--schedule-render-1",
245
+ "--render-start-1",
246
+ "--component-render-start-Example",
247
+ "--component-render-stop",
248
+ "--suspense-suspend-0-Example-mount-1",
249
+ "--render-stop",
250
+ "--commit-start-1",
251
+ "--react-version-17.0.3",
252
+ "--profiler-version-1",
253
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
254
+ "--layout-effects-start-1",
255
+ "--layout-effects-stop",
256
+ "--commit-stop",
257
+ ]
258
+ `);
259
+ }
260
261
clearPendingMarks();
262
263
await expect(fakeSuspensePromise).rejects.toThrow();
259
- expectMarksToEqual(['--suspense-rejected-0-Example']);
264
+ if (gate(flags => flags.enableSchedulingProfiler)) {
265
+ expect(getMarks()).toMatchInlineSnapshot(`
266
+ Array [
267
+ "--suspense-rejected-0-Example",
268
+ ]
269
+ `);
270
+ }
271
});
272
262
- // @gate enableSchedulingProfiler
273
it('should mark concurrent render with suspense that resolves', async () => {
274
const fakeSuspensePromise = Promise.resolve(true);
275
function Example() {
@@ -273,34 +283,49 @@ describe('SchedulingProfiler', () => {
283
{unstable_isConcurrent: true},
284
);
285
276
- expectMarksToEqual([
277
- `--react-init-${ReactVersion}`,
278
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
279
- ]);
286
+ if (gate(flags => flags.enableSchedulingProfiler)) {
287
+ expect(getMarks()).toMatchInlineSnapshot(`
288
+ Array [
289
+ "--schedule-render-16",
290
+ ]
291
+ `);
292
+ }
293
294
clearPendingMarks();
295
296
expect(Scheduler).toFlushUntilNextPaint([]);
297
285
- expectMarksToEqual([
286
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
287
- '--component-render-start-Example',
288
- '--component-render-stop',
289
- '--suspense-suspend-0-Example-mount-16-Default',
290
- '--render-stop',
291
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
292
- `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
293
- '--layout-effects-stop',
294
- '--commit-stop',
295
- ]);
298
+ if (gate(flags => flags.enableSchedulingProfiler)) {
299
+ expect(getMarks()).toMatchInlineSnapshot(`
300
+ Array [
301
+ "--render-start-16",
302
+ "--component-render-start-Example",
303
+ "--component-render-stop",
304
+ "--suspense-suspend-0-Example-mount-16",
305
+ "--render-stop",
306
+ "--commit-start-16",
307
+ "--react-version-17.0.3",
308
+ "--profiler-version-1",
309
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
310
+ "--layout-effects-start-16",
311
+ "--layout-effects-stop",
312
+ "--commit-stop",
313
+ ]
314
+ `);
315
+ }
316
317
clearPendingMarks();
318
319
await fakeSuspensePromise;
300
- expectMarksToEqual(['--suspense-resolved-0-Example']);
320
+ if (gate(flags => flags.enableSchedulingProfiler)) {
321
+ expect(getMarks()).toMatchInlineSnapshot(`
322
+ Array [
323
+ "--suspense-resolved-0-Example",
324
+ ]
325
+ `);
326
+ }
327
});
328
303
- // @gate enableSchedulingProfiler
329
it('should mark concurrent render with suspense that rejects', async () => {
330
const fakeSuspensePromise = Promise.reject(new Error('error'));
331
function Example() {
@@ -314,34 +339,49 @@ describe('SchedulingProfiler', () => {
339
{unstable_isConcurrent: true},
340
);
341
317
- expectMarksToEqual([
318
- `--react-init-${ReactVersion}`,
319
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
320
- ]);
342
+ if (gate(flags => flags.enableSchedulingProfiler)) {
343
+ expect(getMarks()).toMatchInlineSnapshot(`
344
+ Array [
345
+ "--schedule-render-16",
346
+ ]
347
+ `);
348
+ }
349
350
clearPendingMarks();
351
352
expect(Scheduler).toFlushUntilNextPaint([]);
353
326
- expectMarksToEqual([
327
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
328
- '--component-render-start-Example',
329
- '--component-render-stop',
330
- '--suspense-suspend-0-Example-mount-16-Default',
331
- '--render-stop',
332
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
333
- `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
334
- '--layout-effects-stop',
335
- '--commit-stop',
336
- ]);
354
+ if (gate(flags => flags.enableSchedulingProfiler)) {
355
+ expect(getMarks()).toMatchInlineSnapshot(`
356
+ Array [
357
+ "--render-start-16",
358
+ "--component-render-start-Example",
359
+ "--component-render-stop",
360
+ "--suspense-suspend-0-Example-mount-16",
361
+ "--render-stop",
362
+ "--commit-start-16",
363
+ "--react-version-17.0.3",
364
+ "--profiler-version-1",
365
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
366
+ "--layout-effects-start-16",
367
+ "--layout-effects-stop",
368
+ "--commit-stop",
369
+ ]
370
+ `);
371
+ }
372
373
clearPendingMarks();
374
375
await expect(fakeSuspensePromise).rejects.toThrow();
341
- expectMarksToEqual(['--suspense-rejected-0-Example']);
376
+ if (gate(flags => flags.enableSchedulingProfiler)) {
377
+ expect(getMarks()).toMatchInlineSnapshot(`
378
+ Array [
379
+ "--suspense-rejected-0-Example",
380
+ ]
381
+ `);
382
+ }
383
});
384
344
- // @gate enableSchedulingProfiler
385
it('should mark cascading class component state updates', () => {
386
class Example extends React.Component {
387
state = {didMount: false};
@@ -355,35 +395,47 @@ describe('SchedulingProfiler', () => {
395
396
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
397
358
- expectMarksToEqual([
359
- `--react-init-${ReactVersion}`,
360
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
361
- ]);
398
+ if (gate(flags => flags.enableSchedulingProfiler)) {
399
+ expect(getMarks()).toMatchInlineSnapshot(`
400
+ Array [
401
+ "--schedule-render-16",
402
+ ]
403
+ `);
404
+ }
405
406
clearPendingMarks();
407
408
expect(Scheduler).toFlushUntilNextPaint([]);
409
367
- expectMarksToEqual([
368
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
369
- '--component-render-start-Example',
370
- '--component-render-stop',
371
- '--render-stop',
372
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
373
- `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
374
- `--schedule-state-update-${formatLanes(ReactFiberLane.SyncLane)}-Example`,
375
- '--layout-effects-stop',
376
- `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
377
- '--component-render-start-Example',
378
- '--component-render-stop',
379
- '--render-stop',
380
- `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
381
- '--commit-stop',
382
- '--commit-stop',
383
- ]);
410
+ if (gate(flags => flags.enableSchedulingProfiler)) {
411
+ expect(getMarks()).toMatchInlineSnapshot(`
412
+ Array [
413
+ "--render-start-16",
414
+ "--component-render-start-Example",
415
+ "--component-render-stop",
416
+ "--render-stop",
417
+ "--commit-start-16",
418
+ "--react-version-17.0.3",
419
+ "--profiler-version-1",
420
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
421
+ "--layout-effects-start-16",
422
+ "--schedule-state-update-1-Example",
423
+ "--layout-effects-stop",
424
+ "--render-start-1",
425
+ "--component-render-start-Example",
426
+ "--component-render-stop",
427
+ "--render-stop",
428
+ "--commit-start-1",
429
+ "--react-version-17.0.3",
430
+ "--profiler-version-1",
431
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
432
+ "--commit-stop",
433
+ "--commit-stop",
434
+ ]
435
+ `);
436
+ }
437
});
438
386
- // @gate enableSchedulingProfiler
439
it('should mark cascading class component force updates', () => {
440
class Example extends React.Component {
441
componentDidMount() {
@@ -396,37 +448,47 @@ describe('SchedulingProfiler', () => {
448
449
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
450
399
- expectMarksToEqual([
400
- `--react-init-${ReactVersion}`,
401
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
402
- ]);
451
+ if (gate(flags => flags.enableSchedulingProfiler)) {
452
+ expect(getMarks()).toMatchInlineSnapshot(`
453
+ Array [
454
+ "--schedule-render-16",
455
+ ]
456
+ `);
457
+ }
458
459
clearPendingMarks();
460
461
expect(Scheduler).toFlushUntilNextPaint([]);
462
408
- expectMarksToEqual([
409
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
410
- '--component-render-start-Example',
411
- '--component-render-stop',
412
- '--render-stop',
413
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
414
- `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
415
- `--schedule-forced-update-${formatLanes(
416
- ReactFiberLane.SyncLane,
417
- )}-Example`,
418
- '--layout-effects-stop',
419
- `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
420
- '--component-render-start-Example',
421
- '--component-render-stop',
422
- '--render-stop',
423
- `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
424
- '--commit-stop',
425
- '--commit-stop',
426
- ]);
463
+ if (gate(flags => flags.enableSchedulingProfiler)) {
464
+ expect(getMarks()).toMatchInlineSnapshot(`
465
+ Array [
466
+ "--render-start-16",
467
+ "--component-render-start-Example",
468
+ "--component-render-stop",
469
+ "--render-stop",
470
+ "--commit-start-16",
471
+ "--react-version-17.0.3",
472
+ "--profiler-version-1",
473
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
474
+ "--layout-effects-start-16",
475
+ "--schedule-forced-update-1-Example",
476
+ "--layout-effects-stop",
477
+ "--render-start-1",
478
+ "--component-render-start-Example",
479
+ "--component-render-stop",
480
+ "--render-stop",
481
+ "--commit-start-1",
482
+ "--react-version-17.0.3",
483
+ "--profiler-version-1",
484
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
485
+ "--commit-stop",
486
+ "--commit-stop",
487
+ ]
488
+ `);
489
+ }
490
});
491
429
- // @gate enableSchedulingProfiler
492
it('should mark render phase state updates for class component', () => {
493
class Example extends React.Component {
494
state = {didRender: false};
@@ -440,10 +502,13 @@ describe('SchedulingProfiler', () => {
502
503
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
504
443
- expectMarksToEqual([
444
- `--react-init-${ReactVersion}`,
445
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
446
- ]);
505
+ if (gate(flags => flags.enableSchedulingProfiler)) {
506
+ expect(getMarks()).toMatchInlineSnapshot(`
507
+ Array [
508
+ "--schedule-render-16",
509
+ ]
510
+ `);
511
+ }
512
513
clearPendingMarks();
514
@@ -451,14 +516,26 @@ describe('SchedulingProfiler', () => {
516
expect(Scheduler).toFlushUntilNextPaint([]);
517
}).toErrorDev('Cannot update during an existing state transition');
518
454
- expectMarksToContain(
455
- `--schedule-state-update-${formatLanes(
456
- ReactFiberLane.DefaultLane,
457
- )}-Example`,
458
- );
519
+ if (gate(flags => flags.enableSchedulingProfiler)) {
520
+ expect(getMarks()).toMatchInlineSnapshot(`
521
+ Array [
522
+ "--render-start-16",
523
+ "--component-render-start-Example",
524
+ "--schedule-state-update-16-Example",
525
+ "--component-render-stop",
526
+ "--render-stop",
527
+ "--commit-start-16",
528
+ "--react-version-17.0.3",
529
+ "--profiler-version-1",
530
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
531
+ "--layout-effects-start-16",
532
+ "--layout-effects-stop",
533
+ "--commit-stop",
534
+ ]
535
+ `);
536
+ }
537
});
538
461
- // @gate enableSchedulingProfiler
539
it('should mark render phase force updates for class component', () => {
540
class Example extends React.Component {
541
state = {didRender: false};
@@ -472,10 +549,13 @@ describe('SchedulingProfiler', () => {
549
550
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
551
475
- expectMarksToEqual([
476
- `--react-init-${ReactVersion}`,
477
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
478
- ]);
552
+ if (gate(flags => flags.enableSchedulingProfiler)) {
553
+ expect(getMarks()).toMatchInlineSnapshot(`
554
+ Array [
555
+ "--schedule-render-16",
556
+ ]
557
+ `);
558
+ }
559
560
clearPendingMarks();
561
@@ -483,14 +563,26 @@ describe('SchedulingProfiler', () => {
563
expect(Scheduler).toFlushUntilNextPaint([]);
564
}).toErrorDev('Cannot update during an existing state transition');
565
486
- expectMarksToContain(
487
- `--schedule-forced-update-${formatLanes(
488
- ReactFiberLane.DefaultLane,
489
- )}-Example`,
490
- );
566
+ if (gate(flags => flags.enableSchedulingProfiler)) {
567
+ expect(getMarks()).toMatchInlineSnapshot(`
568
+ Array [
569
+ "--render-start-16",
570
+ "--component-render-start-Example",
571
+ "--schedule-forced-update-16-Example",
572
+ "--component-render-stop",
573
+ "--render-stop",
574
+ "--commit-start-16",
575
+ "--react-version-17.0.3",
576
+ "--profiler-version-1",
577
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
578
+ "--layout-effects-start-16",
579
+ "--layout-effects-stop",
580
+ "--commit-stop",
581
+ ]
582
+ `);
583
+ }
584
});
585
493
- // @gate enableSchedulingProfiler
586
it('should mark cascading layout updates', () => {
587
function Example() {
588
const [didMount, setDidMount] = React.useState(false);
@@ -502,37 +594,49 @@ describe('SchedulingProfiler', () => {
594
595
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
596
505
- expectMarksToEqual([
506
- `--react-init-${ReactVersion}`,
507
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
508
- ]);
597
+ if (gate(flags => flags.enableSchedulingProfiler)) {
598
+ expect(getMarks()).toMatchInlineSnapshot(`
599
+ Array [
600
+ "--schedule-render-16",
601
+ ]
602
+ `);
603
+ }
604
605
clearPendingMarks();
606
607
expect(Scheduler).toFlushUntilNextPaint([]);
608
514
- expectMarksToEqual([
515
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
516
- '--component-render-start-Example',
517
- '--component-render-stop',
518
- '--render-stop',
519
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
520
- `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
521
- `--schedule-state-update-${formatLanes(ReactFiberLane.SyncLane)}-Example`,
522
- '--layout-effects-stop',
523
- `--render-start-${formatLanes(ReactFiberLane.SyncLane)}`,
524
- '--component-render-start-Example',
525
- '--component-render-stop',
526
- '--render-stop',
527
- `--commit-start-${formatLanes(ReactFiberLane.SyncLane)}`,
528
- '--commit-stop',
529
- '--commit-stop',
530
- ]);
609
+ if (gate(flags => flags.enableSchedulingProfiler)) {
610
+ expect(getMarks()).toMatchInlineSnapshot(`
611
+ Array [
612
+ "--render-start-16",
613
+ "--component-render-start-Example",
614
+ "--component-render-stop",
615
+ "--render-stop",
616
+ "--commit-start-16",
617
+ "--react-version-17.0.3",
618
+ "--profiler-version-1",
619
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
620
+ "--layout-effects-start-16",
621
+ "--schedule-state-update-1-Example",
622
+ "--layout-effects-stop",
623
+ "--render-start-1",
624
+ "--component-render-start-Example",
625
+ "--component-render-stop",
626
+ "--render-stop",
627
+ "--commit-start-1",
628
+ "--react-version-17.0.3",
629
+ "--profiler-version-1",
630
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
631
+ "--commit-stop",
632
+ "--commit-stop",
633
+ ]
634
+ `);
635
+ }
636
});
637
638
// This test is coupled to lane implementation details, so I'm disabling it in
639
// the new fork until it stabilizes so we don't have to repeatedly update it.
535
- // @gate enableSchedulingProfiler
640
it('should mark cascading passive updates', () => {
641
function Example() {
642
const [didMount, setDidMount] = React.useState(false);
@@ -546,32 +650,38 @@ describe('SchedulingProfiler', () => {
650
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
651
});
652
549
- expectMarksToEqual([
550
- `--react-init-${ReactVersion}`,
551
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
552
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
553
- '--component-render-start-Example',
554
- '--component-render-stop',
555
- '--render-stop',
556
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
557
- `--layout-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
558
- '--layout-effects-stop',
559
- '--commit-stop',
560
- `--passive-effects-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
561
- `--schedule-state-update-${formatLanes(
562
- ReactFiberLane.DefaultLane,
563
- )}-Example`,
564
- '--passive-effects-stop',
565
- `--render-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
566
- '--component-render-start-Example',
567
- '--component-render-stop',
568
- '--render-stop',
569
- `--commit-start-${formatLanes(ReactFiberLane.DefaultLane)}`,
570
- '--commit-stop',
571
- ]);
653
+ if (gate(flags => flags.enableSchedulingProfiler)) {
654
+ expect(getMarks()).toMatchInlineSnapshot(`
655
+ Array [
656
+ "--schedule-render-16",
657
+ "--render-start-16",
658
+ "--component-render-start-Example",
659
+ "--component-render-stop",
660
+ "--render-stop",
661
+ "--commit-start-16",
662
+ "--react-version-17.0.3",
663
+ "--profiler-version-1",
664
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
665
+ "--layout-effects-start-16",
666
+ "--layout-effects-stop",
667
+ "--commit-stop",
668
+ "--passive-effects-start-16",
669
+ "--schedule-state-update-16-Example",
670
+ "--passive-effects-stop",
671
+ "--render-start-16",
672
+ "--component-render-start-Example",
673
+ "--component-render-stop",
674
+ "--render-stop",
675
+ "--commit-start-16",
676
+ "--react-version-17.0.3",
677
+ "--profiler-version-1",
678
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
679
+ "--commit-stop",
680
+ ]
681
+ `);
682
+ }
683
});
684
574
- // @gate enableSchedulingProfiler
685
it('should mark render phase updates', () => {
686
function Example() {
687
const [didRender, setDidRender] = React.useState(false);
@@ -585,10 +695,24 @@ describe('SchedulingProfiler', () => {
695
ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
696
});
697
588
- expectMarksToContain(
589
- `--schedule-state-update-${formatLanes(
590
- ReactFiberLane.DefaultLane,
591
- )}-Example`,
592
- );
698
+ if (gate(flags => flags.enableSchedulingProfiler)) {
699
+ expect(getMarks()).toMatchInlineSnapshot(`
700
+ Array [
701
+ "--schedule-render-16",
702
+ "--render-start-16",
703
+ "--component-render-start-Example",
704
+ "--schedule-state-update-16-Example",
705
+ "--component-render-stop",
706
+ "--render-stop",
707
+ "--commit-start-16",
708
+ "--react-version-17.0.3",
709
+ "--profiler-version-1",
710
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
711
+ "--layout-effects-start-16",
712
+ "--layout-effects-stop",
713
+ "--commit-stop",
714
+ ]
715
+ `);
716
+ }
717
});
718
});
packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js
+97
-57
@@ -15,12 +15,10 @@
15
describe('SchedulingProfiler labels', () => {
16
let React;
17
let ReactDOM;
18
- let ReactFiberLane;
18
19
let act;
20
let clearedMarks;
21
let featureDetectionMarkName = null;
23
- let formatLanes;
22
let marks;
23
24
function polyfillJSDomUserTiming() {
@@ -75,14 +73,6 @@ describe('SchedulingProfiler labels', () => {
73
74
const TestUtils = require('react-dom/test-utils');
75
act = TestUtils.act;
78
-
79
- const SchedulingProfiler = require('react-reconciler/src/SchedulingProfiler');
80
- formatLanes = SchedulingProfiler.formatLanes;
81
-
82
- const ReactFeatureFlags = require('shared/ReactFeatureFlags');
83
- ReactFiberLane = ReactFeatureFlags.enableNewReconciler
84
- ? require('react-reconciler/src/ReactFiberLane.new')
85
- : require('react-reconciler/src/ReactFiberLane.old');
76
});
77
78
afterEach(() => {
@@ -92,29 +82,45 @@ describe('SchedulingProfiler labels', () => {
82
delete global.performance;
83
});
84
95
- // @gate enableSchedulingProfiler
85
it('regression test SyncLane', () => {
86
ReactDOM.render(<div />, document.createElement('div'));
98
- expect(clearedMarks).toContain(
99
- `--schedule-render-${formatLanes(ReactFiberLane.SyncLane)}`,
100
- );
87
+
88
+ if (gate(flags => flags.enableSchedulingProfiler)) {
89
+ expect(clearedMarks).toMatchInlineSnapshot(`
90
+ Array [
91
+ "__v3",
92
+ "--schedule-render-1",
93
+ "--render-start-1",
94
+ "--render-stop",
95
+ "--commit-start-1",
96
+ "--react-version-17.0.3",
97
+ "--profiler-version-1",
98
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
99
+ "--layout-effects-start-1",
100
+ "--layout-effects-stop",
101
+ "--commit-stop",
102
+ ]
103
+ `);
104
+ }
105
});
106
103
- // @gate enableSchedulingProfiler
107
it('regression test DefaultLane', () => {
105
- const container = document.createElement('div');
106
- const root = ReactDOM.createRoot(container);
107
-
108
- act(() => {
109
- root.render(<div />);
110
- expect(clearedMarks).toContain(
111
- `--schedule-render-${formatLanes(ReactFiberLane.DefaultLane)}`,
112
- );
113
- });
108
+ if (gate(flags => flags.enableSchedulingProfiler)) {
109
+ act(() => {
110
+ const container = document.createElement('div');
111
+ const root = ReactDOM.createRoot(container);
112
+
113
+ root.render(<div />);
114
+ expect(clearedMarks).toMatchInlineSnapshot(`
115
+ Array [
116
+ "__v3",
117
+ "--schedule-render-16",
118
+ ]
119
+ `);
120
+ });
121
+ }
122
});
123
116
- // @gate enableSchedulingProfiler
117
- // @gate !enableLegacyFBSupport
124
it('regression test InputDiscreteLane', () => {
125
const container = document.createElement('div');
126
const root = ReactDOM.createRoot(container);
@@ -128,24 +134,41 @@ describe('SchedulingProfiler labels', () => {
134
return <button ref={targetRef} onClick={handleClick} />;
135
}
136
131
- act(() => {
132
- root.render(<App />);
133
- });
134
-
135
- clearedMarks.splice(0);
136
-
137
- act(() => {
138
- targetRef.current.click();
139
- });
140
- expect(clearedMarks).toContain(
141
- `--schedule-state-update-${formatLanes(ReactFiberLane.SyncLane)}-App`,
142
- );
137
+ if (
138
+ gate(
139
+ flags => flags.enableSchedulingProfiler && !flags.enableLegacyFBSupport,
140
+ )
141
+ ) {
142
+ act(() => {
143
+ root.render(<App />);
144
+ });
145
+
146
+ clearedMarks.splice(0);
147
+
148
+ act(() => {
149
+ targetRef.current.click();
150
+ });
151
+
152
+ expect(clearedMarks).toMatchInlineSnapshot(`
153
+ Array [
154
+ "--schedule-state-update-1-App",
155
+ "--render-start-1",
156
+ "--component-render-start-App",
157
+ "--component-render-stop",
158
+ "--render-stop",
159
+ "--commit-start-1",
160
+ "--react-version-17.0.3",
161
+ "--profiler-version-1",
162
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
163
+ "--layout-effects-start-1",
164
+ "--layout-effects-stop",
165
+ "--commit-stop",
166
+ ]
167
+ `);
168
+ }
169
});
170
145
- // @gate enableSchedulingProfiler
171
it('regression test InputContinuousLane', () => {
147
- const container = document.createElement('div');
148
- const root = ReactDOM.createRoot(container);
172
const targetRef = React.createRef(null);
173
174
function App() {
@@ -154,21 +177,38 @@ describe('SchedulingProfiler labels', () => {
177
return <div ref={targetRef} onMouseOver={handleMouseOver} />;
178
}
179
157
- act(() => {
158
- root.render(<App />);
159
- });
160
-
161
- clearedMarks.splice(0);
162
-
163
- act(() => {
164
- const event = document.createEvent('MouseEvents');
165
- event.initEvent('mouseover', true, true);
166
- dispatchAndSetCurrentEvent(targetRef.current, event);
167
- });
168
- expect(clearedMarks).toContain(
169
- `--schedule-state-update-${formatLanes(
170
- ReactFiberLane.InputContinuousLane,
171
- )}-App`,
172
- );
180
+ if (gate(flags => flags.enableSchedulingProfiler)) {
181
+ const container = document.createElement('div');
182
+ const root = ReactDOM.createRoot(container);
183
+
184
+ act(() => {
185
+ root.render(<App />);
186
+ });
187
+
188
+ clearedMarks.splice(0);
189
+
190
+ act(() => {
191
+ const event = document.createEvent('MouseEvents');
192
+ event.initEvent('mouseover', true, true);
193
+ dispatchAndSetCurrentEvent(targetRef.current, event);
194
+ });
195
+
196
+ expect(clearedMarks).toMatchInlineSnapshot(`
197
+ Array [
198
+ "--schedule-state-update-4-App",
199
+ "--render-start-4",
200
+ "--component-render-start-App",
201
+ "--component-render-stop",
202
+ "--render-stop",
203
+ "--commit-start-4",
204
+ "--react-version-17.0.3",
205
+ "--profiler-version-1",
206
+ "--react-lane-labels-Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen",
207
+ "--layout-effects-start-4",
208
+ "--layout-effects-stop",
209
+ "--commit-stop",
210
+ ]
211
+ `);
212
+ }
213
});
214
});