From d6e3bf378d47928a7d744aebc567b480f33b8761 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 28 Jan 2022 17:24:07 -0500 Subject: [PATCH] Refactored Timeline in-memory profiling to better share code Moved the in-memory stack management out of 'profilingHooks.js' and into the new 'TimelineData.js'. Doing this even caught a small time offset bug (fixed). Next step will be to update 'preprocessData.js' to also use the new 'TimelineData.js' when parsing Trace Event data. --- .../src/__tests__/TimelineProfiler-test.js | 6 +- .../__snapshots__/profilingCache-test.js.snap | 600 ------------------ .../src/__tests__/preprocessData-test.js | 60 -- .../src/backend/profilingHooks.js | 542 +++++----------- .../src/backend/renderer.js | 28 +- .../src/TimelineData.js | 465 ++++++++++++++ packages/react-devtools-timeline/src/types.js | 8 +- 7 files changed, 650 insertions(+), 1059 deletions(-) create mode 100644 packages/react-devtools-timeline/src/TimelineData.js diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js index f010261a616e6..21eb6183351c0 100644 --- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js +++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js @@ -1202,7 +1202,7 @@ describe('Timeline profiler', () => { `); }); - it('should mark concurrent render without suspends or state updates', () => { + it('should mark concurrent transitions without suspends or state updates', () => { let updaterFn; function Example() { @@ -1264,7 +1264,7 @@ describe('Timeline profiler', () => { Array [ Object { "componentName": "Example", - "duration": 0, + "duration": 10, "timestamp": 10, "type": "render", "warning": null, @@ -1272,7 +1272,7 @@ describe('Timeline profiler', () => { Object { "componentName": "Example", "duration": 10, - "timestamp": 10, + "timestamp": 20, "type": "render", "warning": null, }, diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap index 258560929435c..24930c6b92bd3 100644 --- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap +++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap @@ -1362,126 +1362,6 @@ Object { }, ], ], - Array [ - 2, - Array [], - ], - Array [ - 4, - Array [], - ], - Array [ - 8, - Array [], - ], - Array [ - 16, - Array [], - ], - Array [ - 32, - Array [], - ], - Array [ - 64, - Array [], - ], - Array [ - 128, - Array [], - ], - Array [ - 256, - Array [], - ], - Array [ - 512, - Array [], - ], - Array [ - 1024, - Array [], - ], - Array [ - 2048, - Array [], - ], - Array [ - 4096, - Array [], - ], - Array [ - 8192, - Array [], - ], - Array [ - 16384, - Array [], - ], - Array [ - 32768, - Array [], - ], - Array [ - 65536, - Array [], - ], - Array [ - 131072, - Array [], - ], - Array [ - 262144, - Array [], - ], - Array [ - 524288, - Array [], - ], - Array [ - 1048576, - Array [], - ], - Array [ - 2097152, - Array [], - ], - Array [ - 4194304, - Array [], - ], - Array [ - 8388608, - Array [], - ], - Array [ - 16777216, - Array [], - ], - Array [ - 33554432, - Array [], - ], - Array [ - 67108864, - Array [], - ], - Array [ - 134217728, - Array [], - ], - Array [ - 268435456, - Array [], - ], - Array [ - 536870912, - Array [], - ], - Array [ - 1073741824, - Array [], - ], ], "nativeEvents": Array [], "networkMeasures": Array [], @@ -2401,126 +2281,6 @@ Object { }, ], ], - Array [ - 2, - Array [], - ], - Array [ - 4, - Array [], - ], - Array [ - 8, - Array [], - ], - Array [ - 16, - Array [], - ], - Array [ - 32, - Array [], - ], - Array [ - 64, - Array [], - ], - Array [ - 128, - Array [], - ], - Array [ - 256, - Array [], - ], - Array [ - 512, - Array [], - ], - Array [ - 1024, - Array [], - ], - Array [ - 2048, - Array [], - ], - Array [ - 4096, - Array [], - ], - Array [ - 8192, - Array [], - ], - Array [ - 16384, - Array [], - ], - Array [ - 32768, - Array [], - ], - Array [ - 65536, - Array [], - ], - Array [ - 131072, - Array [], - ], - Array [ - 262144, - Array [], - ], - Array [ - 524288, - Array [], - ], - Array [ - 1048576, - Array [], - ], - Array [ - 2097152, - Array [], - ], - Array [ - 4194304, - Array [], - ], - Array [ - 8388608, - Array [], - ], - Array [ - 16777216, - Array [], - ], - Array [ - 33554432, - Array [], - ], - Array [ - 67108864, - Array [], - ], - Array [ - 134217728, - Array [], - ], - Array [ - 268435456, - Array [], - ], - Array [ - 536870912, - Array [], - ], - Array [ - 1073741824, - Array [], - ], ], "nativeEvents": Array [], "networkMeasures": Array [], @@ -4109,126 +3869,6 @@ Object { }, ], ], - Array [ - 2, - Array [], - ], - Array [ - 4, - Array [], - ], - Array [ - 8, - Array [], - ], - Array [ - 16, - Array [], - ], - Array [ - 32, - Array [], - ], - Array [ - 64, - Array [], - ], - Array [ - 128, - Array [], - ], - Array [ - 256, - Array [], - ], - Array [ - 512, - Array [], - ], - Array [ - 1024, - Array [], - ], - Array [ - 2048, - Array [], - ], - Array [ - 4096, - Array [], - ], - Array [ - 8192, - Array [], - ], - Array [ - 16384, - Array [], - ], - Array [ - 32768, - Array [], - ], - Array [ - 65536, - Array [], - ], - Array [ - 131072, - Array [], - ], - Array [ - 262144, - Array [], - ], - Array [ - 524288, - Array [], - ], - Array [ - 1048576, - Array [], - ], - Array [ - 2097152, - Array [], - ], - Array [ - 4194304, - Array [], - ], - Array [ - 8388608, - Array [], - ], - Array [ - 16777216, - Array [], - ], - Array [ - 33554432, - Array [], - ], - Array [ - 67108864, - Array [], - ], - Array [ - 134217728, - Array [], - ], - Array [ - 268435456, - Array [], - ], - Array [ - 536870912, - Array [], - ], - Array [ - 1073741824, - Array [], - ], ], "nativeEvents": Array [], "networkMeasures": Array [], @@ -5587,126 +5227,6 @@ Object { }, ], ], - Array [ - 2, - Array [], - ], - Array [ - 4, - Array [], - ], - Array [ - 8, - Array [], - ], - Array [ - 16, - Array [], - ], - Array [ - 32, - Array [], - ], - Array [ - 64, - Array [], - ], - Array [ - 128, - Array [], - ], - Array [ - 256, - Array [], - ], - Array [ - 512, - Array [], - ], - Array [ - 1024, - Array [], - ], - Array [ - 2048, - Array [], - ], - Array [ - 4096, - Array [], - ], - Array [ - 8192, - Array [], - ], - Array [ - 16384, - Array [], - ], - Array [ - 32768, - Array [], - ], - Array [ - 65536, - Array [], - ], - Array [ - 131072, - Array [], - ], - Array [ - 262144, - Array [], - ], - Array [ - 524288, - Array [], - ], - Array [ - 1048576, - Array [], - ], - Array [ - 2097152, - Array [], - ], - Array [ - 4194304, - Array [], - ], - Array [ - 8388608, - Array [], - ], - Array [ - 16777216, - Array [], - ], - Array [ - 33554432, - Array [], - ], - Array [ - 67108864, - Array [], - ], - Array [ - 134217728, - Array [], - ], - Array [ - 268435456, - Array [], - ], - Array [ - 536870912, - Array [], - ], - Array [ - 1073741824, - Array [], - ], ], "nativeEvents": Array [], "networkMeasures": Array [], @@ -7654,126 +7174,6 @@ Object { }, ], ], - Array [ - 2, - Array [], - ], - Array [ - 4, - Array [], - ], - Array [ - 8, - Array [], - ], - Array [ - 16, - Array [], - ], - Array [ - 32, - Array [], - ], - Array [ - 64, - Array [], - ], - Array [ - 128, - Array [], - ], - Array [ - 256, - Array [], - ], - Array [ - 512, - Array [], - ], - Array [ - 1024, - Array [], - ], - Array [ - 2048, - Array [], - ], - Array [ - 4096, - Array [], - ], - Array [ - 8192, - Array [], - ], - Array [ - 16384, - Array [], - ], - Array [ - 32768, - Array [], - ], - Array [ - 65536, - Array [], - ], - Array [ - 131072, - Array [], - ], - Array [ - 262144, - Array [], - ], - Array [ - 524288, - Array [], - ], - Array [ - 1048576, - Array [], - ], - Array [ - 2097152, - Array [], - ], - Array [ - 4194304, - Array [], - ], - Array [ - 8388608, - Array [], - ], - Array [ - 16777216, - Array [], - ], - Array [ - 33554432, - Array [], - ], - Array [ - 67108864, - Array [], - ], - Array [ - 134217728, - Array [], - ], - Array [ - 268435456, - Array [], - ], - Array [ - 536870912, - Array [], - ], - Array [ - 1073741824, - Array [], - ], ], "nativeEvents": Array [], "networkMeasures": Array [], diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js index 0d5ebff509b90..97d60e637c95e 100644 --- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js +++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js @@ -2008,36 +2008,6 @@ describe('Timeline profiler', () => { "type": "layout-effects", }, ], - 2 => Array [], - 4 => Array [], - 8 => Array [], - 16 => Array [], - 32 => Array [], - 64 => Array [], - 128 => Array [], - 256 => Array [], - 512 => Array [], - 1024 => Array [], - 2048 => Array [], - 4096 => Array [], - 8192 => Array [], - 16384 => Array [], - 32768 => Array [], - 65536 => Array [], - 131072 => Array [], - 262144 => Array [], - 524288 => Array [], - 1048576 => Array [], - 2097152 => Array [], - 4194304 => Array [], - 8388608 => Array [], - 16777216 => Array [], - 33554432 => Array [], - 67108864 => Array [], - 134217728 => Array [], - 268435456 => Array [], - 536870912 => Array [], - 1073741824 => Array [], }, "nativeEvents": Array [], "networkMeasures": Array [], @@ -2233,10 +2203,6 @@ describe('Timeline profiler', () => { 1073741824 => "Offscreen", }, "laneToReactMeasureMap": Map { - 1 => Array [], - 2 => Array [], - 4 => Array [], - 8 => Array [], 16 => Array [ Object { "batchUID": 1, @@ -2319,32 +2285,6 @@ describe('Timeline profiler', () => { "type": "passive-effects", }, ], - 32 => Array [], - 64 => Array [], - 128 => Array [], - 256 => Array [], - 512 => Array [], - 1024 => Array [], - 2048 => Array [], - 4096 => Array [], - 8192 => Array [], - 16384 => Array [], - 32768 => Array [], - 65536 => Array [], - 131072 => Array [], - 262144 => Array [], - 524288 => Array [], - 1048576 => Array [], - 2097152 => Array [], - 4194304 => Array [], - 8388608 => Array [], - 16777216 => Array [], - 33554432 => Array [], - 67108864 => Array [], - 134217728 => Array [], - 268435456 => Array [], - 536870912 => Array [], - 1073741824 => Array [], }, "nativeEvents": Array [], "networkMeasures": Array [], diff --git a/packages/react-devtools-shared/src/backend/profilingHooks.js b/packages/react-devtools-shared/src/backend/profilingHooks.js index c02d08ca92320..70b87778c8035 100644 --- a/packages/react-devtools-shared/src/backend/profilingHooks.js +++ b/packages/react-devtools-shared/src/backend/profilingHooks.js @@ -14,26 +14,16 @@ import type { } from 'react-devtools-shared/src/backend/types'; import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type {Wakeable} from 'shared/ReactTypes'; -import type { - BatchUID, - LaneToLabelMap, - ReactComponentMeasure, - ReactMeasure, - ReactMeasureType, - TimelineData, - SuspenseEvent, -} from 'react-devtools-timeline/src/types'; +import type {LaneToLabelMap} from 'react-devtools-timeline/src/types'; +import type {RecordComponentSuspendedCallback} from 'react-devtools-timeline/src/TimelineData'; +import TimelineData from 'react-devtools-timeline/src/TimelineData'; import isArray from 'shared/isArray'; import { REACT_TOTAL_NUM_LANES, SCHEDULING_PROFILER_VERSION, } from 'react-devtools-timeline/src/constants'; -// Add padding to the start/stop time of the profile. -// This makes the UI nicer to use. -const TIME_OFFSET = 10; - let performanceTarget: Performance | null = null; // If performance exists and supports the subset of the User Timing API that we require. @@ -105,25 +95,19 @@ export function createProfilingHooks({ getLaneLabelMap?: () => Map | null, reactVersion: string, |}): Response { - let currentBatchUID: BatchUID = 0; - let currentReactComponentMeasure: ReactComponentMeasure | null = null; - let currentReactMeasuresStack: Array = []; let currentTimelineData: TimelineData | null = null; let isProfiling: boolean = false; - let nextRenderShouldStartNewBatch: boolean = false; - function getRelativeTime() { - const currentTime = getCurrentTime(); - - if (currentTimelineData) { - if (currentTimelineData.startTime === 0) { - currentTimelineData.startTime = currentTime - TIME_OFFSET; - } + const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; - return currentTime - currentTimelineData.startTime; + // $FlowFixMe: Flow cannot handle polymorphic WeakMaps + const wakeableIDs: WeakMap = new PossiblyWeakMap(); + let wakeableIDCounter: number = 0; + function getWakeableID(wakeable: Wakeable): number { + if (!wakeableIDs.has(wakeable)) { + wakeableIDs.set(wakeable, wakeableIDCounter++); } - - return 0; + return ((wakeableIDs.get(wakeable): any): number); } function getInternalModuleRanges() { @@ -198,92 +182,12 @@ export function createProfilingHooks({ ((performanceTarget: any): Performance).clearMarks(markName); } - function recordReactMeasureStarted( - type: ReactMeasureType, - lanes: Lanes, - ): void { - // Decide what depth thi work should be rendered at, based on what's on the top of the stack. - // It's okay to render over top of "idle" work but everything else should be on its own row. - let depth = 0; - if (currentReactMeasuresStack.length > 0) { - const top = - currentReactMeasuresStack[currentReactMeasuresStack.length - 1]; - depth = top.type === 'render-idle' ? top.depth : top.depth + 1; - } - - const lanesArray = laneToLanesArray(lanes); - - const reactMeasure: ReactMeasure = { - type, - batchUID: currentBatchUID, - depth, - lanes: lanesArray, - timestamp: getRelativeTime(), - duration: 0, - }; - - currentReactMeasuresStack.push(reactMeasure); - - if (currentTimelineData) { - const { - batchUIDToMeasuresMap, - laneToReactMeasureMap, - } = currentTimelineData; - - let reactMeasures = batchUIDToMeasuresMap.get(currentBatchUID); - if (reactMeasures != null) { - reactMeasures.push(reactMeasure); - } else { - batchUIDToMeasuresMap.set(currentBatchUID, [reactMeasure]); - } - - lanesArray.forEach(lane => { - reactMeasures = laneToReactMeasureMap.get(lane); - if (reactMeasures) { - reactMeasures.push(reactMeasure); - } - }); - } - } - - function recordReactMeasureCompleted(type: ReactMeasureType): void { - const currentTime = getRelativeTime(); - - if (currentReactMeasuresStack.length === 0) { - console.error( - 'Unexpected type "%s" completed at %sms while currentReactMeasuresStack is empty.', - type, - currentTime, - ); - // Ignore work "completion" user timing mark that doesn't complete anything - return; - } - - const top = currentReactMeasuresStack.pop(); - if (top.type !== type) { - console.error( - 'Unexpected type "%s" completed at %sms before "%s" completed.', - type, - currentTime, - top.type, - ); - } - - // $FlowFixMe This property should not be writable outside of this function. - top.duration = currentTime - top.timestamp; - - if (currentTimelineData) { - currentTimelineData.duration = getRelativeTime() + TIME_OFFSET; - } - } - function markCommitStarted(lanes: Lanes): void { - if (isProfiling) { - recordReactMeasureStarted('commit', lanes); - - // TODO (timeline) Re-think this approach to "batching"; I don't think it works for Suspense or pre-rendering. - // This issue applies to the User Timing data also. - nextRenderShouldStartNewBatch = true; + if (isProfiling && currentTimelineData) { + currentTimelineData.recordCommitStarted( + laneToLanesArray(lanes), + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -298,9 +202,8 @@ export function createProfilingHooks({ } function markCommitStopped(): void { - if (isProfiling) { - recordReactMeasureCompleted('commit'); - recordReactMeasureCompleted('render-idle'); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordCommitStopped(getCurrentTime()); } if (supportsUserTimingV3) { @@ -309,20 +212,15 @@ export function createProfilingHooks({ } function markComponentRenderStarted(fiber: Fiber): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (isProfiling) { - currentReactComponentMeasure = { - componentName, - duration: 0, - timestamp: getRelativeTime(), - type: 'render', - warning: null, - }; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentRenderStarted( + fiber, + componentName, + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -332,18 +230,8 @@ export function createProfilingHooks({ } function markComponentRenderStopped(): void { - if (isProfiling) { - if (currentReactComponentMeasure) { - if (currentTimelineData) { - currentTimelineData.componentMeasures.push( - currentReactComponentMeasure, - ); - } - - currentReactComponentMeasure.duration = - getRelativeTime() - currentReactComponentMeasure.timestamp; - currentReactComponentMeasure = null; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentRenderStopped(getCurrentTime()); } if (supportsUserTimingV3) { @@ -352,20 +240,15 @@ export function createProfilingHooks({ } function markComponentLayoutEffectMountStarted(fiber: Fiber): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (isProfiling) { - currentReactComponentMeasure = { - componentName, - duration: 0, - timestamp: getRelativeTime(), - type: 'layout-effect-mount', - warning: null, - }; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentLayoutEffectMountStarted( + fiber, + componentName, + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -375,18 +258,10 @@ export function createProfilingHooks({ } function markComponentLayoutEffectMountStopped(): void { - if (isProfiling) { - if (currentReactComponentMeasure) { - if (currentTimelineData) { - currentTimelineData.componentMeasures.push( - currentReactComponentMeasure, - ); - } - - currentReactComponentMeasure.duration = - getRelativeTime() - currentReactComponentMeasure.timestamp; - currentReactComponentMeasure = null; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentLayoutEffectMountStopped( + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -395,20 +270,15 @@ export function createProfilingHooks({ } function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (isProfiling) { - currentReactComponentMeasure = { - componentName, - duration: 0, - timestamp: getRelativeTime(), - type: 'layout-effect-unmount', - warning: null, - }; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentLayoutEffectUnmountStarted( + fiber, + componentName, + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -420,18 +290,10 @@ export function createProfilingHooks({ } function markComponentLayoutEffectUnmountStopped(): void { - if (isProfiling) { - if (currentReactComponentMeasure) { - if (currentTimelineData) { - currentTimelineData.componentMeasures.push( - currentReactComponentMeasure, - ); - } - - currentReactComponentMeasure.duration = - getRelativeTime() - currentReactComponentMeasure.timestamp; - currentReactComponentMeasure = null; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentLayoutEffectUnmountStopped( + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -440,20 +302,15 @@ export function createProfilingHooks({ } function markComponentPassiveEffectMountStarted(fiber: Fiber): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (isProfiling) { - currentReactComponentMeasure = { - componentName, - duration: 0, - timestamp: getRelativeTime(), - type: 'passive-effect-mount', - warning: null, - }; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentPassiveEffectMountStarted( + fiber, + componentName, + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -463,18 +320,10 @@ export function createProfilingHooks({ } function markComponentPassiveEffectMountStopped(): void { - if (isProfiling) { - if (currentReactComponentMeasure) { - if (currentTimelineData) { - currentTimelineData.componentMeasures.push( - currentReactComponentMeasure, - ); - } - - currentReactComponentMeasure.duration = - getRelativeTime() - currentReactComponentMeasure.timestamp; - currentReactComponentMeasure = null; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentPassiveEffectMountStopped( + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -483,20 +332,15 @@ export function createProfilingHooks({ } function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (isProfiling) { - currentReactComponentMeasure = { - componentName, - duration: 0, - timestamp: getRelativeTime(), - type: 'passive-effect-unmount', - warning: null, - }; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentPassiveEffectUnmountStarted( + fiber, + componentName, + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -508,18 +352,10 @@ export function createProfilingHooks({ } function markComponentPassiveEffectUnmountStopped(): void { - if (isProfiling) { - if (currentReactComponentMeasure) { - if (currentTimelineData) { - currentTimelineData.componentMeasures.push( - currentReactComponentMeasure, - ); - } - - currentReactComponentMeasure.duration = - getRelativeTime() - currentReactComponentMeasure.timestamp; - currentReactComponentMeasure = null; - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentPassiveEffectUnmountStopped( + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -532,7 +368,7 @@ export function createProfilingHooks({ thrownValue: mixed, lanes: Lanes, ): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; const phase = fiber.alternate === null ? 'mount' : 'update'; @@ -547,17 +383,15 @@ export function createProfilingHooks({ message = thrownValue; } - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (currentTimelineData) { - currentTimelineData.thrownErrors.push({ - componentName, - message, - phase, - timestamp: getRelativeTime(), - type: 'thrown-error', - }); - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordComponentErrored( + fiber, + message, + phase, + laneToLanesArray(lanes), + getDisplayNameForFiber(fiber) || 'Unknown', + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -566,26 +400,12 @@ export function createProfilingHooks({ } } - const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; - - // $FlowFixMe: Flow cannot handle polymorphic WeakMaps - const wakeableIDs: WeakMap = new PossiblyWeakMap(); - let wakeableID: number = 0; - function getWakeableID(wakeable: Wakeable): number { - if (!wakeableIDs.has(wakeable)) { - wakeableIDs.set(wakeable, wakeableID++); - } - return ((wakeableIDs.get(wakeable): any): number); - } - function markComponentSuspended( fiber: Fiber, wakeable: Wakeable, lanes: Lanes, ): void { - if (isProfiling || supportsUserTimingV3) { - const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend'; - const id = getWakeableID(wakeable); + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; const phase = fiber.alternate === null ? 'mount' : 'update'; @@ -593,56 +413,48 @@ export function createProfilingHooks({ // frameworks like Relay may also annotate Promises with a displayName, // describing what operation/data the thrown Promise is related to. // When this is available we should pass it along to the Timeline. - const displayName = (wakeable: any).displayName || ''; + const wakeableDisplayName = (wakeable: any).displayName || ''; - let suspenseEvent: SuspenseEvent | null = null; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - suspenseEvent = { + const eventType = wakeableIDs.has(wakeable) ? 'resuspend' : 'suspend'; + const wakeableID = getWakeableID(wakeable); + + let resolveOrRejectCallback: RecordComponentSuspendedCallback | null = null; + + if (isProfiling && currentTimelineData) { + resolveOrRejectCallback = currentTimelineData.recordComponentSuspended( + fiber, componentName, - depth: 0, - duration: 0, - id: `${id}`, phase, - promiseName: displayName, - resolution: 'unresolved', - timestamp: getRelativeTime(), - type: 'suspense', - warning: null, - }; - - if (currentTimelineData) { - currentTimelineData.suspenseEvents.push(suspenseEvent); - } + wakeableID, + wakeableDisplayName, + laneToLanesArray(lanes), + getCurrentTime(), + ); } if (supportsUserTimingV3) { markAndClear( - `--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`, + `--suspense-${eventType}-${wakeableID}-${componentName}-${phase}-${lanes}-${wakeableDisplayName}`, ); } wakeable.then( () => { - if (suspenseEvent) { - suspenseEvent.duration = - getRelativeTime() - suspenseEvent.timestamp; - suspenseEvent.resolution = 'resolved'; + if (resolveOrRejectCallback !== null) { + resolveOrRejectCallback('resolved', getCurrentTime()); } if (supportsUserTimingV3) { - markAndClear(`--suspense-resolved-${id}-${componentName}`); + markAndClear(`--suspense-resolved-${wakeableID}-${componentName}`); } }, () => { - if (suspenseEvent) { - suspenseEvent.duration = - getRelativeTime() - suspenseEvent.timestamp; - suspenseEvent.resolution = 'rejected'; + if (resolveOrRejectCallback !== null) { + resolveOrRejectCallback('rejected', getCurrentTime()); } if (supportsUserTimingV3) { - markAndClear(`--suspense-rejected-${id}-${componentName}`); + markAndClear(`--suspense-rejected-${wakeableID}-${componentName}`); } }, ); @@ -650,8 +462,11 @@ export function createProfilingHooks({ } function markLayoutEffectsStarted(lanes: Lanes): void { - if (isProfiling) { - recordReactMeasureStarted('layout-effects', lanes); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordLayoutEffectsStarted( + laneToLanesArray(lanes), + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -660,8 +475,8 @@ export function createProfilingHooks({ } function markLayoutEffectsStopped(): void { - if (isProfiling) { - recordReactMeasureCompleted('layout-effects'); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordLayoutEffectsStopped(getCurrentTime()); } if (supportsUserTimingV3) { @@ -670,8 +485,11 @@ export function createProfilingHooks({ } function markPassiveEffectsStarted(lanes: Lanes): void { - if (isProfiling) { - recordReactMeasureStarted('passive-effects', lanes); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordPassiveEffectsStarted( + laneToLanesArray(lanes), + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -680,8 +498,8 @@ export function createProfilingHooks({ } function markPassiveEffectsStopped(): void { - if (isProfiling) { - recordReactMeasureCompleted('passive-effects'); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordPassiveEffectsStopped(getCurrentTime()); } if (supportsUserTimingV3) { @@ -690,23 +508,11 @@ export function createProfilingHooks({ } function markRenderStarted(lanes: Lanes): void { - if (isProfiling) { - if (nextRenderShouldStartNewBatch) { - nextRenderShouldStartNewBatch = false; - currentBatchUID++; - } - - // If this is a new batch of work, wrap an "idle" measure around it. - // Log it before the "render" measure to preserve the stack ordering. - if ( - currentReactMeasuresStack.length === 0 || - currentReactMeasuresStack[currentReactMeasuresStack.length - 1].type !== - 'render-idle' - ) { - recordReactMeasureStarted('render-idle', lanes); - } - - recordReactMeasureStarted('render', lanes); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordRenderStarted( + laneToLanesArray(lanes), + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -715,8 +521,8 @@ export function createProfilingHooks({ } function markRenderYielded(): void { - if (isProfiling) { - recordReactMeasureCompleted('render'); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordRenderYielded(getCurrentTime()); } if (supportsUserTimingV3) { @@ -725,8 +531,8 @@ export function createProfilingHooks({ } function markRenderStopped(): void { - if (isProfiling) { - recordReactMeasureCompleted('render'); + if (isProfiling && currentTimelineData) { + currentTimelineData.recordRenderStopped(getCurrentTime()); } if (supportsUserTimingV3) { @@ -735,15 +541,11 @@ export function createProfilingHooks({ } function markRenderScheduled(lane: Lane): void { - if (isProfiling) { - if (currentTimelineData) { - currentTimelineData.schedulingEvents.push({ - lanes: laneToLanesArray(lane), - timestamp: getRelativeTime(), - type: 'schedule-render', - warning: null, - }); - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordRenderScheduled( + laneToLanesArray(lane), + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -752,20 +554,16 @@ export function createProfilingHooks({ } function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (currentTimelineData) { - currentTimelineData.schedulingEvents.push({ - componentName, - lanes: laneToLanesArray(lane), - timestamp: getRelativeTime(), - type: 'schedule-force-update', - warning: null, - }); - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordForceUpdateScheduled( + fiber, + componentName, + laneToLanesArray(lane), + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -775,20 +573,16 @@ export function createProfilingHooks({ } function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void { - if (isProfiling || supportsUserTimingV3) { + if ((isProfiling && currentTimelineData) || supportsUserTimingV3) { const componentName = getDisplayNameForFiber(fiber) || 'Unknown'; - if (isProfiling) { - // TODO (timeline) Record and cache component stack - if (currentTimelineData) { - currentTimelineData.schedulingEvents.push({ - componentName, - lanes: laneToLanesArray(lane), - timestamp: getRelativeTime(), - type: 'schedule-state-update', - warning: null, - }); - } + if (isProfiling && currentTimelineData) { + currentTimelineData.recordStateUpdateScheduled( + fiber, + componentName, + laneToLanesArray(lane), + getCurrentTime(), + ); } if (supportsUserTimingV3) { @@ -802,8 +596,6 @@ export function createProfilingHooks({ isProfiling = value; if (isProfiling) { - const internalModuleSourceToRanges = new Map(); - if (supportsUserTimingV3) { const ranges = getInternalModuleRanges(); if (ranges) { @@ -821,43 +613,11 @@ export function createProfilingHooks({ } } - const laneToReactMeasureMap = new Map(); - let lane = 1; - for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) { - laneToReactMeasureMap.set(lane, []); - lane *= 2; - } - - currentBatchUID = 0; - currentReactComponentMeasure = null; - currentReactMeasuresStack = []; - currentTimelineData = { - // Session wide metadata; only collected once. - internalModuleSourceToRanges, - laneToLabelMap: laneToLabelMap || new Map(), + currentTimelineData = new TimelineData( + laneToLabelMap, 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; + getCurrentTime(), + ); } } } diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 61bf164dc4598..45ab8bac4ac3c 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -3986,14 +3986,38 @@ export function attach( if (currentTimelineData) { const { batchUIDToMeasuresMap, + componentMeasures, + duration, + flamechart, internalModuleSourceToRanges, laneToLabelMap, laneToReactMeasureMap, - ...rest + nativeEvents, + networkMeasures, + otherUserTimingMarks, + reactVersion, + schedulingEvents, + snapshots, + snapshotHeight, + startTime, + suspenseEvents, + thrownErrors, } = currentTimelineData; timelineData = { - ...rest, + componentMeasures, + duration, + flamechart, + nativeEvents, + networkMeasures, + otherUserTimingMarks, + reactVersion, + schedulingEvents, + snapshots, + snapshotHeight, + startTime, + suspenseEvents, + thrownErrors, // Most of the data is safe to parse as-is, // but we need to convert the nested Arrays back to Maps. diff --git a/packages/react-devtools-timeline/src/TimelineData.js b/packages/react-devtools-timeline/src/TimelineData.js new file mode 100644 index 0000000000000..04169854da6a3 --- /dev/null +++ b/packages/react-devtools-timeline/src/TimelineData.js @@ -0,0 +1,465 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; +import type { + BatchUID, + Flamechart, + InternalModuleSourceToRanges, + LaneToLabelMap, + NativeEvent, + NetworkMeasure, + Phase, + ReactComponentMeasure, + ReactComponentMeasureType, + ReactLane, + ReactMeasure, + ReactMeasureType, + SchedulingEvent, + Snapshot, + SuspenseEvent, + SuspenseEventResolution, + ThrownError, + UserTimingMark, +} from 'react-devtools-timeline/src/types'; + +export type RecordComponentSuspendedCallback = ( + resolution: SuspenseEventResolution, + currentTime: number, +) => void; + +// Add padding to the start/stop time of the profile. +// This makes the UI nicer to use. +const TIME_OFFSET = 10; + +// TODO (timeline) Handle data types from full Chrome profile. +export default class TimelineData { + _currentBatchUID: BatchUID = 0; + _currentReactComponentMeasure: ReactComponentMeasure | null = null; + _currentReactMeasuresStack: Array = []; + _nextRenderShouldStartNewBatch: boolean = true; + + // Session wide metadata; only collected once. + internalModuleSourceToRanges: InternalModuleSourceToRanges = new Map(); + laneToLabelMap: LaneToLabelMap; + reactVersion: string; + + // Data logged by React during profiling session. + componentMeasures: ReactComponentMeasure[] = []; + schedulingEvents: SchedulingEvent[] = []; + suspenseEvents: SuspenseEvent[] = []; + thrownErrors: ThrownError[] = []; + + // Data inferred based on what React logs. + batchUIDToMeasuresMap: Map = new Map(); + duration: number = 0; + laneToReactMeasureMap: Map = new Map(); + startTime: number = 0; + + // Data only available in Chrome profiles. + flamechart: Flamechart = []; + nativeEvents: NativeEvent[] = []; + networkMeasures: NetworkMeasure[] = []; + otherUserTimingMarks: UserTimingMark[] = []; + snapshots: Snapshot[] = []; + snapshotHeight: number = 0; + + constructor( + laneToLabelMap: LaneToLabelMap | null, + reactVersion: string, + currentTime: number, + ) { + this.laneToLabelMap = laneToLabelMap || new Map(); + this.reactVersion = reactVersion; + this.startTime = currentTime - TIME_OFFSET; + } + + recordCommitStarted(lanesArray: ReactLane[], currentTime: number): void { + this._recordReactMeasureStarted('commit', lanesArray, currentTime); + + // TODO (timeline) Re-think this approach to "batching"; I don't think it works for Suspense or pre-rendering. + // This issue applies to the User Timing data also. + this._nextRenderShouldStartNewBatch = true; + } + + recordCommitStopped(currentTime: number): void { + this._recordReactMeasureStopped('commit', currentTime); + this._recordReactMeasureStopped('render-idle', currentTime); + } + + recordComponentRenderStarted( + fiber: Fiber | null, + componentName: string, + currentTime: number, + ): void { + this._recordReactComponentMeasureStarted( + 'render', + fiber, + componentName, + currentTime, + ); + } + + recordComponentRenderStopped(currentTime: number): void { + this._recordReactComponentMeasureStopped('render', currentTime); + } + + recordComponentLayoutEffectMountStarted( + fiber: Fiber | null, + componentName: string, + currentTime: number, + ): void { + this._recordReactComponentMeasureStarted( + 'layout-effect-mount', + fiber, + componentName, + currentTime, + ); + } + + recordComponentLayoutEffectMountStopped(currentTime: number): void { + this._recordReactComponentMeasureStopped( + 'layout-effect-mount', + currentTime, + ); + } + + recordComponentLayoutEffectUnmountStarted( + fiber: Fiber | null, + componentName: string, + currentTime: number, + ): void { + this._recordReactComponentMeasureStarted( + 'layout-effect-unmount', + fiber, + componentName, + currentTime, + ); + } + + recordComponentLayoutEffectUnmountStopped(currentTime: number): void { + this._recordReactComponentMeasureStopped( + 'layout-effect-unmount', + currentTime, + ); + } + + recordComponentPassiveEffectMountStarted( + fiber: Fiber | null, + componentName: string, + currentTime: number, + ): void { + this._recordReactComponentMeasureStarted( + 'passive-effect-mount', + fiber, + componentName, + currentTime, + ); + } + + recordComponentPassiveEffectMountStopped(currentTime: number): void { + this._recordReactComponentMeasureStopped( + 'passive-effect-mount', + currentTime, + ); + } + + recordComponentPassiveEffectUnmountStarted( + fiber: Fiber | null, + componentName: string, + currentTime: number, + ): void { + this._recordReactComponentMeasureStarted( + 'passive-effect-unmount', + fiber, + componentName, + currentTime, + ); + } + + recordComponentPassiveEffectUnmountStopped(currentTime: number): void { + this._recordReactComponentMeasureStopped( + 'passive-effect-unmount', + currentTime, + ); + } + + recordComponentErrored( + fiber: Fiber | null, + message: string, + phase: Phase, + lanesArray: ReactLane[], + componentName: string, + currentTime: number, + ): void { + // TODO (timeline) Record and cache component stack + + this.thrownErrors.push({ + componentName, + message, + phase, + timestamp: currentTime - this.startTime, + type: 'thrown-error', + }); + } + + recordComponentSuspended( + fiber: Fiber | null, + componentName: string, + phase: Phase, + wakeableID: number, + wakeableDisplayName: string, + lanesArray: ReactLane[], + currentStartTime: number, + ): RecordComponentSuspendedCallback { + // TODO (timeline) Record and cache component stack + + const suspenseEvent: SuspenseEvent = { + componentName, + depth: 0, + duration: 0, + id: `${wakeableID}`, + phase, + promiseName: wakeableDisplayName, + resolution: 'unresolved', + timestamp: currentStartTime - this.startTime, + type: 'suspense', + warning: null, + }; + + this.suspenseEvents.push(suspenseEvent); + + return function resolveOrReject( + resolution: SuspenseEventResolution, + currentStopTime: number, + ): void { + suspenseEvent.duration = currentStopTime - currentStartTime; + suspenseEvent.resolution = resolution; + }; + } + + recordLayoutEffectsStarted( + lanesArray: ReactLane[], + currentTime: number, + ): void { + this._recordReactMeasureStarted('layout-effects', lanesArray, currentTime); + } + + recordLayoutEffectsStopped(currentTime: number): void { + this._recordReactMeasureStopped('layout-effects', currentTime); + } + + recordPassiveEffectsStarted( + lanesArray: ReactLane[], + currentTime: number, + ): void { + this._recordReactMeasureStarted('passive-effects', lanesArray, currentTime); + } + + recordPassiveEffectsStopped(currentTime: number): void { + this._recordReactMeasureStopped('passive-effects', currentTime); + } + + recordRenderStarted(lanesArray: ReactLane[], currentTime: number): void { + if (this._nextRenderShouldStartNewBatch) { + this._nextRenderShouldStartNewBatch = false; + this._currentBatchUID++; + } + + // If this is a new batch of work, wrap an "idle" measure around it. + // Log it before the "render" measure to preserve the stack ordering. + const top = + this._currentReactMeasuresStack.length > 0 + ? this._currentReactMeasuresStack[ + this._currentReactMeasuresStack.length - 1 + ] + : null; + if (!top || top.type !== 'render-idle') { + this._recordReactMeasureStarted('render-idle', lanesArray, currentTime); + } + + this._recordReactMeasureStarted('render', lanesArray, currentTime); + } + + recordRenderYielded(currentTime: number): void { + this._recordReactMeasureStopped('render', currentTime); + } + + recordRenderStopped(currentTime: number): void { + this._recordReactMeasureStopped('render', currentTime); + } + + recordRenderScheduled(lanesArray: ReactLane[], currentTime: number): void { + this.schedulingEvents.push({ + lanes: lanesArray, + timestamp: currentTime - this.startTime, + type: 'schedule-render', + warning: null, + }); + } + + recordForceUpdateScheduled( + fiber: Fiber | null, + componentName: string, + lanesArray: ReactLane[], + currentTime: number, + ): void { + this.schedulingEvents.push({ + componentName, + lanes: lanesArray, + timestamp: currentTime - this.startTime, + type: 'schedule-force-update', + warning: null, + }); + } + + recordStateUpdateScheduled( + fiber: Fiber | null, + componentName: string, + lanesArray: ReactLane[], + currentTime: number, + ): void { + this.schedulingEvents.push({ + componentName, + lanes: lanesArray, + timestamp: currentTime - this.startTime, + type: 'schedule-state-update', + warning: null, + }); + } + + _recordReactComponentMeasureStarted( + type: ReactComponentMeasureType, + fiber: Fiber | null, + componentName: string, + currentTime: number, + ): void { + if (this._currentReactComponentMeasure) { + console.warn( + 'React component measure started unexpected. Type "%s" incomplete.', + this._currentReactComponentMeasure.type, + ); + } + + // TODO (timeline) Record and cache component stack + + this._currentReactComponentMeasure = { + componentName, + duration: 0, + timestamp: currentTime - this.startTime, + type, + warning: null, + }; + } + + _recordReactComponentMeasureStopped( + expectedType: ReactComponentMeasureType, + currentTime: number, + ): void { + const reactComponentMeasure = this._currentReactComponentMeasure; + + this._currentReactComponentMeasure = null; + + if (reactComponentMeasure) { + if (reactComponentMeasure.type !== expectedType) { + console.warn( + 'React component measure completed unexpected. Type "%s" was expected but type "%s" was found.', + expectedType, + reactComponentMeasure.type, + ); + } else { + this.componentMeasures.push(reactComponentMeasure); + + const relativeTime = currentTime - this.startTime; + + reactComponentMeasure.duration = + relativeTime - reactComponentMeasure.timestamp; + } + } else { + console.warn( + 'React component measure completed unexpected. No measure found.', + ); + } + } + + _recordReactMeasureStarted( + type: ReactMeasureType, + lanesArray: ReactLane[], + currentTime: number, + ): void { + // Decide what depth thi work should be rendered at, based on what's on the top of the stack. + // It's okay to render over top of "idle" work but everything else should be on its own row. + let depth = 0; + if (this._currentReactMeasuresStack.length > 0) { + const top = this._currentReactMeasuresStack[ + this._currentReactMeasuresStack.length - 1 + ]; + depth = top.type === 'render-idle' ? top.depth : top.depth + 1; + } + + const reactMeasure: ReactMeasure = { + type, + batchUID: this._currentBatchUID, + depth, + lanes: lanesArray, + timestamp: currentTime - this.startTime, + duration: 0, + }; + + this._currentReactMeasuresStack.push(reactMeasure); + + let reactMeasures = this.batchUIDToMeasuresMap.get(this._currentBatchUID); + if (reactMeasures != null) { + reactMeasures.push(reactMeasure); + } else { + this.batchUIDToMeasuresMap.set(this._currentBatchUID, [reactMeasure]); + } + + lanesArray.forEach(lane => { + reactMeasures = this.laneToReactMeasureMap.get(lane); + if (reactMeasures) { + reactMeasures.push(reactMeasure); + } else { + this.laneToReactMeasureMap.set(lane, [reactMeasure]); + } + }); + } + + _recordReactMeasureStopped( + type: ReactMeasureType, + currentTime: number, + ): void { + if (this._currentReactMeasuresStack.length === 0) { + console.error( + 'Unexpected type "%s" completed at %sms while React measures stack is empty.', + type, + currentTime, + ); + // Ignore work "completion" user timing mark that doesn't complete anything + return; + } + + const top = this._currentReactMeasuresStack.pop(); + if (top.type !== type) { + console.error( + 'Unexpected type "%s" completed at %sms before "%s" completed.', + type, + currentTime, + top.type, + ); + } + + const relativeTime = currentTime - this.startTime; + + // $FlowFixMe This property should not be writable outside of this function. + top.duration = relativeTime - top.timestamp; + + this.duration = relativeTime + TIME_OFFSET; + } +} diff --git a/packages/react-devtools-timeline/src/types.js b/packages/react-devtools-timeline/src/types.js index 9469523a1e2c2..642df5b86536b 100644 --- a/packages/react-devtools-timeline/src/types.js +++ b/packages/react-devtools-timeline/src/types.js @@ -60,6 +60,7 @@ export type ReactScheduleForceUpdateEvent = {| export type Phase = 'mount' | 'update'; +export type SuspenseEventResolution = 'rejected' | 'resolved' | 'unresolved'; export type SuspenseEvent = {| ...BaseReactEvent, depth: number, @@ -67,7 +68,7 @@ export type SuspenseEvent = {| +id: string, +phase: Phase | null, promiseName: string | null, - resolution: 'rejected' | 'resolved' | 'unresolved', + resolution: SuspenseEventResolution, +type: 'suspense', |}; @@ -197,7 +198,8 @@ export type InternalModuleSourceToRanges = Map< export type LaneToLabelMap = Map; -export type TimelineData = {| +// TODO (timeline) Remove this type. +export type TimelineData = { batchUIDToMeasuresMap: Map, componentMeasures: ReactComponentMeasure[], duration: number, @@ -215,7 +217,7 @@ export type TimelineData = {| startTime: number, suspenseEvents: SuspenseEvent[], thrownErrors: ThrownError[], -|}; +}; export type TimelineDataExport = {| batchUIDToMeasuresKeyValueArray: Array<[BatchUID, ReactMeasure[]]>,