diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js index 6861e1aac33..f7047d632bb 100644 --- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js +++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js @@ -85,6 +85,8 @@ describe('Timeline profiler', () => { markOptions.startTime++; } }, + measure() {}, + clearMeasures() {}, }; } @@ -364,9 +366,10 @@ describe('Timeline profiler', () => { "--render-start-128", "--component-render-start-Foo", "--component-render-stop", - "--render-yield", + "--render-yield-stop", ] `); + await waitForPaint(['Bar']); }); it('should mark concurrent render with suspense that resolves', async () => { diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js index 06595b67ca1..4ac95b687e8 100644 --- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js +++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js @@ -24,7 +24,7 @@ describe('Timeline profiler', () => { let utils; let assertLog; let waitFor; - + let waitForPaint; describe('User Timing API', () => { let currentlyNotClearedMarks; let registeredMarks; @@ -75,6 +75,8 @@ describe('Timeline profiler', () => { markOptions.startTime++; } }, + measure() {}, + clearMeasures() {}, }; } @@ -101,7 +103,7 @@ describe('Timeline profiler', () => { const InternalTestUtils = require('internal-test-utils'); assertLog = InternalTestUtils.assertLog; waitFor = InternalTestUtils.waitFor; - + waitForPaint = InternalTestUtils.waitForPaint; setPerformanceMock = require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING; setPerformanceMock(createUserTimingPolyfill()); @@ -1301,6 +1303,8 @@ describe('Timeline profiler', () => { const data = await preprocessData(testMarks); const event = data.nativeEvents.find(({type}) => type === 'click'); expect(event.warning).toBe(null); + + await waitForPaint([]); }); // @reactVersion >= 18.0 diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js index 17e6181abe9..3f47f715d84 100644 --- a/packages/react-devtools-shared/src/backend/profilingHooks.js +++ b/packages/react-devtools-shared/src/backend/profilingHooks.js @@ -123,6 +123,7 @@ export function createProfilingHooks({ let currentBatchUID: BatchUID = 0; let currentReactComponentMeasure: ReactComponentMeasure | null = null; let currentReactMeasuresStack: Array = []; + const currentBeginMarksStack: Array = []; let currentTimelineData: TimelineData | null = null; let currentFiberStacks: Map> = new Map(); let isProfiling: boolean = false; @@ -214,6 +215,56 @@ export function createProfilingHooks({ ((performanceTarget: any): Performance).clearMarks(markName); } + function beginMark(taskName: string, ending: string | number) { + // This name format is used in preprocessData.js so it cannot just be changed. + const startMarkName = `--${taskName}-start-${ending}`; + currentBeginMarksStack.push(startMarkName); + // This method won't be called unless these functions are defined, so we can skip the extra typeof check. + ((performanceTarget: any): Performance).mark(startMarkName); + } + + function endMarkAndClear(taskName: string) { + const startMarkName = currentBeginMarksStack.pop(); + if (!startMarkName) { + console.error( + 'endMarkAndClear was unexpectedly called without a corresponding start mark', + ); + return; + } + const markEnding = startMarkName.split('-').at(-1) || ''; + const endMarkName = `--${taskName}-stop`; + const measureName = `${taskName} ${markEnding}`; + + // Use different color for rendering tasks. + const color = taskName.includes('render') ? 'primary' : 'tertiary'; + // If the ending is not a number, then it's a component name. + const properties = isNaN(parseInt(markEnding, 10)) + ? [['Component', markEnding]] + : []; + // This method won't be called unless these functions are defined, so we can skip the extra typeof check. + ((performanceTarget: any): Performance).mark(endMarkName); + // Based on the format in https://bit.ly/rpp-e11y + const measureOptions = { + start: startMarkName, + end: endMarkName, + detail: { + devtools: { + dataType: 'track-entry', + color, + track: '⚛️ React', + properties, + }, + }, + }; + ((performanceTarget: any): Performance).measure( + measureName, + measureOptions, + ); + ((performanceTarget: any): Performance).clearMarks(startMarkName); + ((performanceTarget: any): Performance).clearMarks(endMarkName); + ((performanceTarget: any): Performance).clearMeasures(measureName); + } + function recordReactMeasureStarted( type: ReactMeasureType, lanes: Lanes, @@ -301,7 +352,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear(`--commit-start-${lanes}`); + beginMark('commit', lanes); // Some metadata only needs to be logged once per session, // but if profiling information is being recorded via the Performance tab, @@ -318,7 +369,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--commit-stop'); + endMarkAndClear('commit'); } } @@ -340,7 +391,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear(`--component-render-start-${componentName}`); + beginMark('component-render', componentName); } } } @@ -361,9 +412,8 @@ export function createProfilingHooks({ currentReactComponentMeasure = null; } } - if (supportsUserTimingV3) { - markAndClear('--component-render-stop'); + endMarkAndClear('component-render'); } } @@ -385,7 +435,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear(`--component-layout-effect-mount-start-${componentName}`); + beginMark('component-layout-effect-mount', componentName); } } } @@ -408,7 +458,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--component-layout-effect-mount-stop'); + endMarkAndClear('component-layout-effect-mount'); } } @@ -430,9 +480,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear( - `--component-layout-effect-unmount-start-${componentName}`, - ); + beginMark('component-layout-effect-unmount', componentName); } } } @@ -455,7 +503,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--component-layout-effect-unmount-stop'); + endMarkAndClear('component-layout-effect-unmount'); } } @@ -477,7 +525,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear(`--component-passive-effect-mount-start-${componentName}`); + beginMark('component-passive-effect-mount', componentName); } } } @@ -500,7 +548,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--component-passive-effect-mount-stop'); + endMarkAndClear('component-passive-effect-mount'); } } @@ -522,9 +570,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear( - `--component-passive-effect-unmount-start-${componentName}`, - ); + beginMark('component-passive-effect-unmount', componentName); } } } @@ -547,7 +593,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--component-passive-effect-unmount-stop'); + endMarkAndClear('component-passive-effect-unmount'); } } @@ -679,7 +725,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear(`--layout-effects-start-${lanes}`); + beginMark('layout-effects', lanes); } } @@ -689,7 +735,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--layout-effects-stop'); + endMarkAndClear('layout-effects'); } } @@ -699,7 +745,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear(`--passive-effects-start-${lanes}`); + beginMark('passive-effects', lanes); } } @@ -709,7 +755,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--passive-effects-stop'); + endMarkAndClear('passive-effects'); } } @@ -734,7 +780,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear(`--render-start-${lanes}`); + beginMark('render', lanes); } } @@ -744,7 +790,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--render-yield'); + endMarkAndClear('render-yield'); } } @@ -754,7 +800,7 @@ export function createProfilingHooks({ } if (supportsUserTimingV3) { - markAndClear('--render-stop'); + endMarkAndClear('render'); } }