@samitouri / QOS-React-2 / commits / a8725a3e62

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 });