diff --git a/packages/react-devtools-shared/src/backend/agent.js b/packages/react-devtools-shared/src/backend/agent.js index 5c4a510948a08..ac155fc3851bb 100644 --- a/packages/react-devtools-shared/src/backend/agent.js +++ b/packages/react-devtools-shared/src/backend/agent.js @@ -737,17 +737,19 @@ export default class Agent extends EventEmitter<{ this.emit('shutdown'); }; - startProfiling: (recordChangeDescriptions: boolean) => void = - recordChangeDescriptions => { - this._isProfiling = true; - for (const rendererID in this._rendererInterfaces) { - const renderer = ((this._rendererInterfaces[ - (rendererID: any) - ]: any): RendererInterface); - renderer.startProfiling(recordChangeDescriptions); - } - this._bridge.send('profilingStatus', this._isProfiling); - }; + startProfiling: ( + recordChangeDescriptions: boolean, + recordTimeline: boolean, + ) => void = (recordChangeDescriptions, recordTimeline) => { + this._isProfiling = true; + for (const rendererID in this._rendererInterfaces) { + const renderer = ((this._rendererInterfaces[ + (rendererID: any) + ]: any): RendererInterface); + renderer.startProfiling(recordChangeDescriptions, recordTimeline); + } + this._bridge.send('profilingStatus', this._isProfiling); + }; stopProfiling: () => void = () => { this._isProfiling = false; diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 103c0b0d71327..31c20b95a0341 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -5176,7 +5176,10 @@ export function attach( } } - function startProfiling(shouldRecordChangeDescriptions: boolean) { + function startProfiling( + shouldRecordChangeDescriptions: boolean, + recordTimeline: boolean, + ) { if (isProfiling) { return; } @@ -5212,7 +5215,7 @@ export function attach( rootToCommitProfilingMetadataMap = new Map(); if (toggleProfilingStatus !== null) { - toggleProfilingStatus(true); + toggleProfilingStatus(true, profilingSettings.recordTimeline); } } @@ -5221,13 +5224,16 @@ export function attach( recordChangeDescriptions = false; if (toggleProfilingStatus !== null) { - toggleProfilingStatus(false); + toggleProfilingStatus(false, profilingSettings.recordTimeline); } } // Automatically start profiling so that we don't miss timing info from initial "mount". if (shouldStartProfilingRightNow) { - startProfiling(profilingSettings.recordChangeDescriptions); + startProfiling( + profilingSettings.recordChangeDescriptions, + profilingSettings.recordTimeline, + ); } function getNearestFiber(devtoolsInstance: DevToolsInstance): null | Fiber { diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js index 47a01035308e2..a8111713c9fb2 100644 --- a/packages/react-devtools-shared/src/backend/profilingHooks.js +++ b/packages/react-devtools-shared/src/backend/profilingHooks.js @@ -97,7 +97,10 @@ export function setPerformanceMock_ONLY_FOR_TESTING( } export type GetTimelineData = () => TimelineData | null; -export type ToggleProfilingStatus = (value: boolean) => void; +export type ToggleProfilingStatus = ( + value: boolean, + recordTimeline?: boolean, +) => void; type Response = { getTimelineData: GetTimelineData, @@ -839,7 +842,10 @@ export function createProfilingHooks({ } } - function toggleProfilingStatus(value: boolean) { + function toggleProfilingStatus( + value: boolean, + recordTimeline: boolean = false, + ) { if (isProfiling !== value) { isProfiling = value; @@ -875,34 +881,45 @@ export function createProfilingHooks({ currentReactComponentMeasure = null; currentReactMeasuresStack = []; currentFiberStacks = new Map(); - currentTimelineData = { - // Session wide metadata; only collected once. - internalModuleSourceToRanges, - laneToLabelMap: laneToLabelMap || new Map(), - reactVersion, - - // Data logged by React during profiling session. - componentMeasures: [], - schedulingEvents: [], - suspenseEvents: [], - thrownErrors: [], - - // Data inferred based on what React logs. - batchUIDToMeasuresMap: new Map(), - duration: 0, - laneToReactMeasureMap, - startTime: 0, - - // Data only available in Chrome profiles. - flamechart: [], - nativeEvents: [], - networkMeasures: [], - otherUserTimingMarks: [], - snapshots: [], - snapshotHeight: 0, - }; + if (recordTimeline) { + currentTimelineData = { + // Session wide metadata; only collected once. + internalModuleSourceToRanges, + laneToLabelMap: laneToLabelMap || new Map(), + reactVersion, + + // Data logged by React during profiling session. + componentMeasures: [], + schedulingEvents: [], + suspenseEvents: [], + thrownErrors: [], + + // Data inferred based on what React logs. + batchUIDToMeasuresMap: new Map(), + duration: 0, + laneToReactMeasureMap, + startTime: 0, + + // Data only available in Chrome profiles. + flamechart: [], + nativeEvents: [], + networkMeasures: [], + otherUserTimingMarks: [], + snapshots: [], + snapshotHeight: 0, + }; + } nextRenderShouldStartNewBatch = true; } else { + // This is __EXPENSIVE__. + // We could end up with hundreds of state updated, and for each one of them + // would try to create a component stack with possibly hundreds of Fibers. + // Creating a cache of component stacks won't help, generating a single stack is already expensive enough. + // We should find a way to lazily generate component stacks on demand, when user inspects a specific event. + // If we succeed with moving React DevTools Timeline Profiler to Performance panel, then Timeline Profiler would probably be removed. + // If not, then once enableOwnerStacks is adopted, revisit this again and cache component stacks per Fiber, + // but only return them when needed, sending hundreds of component stacks is beyond the Bridge's bandwidth. + // Postprocess Profile data if (currentTimelineData !== null) { currentTimelineData.schedulingEvents.forEach(event => { diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 61546f2c289a6..9d9d9a8eb5e65 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -419,7 +419,10 @@ export type RendererInterface = { renderer: ReactRenderer | null, setTraceUpdatesEnabled: (enabled: boolean) => void, setTrackedPath: (path: Array | null) => void, - startProfiling: (recordChangeDescriptions: boolean) => void, + startProfiling: ( + recordChangeDescriptions: boolean, + recordTimeline: boolean, + ) => void, stopProfiling: () => void, storeAsGlobal: ( id: number, @@ -487,6 +490,7 @@ export type DevToolsBackend = { export type ProfilingSettings = { recordChangeDescriptions: boolean, + recordTimeline: boolean, }; export type DevToolsHook = { diff --git a/packages/react-devtools-shared/src/bridge.js b/packages/react-devtools-shared/src/bridge.js index dde6e7c3ffff8..854b8d9601cdb 100644 --- a/packages/react-devtools-shared/src/bridge.js +++ b/packages/react-devtools-shared/src/bridge.js @@ -232,7 +232,7 @@ type FrontendEvents = { setTraceUpdatesEnabled: [boolean], shutdown: [], startInspectingHost: [], - startProfiling: [boolean], + startProfiling: [boolean, boolean], stopInspectingHost: [boolean], stopProfiling: [], storeAsGlobal: [StoreAsGlobalParams], diff --git a/packages/react-devtools-shared/src/devtools/ProfilerStore.js b/packages/react-devtools-shared/src/devtools/ProfilerStore.js index 5ed7e69f294b9..30b3c9035a83f 100644 --- a/packages/react-devtools-shared/src/devtools/ProfilerStore.js +++ b/packages/react-devtools-shared/src/devtools/ProfilerStore.js @@ -191,7 +191,11 @@ export default class ProfilerStore extends EventEmitter<{ } startProfiling(): void { - this._bridge.send('startProfiling', this._store.recordChangeDescriptions); + this._bridge.send( + 'startProfiling', + this._store.recordChangeDescriptions, + this._store.supportsTimeline, + ); this._isProfilingBasedOnUserInput = true; this.emit('isProfiling'); diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index bca5d4142d29e..0d702947b4566 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -52,6 +52,7 @@ const targetConsole: Object = console; const defaultProfilingSettings: ProfilingSettings = { recordChangeDescriptions: false, + recordTimeline: false, }; export function installHook(