/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
'use strict';
import {normalizeCodeLocInfo} from './utils';
describe('Timeline profiler', () => {
let React;
let ReactDOM;
let ReactDOMClient;
let Scheduler;
let utils;
let assertLog;
let waitFor;
describe('User Timing API', () => {
let clearedMarks;
let featureDetectionMarkName = null;
let marks;
let setPerformanceMock;
function createUserTimingPolyfill() {
featureDetectionMarkName = null;
clearedMarks = [];
marks = [];
// Remove file-system specific bits or version-specific bits of information from the module range marks.
function filterMarkData(markName) {
if (markName.startsWith('--react-internal-module-start')) {
return '--react-internal-module-start- at filtered (<anonymous>:0:0)';
} else if (markName.startsWith('--react-internal-module-stop')) {
return '--react-internal-module-stop- at filtered (<anonymous>:1:1)';
} else if (markName.startsWith('--react-version')) {
return '--react-version-<filtered-version>';
} else {
return markName;
}
}
// This is not a true polyfill, but it gives us enough to capture marks.
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
return {
clearMarks(markName) {
markName = filterMarkData(markName);
clearedMarks.push(markName);
marks = marks.filter(mark => mark !== markName);
},
mark(markName, markOptions) {
markName = filterMarkData(markName);
if (featureDetectionMarkName === null) {
featureDetectionMarkName = markName;
}
marks.push(markName);
if (markOptions != null) {
// This is triggers the feature detection.
markOptions.startTime++;
}
},
};
}
function clearPendingMarks() {
clearedMarks.splice(0);
}
beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitFor = InternalTestUtils.waitFor;
setPerformanceMock =
require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
setPerformanceMock(createUserTimingPolyfill());
const store = global.store;
// Start profiling so that data will actually be recorded.
utils.act(() => store.profilerStore.startProfiling());
global.IS_REACT_ACT_ENVIRONMENT = true;
});
afterEach(() => {
// Verify all logged marks also get cleared.
expect(marks).toHaveLength(0);
setPerformanceMock(null);
});
describe('getLanesFromTransportDecimalBitmask', () => {
let getLanesFromTransportDecimalBitmask;
beforeEach(() => {
getLanesFromTransportDecimalBitmask =
require('react-devtools-timeline/src/import-worker/preprocessData').getLanesFromTransportDecimalBitmask;
});
// @reactVersion >= 18.0
it('should return array of lane numbers from bitmask string', () => {
expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]);
expect(getLanesFromTransportDecimalBitmask('512')).toEqual([9]);
expect(getLanesFromTransportDecimalBitmask('3')).toEqual([0, 1]);
expect(getLanesFromTransportDecimalBitmask('1234')).toEqual([
1, 4, 6, 7, 10,
]); // 2 + 16 + 64 + 128 + 1024
expect(
getLanesFromTransportDecimalBitmask('1073741824'), // 0b1000000000000000000000000000000
).toEqual([30]);
expect(
getLanesFromTransportDecimalBitmask('2147483647'), // 0b1111111111111111111111111111111
).toEqual(Array.from(Array(31).keys()));
});
// @reactVersion >= 18.0
it('should return empty array if laneBitmaskString is not a bitmask', () => {
expect(getLanesFromTransportDecimalBitmask('')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('hello')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('-1')).toEqual([]);
expect(getLanesFromTransportDecimalBitmask('-0')).toEqual([]);
});
// @reactVersion >= 18.0
it('should ignore lanes outside REACT_TOTAL_NUM_LANES', () => {
const REACT_TOTAL_NUM_LANES =
require('react-devtools-timeline/src/constants').REACT_TOTAL_NUM_LANES;
// Sanity check; this test may need to be updated when the no. of fiber lanes are changed.
expect(REACT_TOTAL_NUM_LANES).toBe(31);
expect(
getLanesFromTransportDecimalBitmask(
'4294967297', // 2^32 + 1
),
).toEqual([0]);
});
});
describe('preprocessData', () => {
let preprocessData;
beforeEach(() => {
preprocessData =
require('react-devtools-timeline/src/import-worker/preprocessData').default;
});
// These should be dynamic to mimic a real profile,
// but reprooducible between test runs.
let pid = 0;
let tid = 0;
let startTime = 0;
function createUserTimingEntry(data) {
return {
pid: ++pid,
tid: ++tid,
ts: ++startTime,
...data,
};
}
function createProfilerVersionEntry() {
const SCHEDULING_PROFILER_VERSION =
require('react-devtools-timeline/src/constants').SCHEDULING_PROFILER_VERSION;
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--profiler-version-' + SCHEDULING_PROFILER_VERSION,
});
}
function createReactVersionEntry() {
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--react-version-<filtered-version>',
});
}
function createLaneLabelsEntry() {
return createUserTimingEntry({
cat: 'blink.user_timing',
name: '--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',
});
}
function createNativeEventEntry(type, duration) {
return createUserTimingEntry({
cat: 'devtools.timeline',
name: 'EventDispatch',
args: {data: {type}},
dur: duration,
tdur: duration,
});
}
function creactCpuProfilerSample() {
return createUserTimingEntry({
args: {data: {startTime: ++startTime}},
cat: 'disabled-by-default-v8.cpu_profiler',
id: '0x1',
name: 'Profile',
ph: 'P',
});
}
function createBoilerplateEntries() {
return [
createProfilerVersionEntry(),
createReactVersionEntry(),
createLaneLabelsEntry(),
];
}
function createUserTimingData(sampleMarks) {
const cpuProfilerSample = creactCpuProfilerSample();
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},
});
const userTimingData = [cpuProfilerSample, randomSample];
sampleMarks.forEach(markName => {
userTimingData.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});
return userTimingData;
}
beforeEach(() => {
tid = 0;
pid = 0;
startTime = 0;
});
// @reactVersion >= 18.0
it('should throw given an empty timeline', async () => {
await expect(async () => preprocessData([])).rejects.toThrow();
});
// @reactVersion >= 18.0
it('should throw given a timeline with no Profile event', async () => {
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},
});
await expect(async () =>
preprocessData([randomSample]),
).rejects.toThrow();
});
// @reactVersion >= 18.0
it('should throw given a timeline without an explicit profiler version mark nor any other React marks', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
await expect(
async () => await preprocessData([cpuProfilerSample]),
).rejects.toThrow(
'Please provide profiling data from an React application',
);
});
// @reactVersion >= 18.0
it('should throw given a timeline with React scheduling marks, but without an explicit profiler version mark', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
const scheduleRenderSample = createUserTimingEntry({
cat: 'blink.user_timing',
name: '--schedule-render-512-',
});
const samples = [cpuProfilerSample, scheduleRenderSample];
await expect(async () => await preprocessData(samples)).rejects.toThrow(
'This version of profiling data is not supported',
);
});
// @reactVersion >= 18.0
it('should return empty data given a timeline with no React scheduling profiling marks', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
const randomSample = createUserTimingEntry({
dur: 100,
tdur: 200,
ph: 'X',
cat: 'disabled-by-default-devtools.timeline',
name: 'RunTask',
args: {},
});
const data = await preprocessData([
...createBoilerplateEntries(),
cpuProfilerSample,
randomSample,
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {},
"componentMeasures": [],
"duration": 0.005,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [],
2 => [],
3 => [],
4 => [],
5 => [],
6 => [],
7 => [],
8 => [],
9 => [],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 1,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});
// @reactVersion >= 18.0
it('should process legacy data format (before lane labels were added)', async () => {
const cpuProfilerSample = creactCpuProfilerSample();
// Data below is hard-coded based on an older profile sample.
// Should be fine since this is explicitly a legacy-format test.
const data = await preprocessData([
...createBoilerplateEntries(),
cpuProfilerSample,
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--schedule-render-512-',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--render-start-512',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--render-stop',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--commit-start-512',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--layout-effects-start-512',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--layout-effects-stop',
}),
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--commit-stop',
}),
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
0 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.005,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.009,
"type": "layout-effects",
},
],
},
"componentMeasures": [],
"duration": 0.011,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [],
2 => [],
3 => [],
4 => [],
5 => [],
6 => [],
7 => [],
8 => [],
9 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.005,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.009,
"type": "layout-effects",
},
],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000001001",
"timestamp": 0.005,
"type": "schedule-render",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 1,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});
it('should process a sample legacy render sequence', async () => {
utils.legacyRender(<div />, document.createElement('div'));
const data = await preprocessData([
...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
0 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.01,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.014,
"type": "layout-effects",
},
],
},
"componentMeasures": [],
"duration": 0.016,
"flamechart": [],
"internalModuleSourceToRanges": Map {
undefined => [
[
{
"columnNumber": 0,
"functionName": "filtered",
"lineNumber": 0,
"source": " at filtered (<anonymous>:0:0)",
},
{
"columnNumber": 1,
"functionName": "filtered",
"lineNumber": 1,
"source": " at filtered (<anonymous>:1:1)",
},
],
],
},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.01,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.008,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.014,
"type": "layout-effects",
},
],
2 => [],
3 => [],
4 => [],
5 => [],
6 => [],
7 => [],
8 => [],
9 => [],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000000001",
"timestamp": 0.005,
"type": "schedule-render",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 4,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});
it('should process a sample createRoot render sequence', async () => {
function App() {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
if (!didMount) {
setDidMount(true);
}
});
return true;
}
const root = ReactDOMClient.createRoot(document.createElement('div'));
utils.act(() => root.render(<App />));
const data = await preprocessData([
...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);
expect(data).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
0 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.01,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.016,
"type": "layout-effects",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.019,
"type": "passive-effects",
},
],
1 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.028,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.034,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.037,
"type": "passive-effects",
},
],
},
"componentMeasures": [
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.007,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0.002,
"timestamp": 0.02,
"type": "passive-effect-mount",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.025,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
"timestamp": 0.038,
"type": "passive-effect-mount",
"warning": null,
},
],
"duration": 0.04,
"flamechart": [],
"internalModuleSourceToRanges": Map {
undefined => [
[
{
"columnNumber": 0,
"functionName": "filtered",
"lineNumber": 0,
"source": " at filtered (<anonymous>:0:0)",
},
{
"columnNumber": 1,
"functionName": "filtered",
"lineNumber": 1,
"source": " at filtered (<anonymous>:1:1)",
},
],
],
},
"laneToLabelMap": Map {
0 => "Sync",
1 => "InputContinuousHydration",
2 => "InputContinuous",
3 => "DefaultHydration",
4 => "Default",
5 => "TransitionHydration",
6 => "Transition",
7 => "Transition",
8 => "Transition",
9 => "Transition",
10 => "Transition",
11 => "Transition",
12 => "Transition",
13 => "Transition",
14 => "Transition",
15 => "Transition",
16 => "Transition",
17 => "Transition",
18 => "Transition",
19 => "Transition",
20 => "Transition",
21 => "Transition",
22 => "Retry",
23 => "Retry",
24 => "Retry",
25 => "Retry",
26 => "Retry",
27 => "SelectiveHydration",
28 => "IdleHydration",
29 => "Idle",
30 => "Offscreen",
},
"laneToReactMeasureMap": Map {
0 => [],
1 => [],
2 => [],
3 => [],
4 => [],
5 => [
{
"batchUID": 0,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render-idle",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.006,
"type": "render",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.01,
"type": "commit",
},
{
"batchUID": 0,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.016,
"type": "layout-effects",
},
{
"batchUID": 0,
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.019,
"type": "passive-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.024,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.028,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0.001,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.034,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.037,
"type": "passive-effects",
},
],
6 => [],
7 => [],
8 => [],
9 => [],
10 => [],
11 => [],
12 => [],
13 => [],
14 => [],
15 => [],
16 => [],
17 => [],
18 => [],
19 => [],
20 => [],
21 => [],
22 => [],
23 => [],
24 => [],
25 => [],
26 => [],
27 => [],
28 => [],
29 => [],
30 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.005,
"type": "schedule-render",
"warning": null,
},
{
"componentName": "App",
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.021,
"type": "schedule-state-update",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": 4,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});
// @reactVersion >= 18.0
it('should error if events and measures are incomplete', async () => {
const container = document.createElement('div');
utils.legacyRender(<div />, container);
const invalidMarks = clearedMarks.filter(
mark => !mark.includes('render-stop'),
);
const invalidUserTimingData = createUserTimingData(invalidMarks);
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
preprocessData([
...createBoilerplateEntries(),
...invalidUserTimingData,
]);
expect(error).toHaveBeenCalled();
});
// @reactVersion >= 18.0
it('should error if work is completed without being started', async () => {
const container = document.createElement('div');
utils.legacyRender(<div />, container);
const invalidMarks = clearedMarks.filter(
mark => !mark.includes('render-start'),
);
const invalidUserTimingData = createUserTimingData(invalidMarks);
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
preprocessData([
...createBoilerplateEntries(),
...invalidUserTimingData,
]);
expect(error).toHaveBeenCalled();
});
// @reactVersion >= 18.0
it('should populate other user timing marks', async () => {
const userTimingData = createUserTimingData([]);
userTimingData.push(
createUserTimingEntry({
args: {},
cat: 'blink.user_timing',
id: '0xcdf75f7c',
name: 'VCWithoutImage: root',
ph: 'n',
scope: 'blink.user_timing',
}),
);
userTimingData.push(
createUserTimingEntry({
cat: 'blink.user_timing',
name: '--a-mark-that-looks-like-one-of-ours',
ph: 'R',
}),
);
userTimingData.push(
createUserTimingEntry({
cat: 'blink.user_timing',
name: 'Some other mark',
ph: 'R',
}),
);
const data = await preprocessData([
...createBoilerplateEntries(),
...userTimingData,
]);
expect(data.otherUserTimingMarks).toMatchInlineSnapshot(`
[
{
"name": "VCWithoutImage: root",
"timestamp": 0.003,
},
{
"name": "--a-mark-that-looks-like-one-of-ours",
"timestamp": 0.004,
},
{
"name": "Some other mark",
"timestamp": 0.005,
},
]
`);
});
// @reactVersion >= 18.0
it('should include a suspended resource "displayName" if one is set', async () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
promise = Promise.resolve(true).then(() => {
resolvedValue = value;
});
promise.displayName = 'Testing displayName';
}
throw promise;
}
function Component() {
const value = readValue(123);
return value;
}
const testMarks = [creactCpuProfilerSample()];
const root = ReactDOMClient.createRoot(document.createElement('div'));
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component />
</React.Suspense>,
),
);
testMarks.push(...createUserTimingData(clearedMarks));
let data;
await utils.actAsync(async () => {
data = await preprocessData(testMarks);
});
expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
});
describe('warnings', () => {
describe('long event handlers', () => {
// @reactVersion >= 18.0
it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {
function App() {
return null;
}
const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
createNativeEventEntry('click', 5),
];
clearPendingMarks();
utils.legacyRender(<App />, document.createElement('div'));
testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);
const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});
// @reactVersion >= 18.0
it('should not warn about long events if the cause was non-React JavaScript', async () => {
function App() {
return null;
}
const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
createNativeEventEntry('click', 25000),
];
startTime += 2000;
clearPendingMarks();
utils.legacyRender(<App />, document.createElement('div'));
testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);
const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});
// @reactVersion >= 18.0
it('should warn when React scedules a long (sync) update inside of an event', async () => {
function App() {
return null;
}
const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
createNativeEventEntry('click', 25000),
];
clearPendingMarks();
utils.legacyRender(<App />, document.createElement('div'));
clearedMarks.forEach(markName => {
if (markName === '--render-stop') {
// Fake a long running render
startTime += 20000;
}
testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});
const data = await preprocessData(testMarks);
const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toMatchInlineSnapshot(
`"An event handler scheduled a big update with React. Consider using the Transition API to defer some of this work."`,
);
});
// @reactVersion >= 18.2
it('should not warn when React finishes a previously long (async) update with a short (sync) update inside of an event', async () => {
function Yield({id, value}) {
Scheduler.log(`${id}:${value}`);
return null;
}
const testMarks = [
creactCpuProfilerSample(),
...createBoilerplateEntries(),
];
// Advance the clock by some arbitrary amount.
startTime += 50000;
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
// Temporarily turn off the act environment, since we're intentionally using Scheduler instead.
global.IS_REACT_ACT_ENVIRONMENT = false;
React.startTransition(() => {
// Start rendering an async update (but don't finish).
root.render(
<>
<Yield id="A" value={1} />
<Yield id="B" value={1} />
</>,
);
});
await waitFor(['A:1']);
testMarks.push(...createUserTimingData(clearedMarks));
clearPendingMarks();
// Advance the clock some more to make the pending React update seem long.
startTime += 20000;
// Fake a long "click" event in the middle
// and schedule a sync update that will also flush the previous work.
testMarks.push(createNativeEventEntry('click', 25000));
ReactDOM.flushSync(() => {
root.render(
<>
<Yield id="A" value={2} />
<Yield id="B" value={2} />
</>,
);
});
assertLog(['A:2', 'B:2']);
testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);
const event = data.nativeEvents.find(({type}) => type === 'click');
expect(event.warning).toBe(null);
});
});
describe('nested updates', () => {
// @reactVersion >= 18.2
it('should not warn about short nested (state) updates during layout effects', async () => {
function Component() {
const [didMount, setDidMount] = React.useState(false);
Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
React.useLayoutEffect(() => {
setDidMount(true);
}, []);
return didMount;
}
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});
assertLog(['Component mount', 'Component update']);
const data = await preprocessData([
...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-state-update',
);
expect(event.warning).toBe(null);
});
// @reactVersion >= 18.2
it('should not warn about short (forced) updates during layout effects', async () => {
class Component extends React.Component {
_didMount: boolean = false;
componentDidMount() {
this._didMount = true;
this.forceUpdate();
}
render() {
Scheduler.log(
`Component ${this._didMount ? 'update' : 'mount'}`,
);
return null;
}
}
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});
assertLog(['Component mount', 'Component update']);
const data = await preprocessData([
...createBoilerplateEntries(),
...createUserTimingData(clearedMarks),
]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-force-update',
);
expect(event.warning).toBe(null);
});
// This is temporarily disabled because the warning doesn't work
// with useDeferredValue
it.skip('should warn about long nested (state) updates during layout effects', async () => {
function Component() {
const [didMount, setDidMount] = React.useState(false);
Scheduler.log(`Component ${didMount ? 'update' : 'mount'}`);
// Fake a long render
startTime += 20000;
React.useLayoutEffect(() => {
setDidMount(true);
}, []);
return didMount;
}
const cpuProfilerSample = creactCpuProfilerSample();
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});
assertLog(['Component mount', 'Component update']);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}
testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});
const data = await preprocessData([
cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-state-update',
);
expect(event.warning).toMatchInlineSnapshot(
`"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
);
});
// This is temporarily disabled because the warning doesn't work
// with useDeferredValue
it.skip('should warn about long nested (forced) updates during layout effects', async () => {
class Component extends React.Component {
_didMount: boolean = false;
componentDidMount() {
this._didMount = true;
this.forceUpdate();
}
render() {
Scheduler.log(
`Component ${this._didMount ? 'update' : 'mount'}`,
);
return null;
}
}
const cpuProfilerSample = creactCpuProfilerSample();
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});
assertLog(['Component mount', 'Component update']);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}
testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});
const data = await preprocessData([
cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);
const event = data.schedulingEvents.find(
({type}) => type === 'schedule-force-update',
);
expect(event.warning).toMatchInlineSnapshot(
`"A big nested update was scheduled during layout. Nested updates require React to re-render synchronously before the browser can paint. Consider delaying this update by moving it to a passive effect (useEffect)."`,
);
});
// @reactVersion >= 18.2
it('should not warn about transition updates scheduled during commit phase', async () => {
function Component() {
const [value, setValue] = React.useState(0);
// eslint-disable-next-line no-unused-vars
const [isPending, startTransition] = React.useTransition();
Scheduler.log(`Component rendered with value ${value}`);
// Fake a long render
if (value !== 0) {
Scheduler.log('Long render');
startTime += 20000;
}
React.useLayoutEffect(() => {
startTransition(() => {
setValue(1);
});
}, []);
return value;
}
const cpuProfilerSample = creactCpuProfilerSample();
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});
assertLog([
'Component rendered with value 0',
'Component rendered with value 0',
'Component rendered with value 1',
'Long render',
]);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}
testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});
const data = await preprocessData([
cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);
data.schedulingEvents.forEach(event => {
expect(event.warning).toBeNull();
});
});
// This is temporarily disabled because the warning doesn't work
// with useDeferredValue
it.skip('should not warn about deferred value updates scheduled during commit phase', async () => {
function Component() {
const [value, setValue] = React.useState(0);
const deferredValue = React.useDeferredValue(value);
Scheduler.log(
`Component rendered with value ${value} and deferredValue ${deferredValue}`,
);
// Fake a long render
if (deferredValue !== 0) {
Scheduler.log('Long render');
startTime += 20000;
}
React.useLayoutEffect(() => {
setValue(1);
}, []);
return value + deferredValue;
}
const cpuProfilerSample = creactCpuProfilerSample();
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() => {
root.render(<Component />);
});
assertLog([
'Component rendered with value 0 and deferredValue 0',
'Component rendered with value 1 and deferredValue 0',
'Component rendered with value 1 and deferredValue 1',
'Long render',
]);
const testMarks = [];
clearedMarks.forEach(markName => {
if (markName === '--component-render-start-Component') {
// Fake a long running render
startTime += 20000;
}
testMarks.push({
pid: ++pid,
tid: ++tid,
ts: ++startTime,
args: {data: {}},
cat: 'blink.user_timing',
name: markName,
ph: 'R',
});
});
const data = await preprocessData([
cpuProfilerSample,
...createBoilerplateEntries(),
...testMarks,
]);
data.schedulingEvents.forEach(event => {
expect(event.warning).toBeNull();
});
});
});
describe('errors thrown while rendering', () => {
// @reactVersion >= 18.0
it('shoult parse Errors thrown during render', async () => {
jest.spyOn(console, 'error');
class ErrorBoundary extends React.Component {
state = {error: null};
componentDidCatch(error) {
this.setState({error});
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
function ExampleThatThrows() {
throw Error('Expected error');
}
const testMarks = [creactCpuProfilerSample()];
// Mount and commit the app
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() =>
root.render(
<ErrorBoundary>
<ExampleThatThrows />
</ErrorBoundary>,
),
);
testMarks.push(...createUserTimingData(clearedMarks));
const data = await preprocessData(testMarks);
expect(data.thrownErrors).toHaveLength(2);
expect(data.thrownErrors[0].message).toMatchInlineSnapshot(
'"Expected error"',
);
});
});
describe('suspend during an update', () => {
// This also tests an edge case where a component suspends while profiling
// before the first commit is logged (so the lane-to-labels map will not yet exist).
// @reactVersion >= 18.2
it('should warn about suspending during an update', async () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
promise = Promise.resolve(true).then(() => {
resolvedValue = value;
});
}
throw promise;
}
function Component({shouldSuspend}) {
Scheduler.log(`Component ${shouldSuspend}`);
if (shouldSuspend) {
readValue(123);
}
return null;
}
// Mount and commit the app
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={false} />
</React.Suspense>,
),
);
const testMarks = [creactCpuProfilerSample()];
// Start profiling and suspend during a render.
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={true} />
</React.Suspense>,
),
);
testMarks.push(...createUserTimingData(clearedMarks));
let data;
await utils.actAsync(async () => {
data = await preprocessData(testMarks);
});
expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].warning).toMatchInlineSnapshot(
`"A component suspended during an update which caused a fallback to be shown. Consider using the Transition API to avoid hiding components after they've been mounted."`,
);
});
// @reactVersion >= 18.2
it('should not warn about suspending during an transition', async () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
promise = Promise.resolve(true).then(() => {
resolvedValue = value;
});
}
throw promise;
}
function Component({shouldSuspend}) {
Scheduler.log(`Component ${shouldSuspend}`);
if (shouldSuspend) {
readValue(123);
}
return null;
}
// Mount and commit the app
const root = ReactDOMClient.createRoot(
document.createElement('div'),
);
utils.act(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={false} />
</React.Suspense>,
),
);
const testMarks = [creactCpuProfilerSample()];
// Start profiling and suspend during a render.
await utils.actAsync(async () =>
React.startTransition(() =>
root.render(
<React.Suspense fallback="Loading...">
<Component shouldSuspend={true} />
</React.Suspense>,
),
),
);
testMarks.push(...createUserTimingData(clearedMarks));
let data;
await utils.actAsync(async () => {
data = await preprocessData(testMarks);
});
expect(data.suspenseEvents).toHaveLength(1);
expect(data.suspenseEvents[0].warning).toBe(null);
});
});
});
// TODO: Add test for snapshot base64 parsing
// TODO: Add test for flamechart parsing
});
});
// Note the in-memory tests vary slightly (e.g. timestamp values, lane numbers) from the above tests.
// That's okay; the important thing is the lane-to-label matches the subsequent events/measures.
describe('DevTools hook (in memory)', () => {
let store;
beforeEach(() => {
utils = require('./utils');
utils.beforeEachProfiling();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
store = global.store;
// Start profiling so that data will actually be recorded.
utils.act(() => store.profilerStore.startProfiling());
global.IS_REACT_ACT_ENVIRONMENT = true;
});
it('should process a sample legacy render sequence', async () => {
utils.legacyRender(<div />, document.createElement('div'));
utils.act(() => store.profilerStore.stopProfiling());
const data = store.profilerStore.profilingData?.timelineData;
expect(data).toHaveLength(1);
const timelineData = data[0];
expect(timelineData).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
1 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "layout-effects",
},
],
},
"componentMeasures": [],
"duration": 20,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
1 => "SyncHydrationLane",
2 => "Sync",
4 => "InputContinuousHydration",
8 => "InputContinuous",
16 => "DefaultHydration",
32 => "Default",
64 => "TransitionHydration",
128 => "Transition",
256 => "Transition",
512 => "Transition",
1024 => "Transition",
2048 => "Transition",
4096 => "Transition",
8192 => "Transition",
16384 => "Transition",
32768 => "Transition",
65536 => "Transition",
131072 => "Transition",
262144 => "Transition",
524288 => "Transition",
1048576 => "Transition",
2097152 => "Transition",
4194304 => "Retry",
8388608 => "Retry",
16777216 => "Retry",
33554432 => "Retry",
67108864 => "SelectiveHydration",
134217728 => "IdleHydration",
268435456 => "Idle",
536870912 => "Offscreen",
1073741824 => "Deferred",
},
"laneToReactMeasureMap": Map {
1 => [],
2 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "layout-effects",
},
],
4 => [],
8 => [],
16 => [],
32 => [],
64 => [],
128 => [],
256 => [],
512 => [],
1024 => [],
2048 => [],
4096 => [],
8192 => [],
16384 => [],
32768 => [],
65536 => [],
131072 => [],
262144 => [],
524288 => [],
1048576 => [],
2097152 => [],
4194304 => [],
8388608 => [],
16777216 => [],
33554432 => [],
67108864 => [],
134217728 => [],
268435456 => [],
536870912 => [],
1073741824 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000000010",
"timestamp": 10,
"type": "schedule-render",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": -10,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});
it('should process a sample createRoot render sequence', async () => {
function App() {
const [didMount, setDidMount] = React.useState(false);
React.useEffect(() => {
if (!didMount) {
setDidMount(true);
}
});
return true;
}
const root = ReactDOMClient.createRoot(document.createElement('div'));
utils.act(() => root.render(<App />));
utils.act(() => store.profilerStore.stopProfiling());
const data = store.profilerStore.profilingData?.timelineData;
expect(data).toHaveLength(1);
const timelineData = data[0];
// normalize the location for component stack source
// for snapshot testing
timelineData.schedulingEvents.forEach(event => {
if (event.componentStack) {
event.componentStack = normalizeCodeLocInfo(event.componentStack);
}
});
expect(timelineData).toMatchInlineSnapshot(`
{
"batchUIDToMeasuresMap": Map {
1 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
],
2 => [
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 2,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
],
},
"componentMeasures": [
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "passive-effect-mount",
"warning": null,
},
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0,
"timestamp": 10,
"type": "passive-effect-mount",
"warning": null,
},
],
"duration": 20,
"flamechart": [],
"internalModuleSourceToRanges": Map {},
"laneToLabelMap": Map {
1 => "SyncHydrationLane",
2 => "Sync",
4 => "InputContinuousHydration",
8 => "InputContinuous",
16 => "DefaultHydration",
32 => "Default",
64 => "TransitionHydration",
128 => "Transition",
256 => "Transition",
512 => "Transition",
1024 => "Transition",
2048 => "Transition",
4096 => "Transition",
8192 => "Transition",
16384 => "Transition",
32768 => "Transition",
65536 => "Transition",
131072 => "Transition",
262144 => "Transition",
524288 => "Transition",
1048576 => "Transition",
2097152 => "Transition",
4194304 => "Retry",
8388608 => "Retry",
16777216 => "Retry",
33554432 => "Retry",
67108864 => "SelectiveHydration",
134217728 => "IdleHydration",
268435456 => "Idle",
536870912 => "Offscreen",
1073741824 => "Deferred",
},
"laneToReactMeasureMap": Map {
1 => [],
2 => [],
4 => [],
8 => [],
16 => [],
32 => [
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 1,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 1,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render-idle",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "render",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "commit",
},
{
"batchUID": 2,
"depth": 1,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "layout-effects",
},
{
"batchUID": 2,
"depth": 0,
"duration": 0,
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "passive-effects",
},
],
64 => [],
128 => [],
256 => [],
512 => [],
1024 => [],
2048 => [],
4096 => [],
8192 => [],
16384 => [],
32768 => [],
65536 => [],
131072 => [],
262144 => [],
524288 => [],
1048576 => [],
2097152 => [],
4194304 => [],
8388608 => [],
16777216 => [],
33554432 => [],
67108864 => [],
134217728 => [],
268435456 => [],
536870912 => [],
1073741824 => [],
},
"nativeEvents": [],
"networkMeasures": [],
"otherUserTimingMarks": [],
"reactVersion": "<filtered-version>",
"schedulingEvents": [
{
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "schedule-render",
"warning": null,
},
{
"componentName": "App",
"componentStack": "
in App (at **)",
"lanes": "0b0000000000000000000000000100000",
"timestamp": 10,
"type": "schedule-state-update",
"warning": null,
},
],
"snapshotHeight": 0,
"snapshots": [],
"startTime": -10,
"suspenseEvents": [],
"thrownErrors": [],
}
`);
});
});
});