diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 0af7ffb767bfb..fb2c7347010b6 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -532,10 +532,10 @@ export function createHostRootFiber( mode = NoMode; } - if (enableProfilerTimer && isDevToolsPresent) { - // Always collect profile timings when DevTools are present. - // This enables DevTools to start capturing timing at any point– - // Without some nodes in the tree having empty base times. + if (__DEV__ || (enableProfilerTimer && isDevToolsPresent)) { + // dev: Enable profiling instrumentation by default. + // profile: enabled if DevTools is present or subtree is wrapped in . + // production: disabled. mode |= ProfileMode; } diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 22a80d8dbe977..1fae90f07b1af 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -179,7 +179,7 @@ const classComponentUpdater = { const root = enqueueUpdate(fiber, update, lane); if (root !== null) { - startUpdateTimerByLane(lane, 'this.setState()'); + startUpdateTimerByLane(lane, 'this.setState()', fiber); scheduleUpdateOnFiber(root, fiber, lane); entangleTransitions(root, fiber, lane); } @@ -205,7 +205,7 @@ const classComponentUpdater = { const root = enqueueUpdate(fiber, update, lane); if (root !== null) { - startUpdateTimerByLane(lane, 'this.replaceState()'); + startUpdateTimerByLane(lane, 'this.replaceState()', fiber); scheduleUpdateOnFiber(root, fiber, lane); entangleTransitions(root, fiber, lane); } @@ -231,7 +231,7 @@ const classComponentUpdater = { const root = enqueueUpdate(fiber, update, lane); if (root !== null) { - startUpdateTimerByLane(lane, 'this.forceUpdate()'); + startUpdateTimerByLane(lane, 'this.forceUpdate()', fiber); scheduleUpdateOnFiber(root, fiber, lane); entangleTransitions(root, fiber, lane); } diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index ca62bf5a718a5..66a390ebb8150 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -1866,7 +1866,7 @@ function subscribeToStore( // read from the store. if (checkIfSnapshotChanged(inst)) { // Force a re-render. - startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()'); + startUpdateTimerByLane(SyncLane, 'updateSyncExternalStore()', fiber); forceStoreRerender(fiber); } }; @@ -3518,7 +3518,7 @@ function refreshCache(fiber: Fiber, seedKey: ?() => T, seedValue: T): void { const refreshUpdate = createLegacyQueueUpdate(lane); const root = enqueueLegacyQueueUpdate(provider, refreshUpdate, lane); if (root !== null) { - startUpdateTimerByLane(lane, 'refresh()'); + startUpdateTimerByLane(lane, 'refresh()', fiber); scheduleUpdateOnFiber(root, provider, lane); entangleLegacyQueueTransitions(root, provider, lane); } @@ -3587,7 +3587,7 @@ function dispatchReducerAction( } else { const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); if (root !== null) { - startUpdateTimerByLane(lane, 'dispatch()'); + startUpdateTimerByLane(lane, 'dispatch()', fiber); scheduleUpdateOnFiber(root, fiber, lane); entangleTransitionUpdate(root, queue, lane); } @@ -3621,7 +3621,7 @@ function dispatchSetState( lane, ); if (didScheduleUpdate) { - startUpdateTimerByLane(lane, 'setState()'); + startUpdateTimerByLane(lane, 'setState()', fiber); } markUpdateInDevTools(fiber, lane, action); } @@ -3783,7 +3783,7 @@ function dispatchOptimisticSetState( // will never be attempted before the optimistic update. This currently // holds because the optimistic update is always synchronous. If we ever // change that, we'll need to account for this. - startUpdateTimerByLane(lane, 'setOptimistic()'); + startUpdateTimerByLane(lane, 'setOptimistic()', fiber); scheduleUpdateOnFiber(root, fiber, lane); // Optimistic updates are always synchronous, so we don't need to call // entangleTransitionUpdate here. diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 8e86a85476965..86f96d5d079aa 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -631,6 +631,8 @@ export function logBlockingStart( renderStartTime: number, lanes: Lanes, debugTask: null | ConsoleTask, // DEV-only + updateMethodName: null | string, + updateComponentName: null | string, ): void { if (supportsUserTiming) { currentTrack = 'Blocking'; @@ -672,34 +674,46 @@ export function logBlockingStart( : includesOnlyHydrationOrOffscreenLanes(lanes) ? 'tertiary-light' : 'primary-light'; - if (__DEV__ && debugTask) { - debugTask.run( - // $FlowFixMe[method-unbinding] - console.timeStamp.bind( - console, - isPingedUpdate - ? 'Promise Resolved' - : isSpawnedUpdate - ? 'Cascading Update' - : renderStartTime - updateTime > 5 - ? 'Update Blocked' - : 'Update', - updateTime, - renderStartTime, - currentTrack, - LANES_TRACK_GROUP, - color, - ), - ); + const label = isPingedUpdate + ? 'Promise Resolved' + : isSpawnedUpdate + ? 'Cascading Update' + : renderStartTime - updateTime > 5 + ? 'Update Blocked' + : 'Update'; + + if (__DEV__) { + const properties = []; + if (updateComponentName != null) { + properties.push(['Component name', updateComponentName]); + } + if (updateMethodName != null) { + properties.push(['Method name', updateMethodName]); + } + const measureOptions = { + start: updateTime, + end: renderStartTime, + detail: { + devtools: { + properties, + track: currentTrack, + trackGroup: LANES_TRACK_GROUP, + color, + }, + }, + }; + + if (debugTask) { + debugTask.run( + // $FlowFixMe[method-unbinding] + performance.measure.bind(performance, label, measureOptions), + ); + } else { + performance.measure(label, measureOptions); + } } else { console.timeStamp( - isPingedUpdate - ? 'Promise Resolved' - : isSpawnedUpdate - ? 'Cascading Update' - : renderStartTime - updateTime > 5 - ? 'Update Blocked' - : 'Update', + label, updateTime, renderStartTime, currentTrack, @@ -720,6 +734,8 @@ export function logTransitionStart( isPingedUpdate: boolean, renderStartTime: number, debugTask: null | ConsoleTask, // DEV-only + updateMethodName: null | string, + updateComponentName: null | string, ): void { if (supportsUserTiming) { currentTrack = 'Transition'; @@ -781,30 +797,43 @@ export function logTransitionStart( } if (updateTime > 0 && renderStartTime > updateTime) { // Log the time from when we called setState until we started rendering. - if (__DEV__ && debugTask) { - debugTask.run( - // $FlowFixMe[method-unbinding] - console.timeStamp.bind( - console, - isPingedUpdate - ? 'Promise Resolved' - : renderStartTime - updateTime > 5 - ? 'Update Blocked' - : 'Update', - updateTime, - renderStartTime, - currentTrack, - LANES_TRACK_GROUP, - 'primary-light', - ), - ); + const label = isPingedUpdate + ? 'Promise Resolved' + : renderStartTime - updateTime > 5 + ? 'Update Blocked' + : 'Update'; + if (__DEV__) { + const properties = []; + if (updateComponentName != null) { + properties.push(['Component name', updateComponentName]); + } + if (updateMethodName != null) { + properties.push(['Method name', updateMethodName]); + } + const measureOptions = { + start: updateTime, + end: renderStartTime, + detail: { + devtools: { + properties, + track: currentTrack, + trackGroup: LANES_TRACK_GROUP, + color: 'primary-light', + }, + }, + }; + + if (debugTask) { + debugTask.run( + // $FlowFixMe[method-unbinding] + performance.measure.bind(performance, label, measureOptions), + ); + } else { + performance.measure(label, measureOptions); + } } else { console.timeStamp( - isPingedUpdate - ? 'Promise Resolved' - : renderStartTime - updateTime > 5 - ? 'Update Blocked' - : 'Update', + label, updateTime, renderStartTime, currentTrack, diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 14764c0dcb7e9..3b4d321416b98 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -346,7 +346,7 @@ export function createHydrationContainer( update.callback = callback !== undefined && callback !== null ? callback : null; enqueueUpdate(current, update, lane); - startUpdateTimerByLane(lane, 'hydrateRoot()'); + startUpdateTimerByLane(lane, 'hydrateRoot()', null); scheduleInitialHydrationOnRoot(root, lane); return root; @@ -453,7 +453,7 @@ function updateContainerImpl( const root = enqueueUpdate(rootFiber, update, lane); if (root !== null) { - startUpdateTimerByLane(lane, 'root.render()'); + startUpdateTimerByLane(lane, 'root.render()', null); scheduleUpdateOnFiber(root, rootFiber, lane); entangleTransitions(root, rootFiber, lane); } diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index d1e826797c8cb..00c8ce91c1a75 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -267,6 +267,8 @@ import { blockingUpdateTime, blockingUpdateTask, blockingUpdateType, + blockingUpdateMethodName, + blockingUpdateComponentName, blockingEventTime, blockingEventType, blockingEventIsRepeat, @@ -276,6 +278,8 @@ import { transitionUpdateTime, transitionUpdateTask, transitionUpdateType, + transitionUpdateMethodName, + transitionUpdateComponentName, transitionEventTime, transitionEventType, transitionEventIsRepeat, @@ -1940,6 +1944,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { renderStartTime, lanes, blockingUpdateTask, + blockingUpdateMethodName, + blockingUpdateComponentName, ); clearBlockingTimers(); } @@ -1980,6 +1986,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { transitionUpdateType === PINGED_UPDATE, renderStartTime, transitionUpdateTask, + transitionUpdateMethodName, + transitionUpdateComponentName, ); clearTransitionTimers(); } diff --git a/packages/react-reconciler/src/ReactProfilerTimer.js b/packages/react-reconciler/src/ReactProfilerTimer.js index 4768ffffb5635..a1779235fc02b 100644 --- a/packages/react-reconciler/src/ReactProfilerTimer.js +++ b/packages/react-reconciler/src/ReactProfilerTimer.js @@ -33,6 +33,7 @@ import { enableComponentPerformanceTrack, } from 'shared/ReactFeatureFlags'; +import getComponentNameFromFiber from './getComponentNameFromFiber'; import {isAlreadyRendering} from './ReactFiberWorkLoop'; // Intentionally not named imports because Rollup would use dynamic dispatch for @@ -68,6 +69,8 @@ export let blockingClampTime: number = -0; export let blockingUpdateTime: number = -1.1; // First sync setState scheduled. export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace. export let blockingUpdateType: UpdateType = 0; +export let blockingUpdateMethodName: null | string = null; // The name of the method that caused first sync update. +export let blockingUpdateComponentName: null | string = null; // The name of the component where first sync update happened. export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState. export let blockingEventType: null | string = null; // Event type of the first setState. export let blockingEventIsRepeat: boolean = false; @@ -78,6 +81,8 @@ export let transitionStartTime: number = -1.1; // First startTransition call bef export let transitionUpdateTime: number = -1.1; // First transition setState scheduled. export let transitionUpdateType: UpdateType = 0; export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace. +export let transitionUpdateMethodName: null | string = null; // The name of the method that caused first transition update. +export let transitionUpdateComponentName: null | string = null; // The name of the component where first transition update happened. export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition. export let transitionEventType: null | string = null; // Event type of the first transition. export let transitionEventIsRepeat: boolean = false; @@ -94,7 +99,11 @@ export function startYieldTimer(reason: SuspendedReason) { yieldReason = reason; } -export function startUpdateTimerByLane(lane: Lane, method: string): void { +export function startUpdateTimerByLane( + lane: Lane, + method: string, + fiber: Fiber | null, +): void { if (!enableProfilerTimer || !enableComponentPerformanceTrack) { return; } @@ -102,6 +111,10 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void { if (blockingUpdateTime < 0) { blockingUpdateTime = now(); blockingUpdateTask = createTask(method); + blockingUpdateMethodName = method; + if (__DEV__ && fiber != null) { + blockingUpdateComponentName = getComponentNameFromFiber(fiber); + } if (isAlreadyRendering()) { blockingUpdateType = SPAWNED_UPDATE; } @@ -125,6 +138,10 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void { if (transitionUpdateTime < 0) { transitionUpdateTime = now(); transitionUpdateTask = createTask(method); + transitionUpdateMethodName = method; + if (__DEV__ && fiber != null) { + transitionUpdateComponentName = getComponentNameFromFiber(fiber); + } if (transitionStartTime < 0) { const newEventTime = resolveEventTimeStamp(); const newEventType = resolveEventType(); @@ -225,6 +242,8 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) { export function clearBlockingTimers(): void { blockingUpdateTime = -1.1; blockingUpdateType = 0; + blockingUpdateMethodName = null; + blockingUpdateComponentName = null; blockingSuspendedTime = -1.1; blockingEventIsRepeat = true; } diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index d51b037c18e4c..a8c00b2b562dc 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -125,6 +125,7 @@ describe(`onRender`, () => { expect(callback).toHaveBeenCalledTimes(1); }); + // @gate !__DEV__ it('does not record times for components outside of Profiler tree', async () => { // Mock the Scheduler module so we can track how many times the current // time is read