0% found this document useful (0 votes)
105 views17 pages

Profiling Hooks

This document defines functions for creating profiling hooks to collect timing data in React. It handles starting and stopping measures, mapping lanes to labels, and collecting data into a timeline. User timing is used to record marks if supported by the environment.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
105 views17 pages

Profiling Hooks

This document defines functions for creating profiling hooks to collect timing data in React. It handles starting and stopping measures, mapping lanes to labels, and collecting data into a timeline. User timing is used to record marks if supported by the environment.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 17

/**

* 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
*/

import type {
Lane,
Lanes,
DevToolsProfilingHooks,
WorkTagMap,
CurrentDispatcherRef,
} from 'react-devtools-shared/src/backend/types';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import type {Wakeable} from 'shared/ReactTypes';
import type {
BatchUID,
InternalModuleSourceToRanges,
LaneToLabelMap,
ReactComponentMeasure,
ReactLane,
ReactMeasure,
ReactMeasureType,
ReactScheduleStateUpdateEvent,
SchedulingEvent,
SuspenseEvent,
TimelineData,
} from 'react-devtools-timeline/src/types';

import isArray from 'shared/isArray';


import {
REACT_TOTAL_NUM_LANES,
SCHEDULING_PROFILER_VERSION,
} from 'react-devtools-timeline/src/constants';
import {describeFiber} from './DevToolsFiberComponentStack';

// 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.
let supportsUserTiming =
typeof performance !== 'undefined' &&
// $FlowFixMe[method-unbinding]
typeof performance.mark === 'function' &&
// $FlowFixMe[method-unbinding]
typeof performance.clearMarks === 'function';

let supportsUserTimingV3 = false;


if (supportsUserTiming) {
const CHECK_V3_MARK = '__v3';
const markOptions: {
detail?: mixed,
startTime?: number,
} = {};
Object.defineProperty(markOptions, 'startTime', {
get: function () {
supportsUserTimingV3 = true;
return 0;
},
set: function () {},
});

try {
performance.mark(CHECK_V3_MARK, markOptions);
} catch (error) {
// Ignore
} finally {
performance.clearMarks(CHECK_V3_MARK);
}
}

if (supportsUserTimingV3) {
performanceTarget = performance;
}

// Some environments (e.g. React Native / Hermes) don't support the performance API
yet.
const getCurrentTime =
// $FlowFixMe[method-unbinding]
typeof performance === 'object' && typeof performance.now === 'function'
? () => performance.now()
: () => Date.now();

// Mocking the Performance Object (and User Timing APIs) for testing is fragile.
// This API allows tests to directly override the User Timing APIs.
export function setPerformanceMock_ONLY_FOR_TESTING(
performanceMock: Performance | null,
) {
performanceTarget = performanceMock;
supportsUserTiming = performanceMock !== null;
supportsUserTimingV3 = performanceMock !== null;
}

export type GetTimelineData = () => TimelineData | null;


export type ToggleProfilingStatus = (value: boolean) => void;

type Response = {
getTimelineData: GetTimelineData,
profilingHooks: DevToolsProfilingHooks,
toggleProfilingStatus: ToggleProfilingStatus,
};

export function createProfilingHooks({


getDisplayNameForFiber,
getIsProfiling,
getLaneLabelMap,
workTagMap,
currentDispatcherRef,
reactVersion,
}: {
getDisplayNameForFiber: (fiber: Fiber) => string | null,
getIsProfiling: () => boolean,
getLaneLabelMap?: () => Map<Lane, string> | null,
currentDispatcherRef?: CurrentDispatcherRef,
workTagMap: WorkTagMap,
reactVersion: string,
}): Response {
let currentBatchUID: BatchUID = 0;
let currentReactComponentMeasure: ReactComponentMeasure | null = null;
let currentReactMeasuresStack: Array<ReactMeasure> = [];
let currentTimelineData: TimelineData | null = null;
let currentFiberStacks: Map<SchedulingEvent, Array<Fiber>> = new Map();
let isProfiling: boolean = false;
let nextRenderShouldStartNewBatch: boolean = false;

function getRelativeTime() {
const currentTime = getCurrentTime();

if (currentTimelineData) {
if (currentTimelineData.startTime === 0) {
currentTimelineData.startTime = currentTime - TIME_OFFSET;
}

return currentTime - currentTimelineData.startTime;


}

return 0;
}

function getInternalModuleRanges() {
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges ===
'function'
) {
// Ask the DevTools hook for module ranges that may have been reported by the
current renderer(s).
// Don't do this eagerly like the laneToLabelMap,
// because some modules might not yet have registered their boundaries when
the renderer is injected.
const ranges = __REACT_DEVTOOLS_GLOBAL_HOOK__.getInternalModuleRanges();

// This check would not be required,


// except that it's possible for things to override
__REACT_DEVTOOLS_GLOBAL_HOOK__.
if (isArray(ranges)) {
return ranges;
}
}

return null;
}

function getTimelineData(): TimelineData | null {


return currentTimelineData;
}

function laneToLanesArray(lanes: Lane) {


const lanesArray = [];
let lane = 1;
for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
if (lane & lanes) {
lanesArray.push(lane);
}
lane *= 2;
}

return lanesArray;
}

const laneToLabelMap: LaneToLabelMap | null =


typeof getLaneLabelMap === 'function' ? getLaneLabelMap() : null;

function markMetadata() {
markAndClear(`--react-version-${reactVersion}`);
markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);

const ranges = getInternalModuleRanges();


if (ranges) {
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
if (isArray(range) && range.length === 2) {
const [startStackFrame, stopStackFrame] = ranges[i];

markAndClear(`--react-internal-module-start-${startStackFrame}`);
markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
}
}
}

if (laneToLabelMap != null) {
const labels = Array.from(laneToLabelMap.values()).join(',');
markAndClear(`--react-lane-labels-${labels}`);
}
}

function markAndClear(markName: string) {


// This method won't be called unless these functions are defined, so we can
skip the extra typeof check.
((performanceTarget: any): Performance).mark(markName);
((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[cannot-write] 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 (supportsUserTimingV3) {
markAndClear(`--commit-start-${lanes}`);

// Some metadata only needs to be logged once per session,


// but if profiling information is being recorded via the Performance tab,
// DevTools has no way of knowing when the recording starts.
// Because of that, we log thie type of data periodically (once per commit).
markMetadata();
}
}

function markCommitStopped(): void {


if (isProfiling) {
recordReactMeasureCompleted('commit');
recordReactMeasureCompleted('render-idle');
}

if (supportsUserTimingV3) {
markAndClear('--commit-stop');
}
}

function markComponentRenderStarted(fiber: Fiber): void {


if (isProfiling || 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 (supportsUserTimingV3) {
markAndClear(`--component-render-start-${componentName}`);
}
}
}

function markComponentRenderStopped(): void {


if (isProfiling) {
if (currentReactComponentMeasure) {
if (currentTimelineData) {
currentTimelineData.componentMeasures.push(
currentReactComponentMeasure,
);
}

// $FlowFixMe[incompatible-use] found when upgrading Flow


currentReactComponentMeasure.duration =
// $FlowFixMe[incompatible-use] found when upgrading Flow
getRelativeTime() - currentReactComponentMeasure.timestamp;
currentReactComponentMeasure = null;
}
}

if (supportsUserTimingV3) {
markAndClear('--component-render-stop');
}
}

function markComponentLayoutEffectMountStarted(fiber: Fiber): void {


if (isProfiling || 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 (supportsUserTimingV3) {
markAndClear(`--component-layout-effect-mount-start-${componentName}`);
}
}
}

function markComponentLayoutEffectMountStopped(): void {


if (isProfiling) {
if (currentReactComponentMeasure) {
if (currentTimelineData) {
currentTimelineData.componentMeasures.push(
currentReactComponentMeasure,
);
}

// $FlowFixMe[incompatible-use] found when upgrading Flow


currentReactComponentMeasure.duration =
// $FlowFixMe[incompatible-use] found when upgrading Flow
getRelativeTime() - currentReactComponentMeasure.timestamp;
currentReactComponentMeasure = null;
}
}

if (supportsUserTimingV3) {
markAndClear('--component-layout-effect-mount-stop');
}
}

function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {


if (isProfiling || 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 (supportsUserTimingV3) {
markAndClear(
`--component-layout-effect-unmount-start-${componentName}`,
);
}
}
}

function markComponentLayoutEffectUnmountStopped(): void {


if (isProfiling) {
if (currentReactComponentMeasure) {
if (currentTimelineData) {
currentTimelineData.componentMeasures.push(
currentReactComponentMeasure,
);
}

// $FlowFixMe[incompatible-use] found when upgrading Flow


currentReactComponentMeasure.duration =
// $FlowFixMe[incompatible-use] found when upgrading Flow
getRelativeTime() - currentReactComponentMeasure.timestamp;
currentReactComponentMeasure = null;
}
}

if (supportsUserTimingV3) {
markAndClear('--component-layout-effect-unmount-stop');
}
}
function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
if (isProfiling || 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 (supportsUserTimingV3) {
markAndClear(`--component-passive-effect-mount-start-${componentName}`);
}
}
}

function markComponentPassiveEffectMountStopped(): void {


if (isProfiling) {
if (currentReactComponentMeasure) {
if (currentTimelineData) {
currentTimelineData.componentMeasures.push(
currentReactComponentMeasure,
);
}

// $FlowFixMe[incompatible-use] found when upgrading Flow


currentReactComponentMeasure.duration =
// $FlowFixMe[incompatible-use] found when upgrading Flow
getRelativeTime() - currentReactComponentMeasure.timestamp;
currentReactComponentMeasure = null;
}
}

if (supportsUserTimingV3) {
markAndClear('--component-passive-effect-mount-stop');
}
}

function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {


if (isProfiling || 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 (supportsUserTimingV3) {
markAndClear(
`--component-passive-effect-unmount-start-${componentName}`,
);
}
}
}

function markComponentPassiveEffectUnmountStopped(): void {


if (isProfiling) {
if (currentReactComponentMeasure) {
if (currentTimelineData) {
currentTimelineData.componentMeasures.push(
currentReactComponentMeasure,
);
}

// $FlowFixMe[incompatible-use] found when upgrading Flow


currentReactComponentMeasure.duration =
// $FlowFixMe[incompatible-use] found when upgrading Flow
getRelativeTime() - currentReactComponentMeasure.timestamp;
currentReactComponentMeasure = null;
}
}

if (supportsUserTimingV3) {
markAndClear('--component-passive-effect-unmount-stop');
}
}

function markComponentErrored(
fiber: Fiber,
thrownValue: mixed,
lanes: Lanes,
): void {
if (isProfiling || supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
const phase = fiber.alternate === null ? 'mount' : 'update';

let message = '';


if (
thrownValue !== null &&
typeof thrownValue === 'object' &&
typeof thrownValue.message === 'string'
) {
message = thrownValue.message;
} else if (typeof thrownValue === 'string') {
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 (supportsUserTimingV3) {
markAndClear(`--error-${componentName}-${phase}-${message}`);
}
}
}

const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;

// $FlowFixMe[incompatible-type]: Flow cannot handle polymorphic WeakMaps


const wakeableIDs: WeakMap<Wakeable, number> = 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);
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
const phase = fiber.alternate === null ? 'mount' : 'update';

// Following the non-standard fn.displayName convention,


// 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 || '';

let suspenseEvent: SuspenseEvent | null = null;


if (isProfiling) {
// TODO (timeline) Record and cache component stack
suspenseEvent = {
componentName,
depth: 0,
duration: 0,
id: `${id}`,
phase,
promiseName: displayName,
resolution: 'unresolved',
timestamp: getRelativeTime(),
type: 'suspense',
warning: null,
};

if (currentTimelineData) {
currentTimelineData.suspenseEvents.push(suspenseEvent);
}
}

if (supportsUserTimingV3) {
markAndClear(
`--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-$
{displayName}`,
);
}

wakeable.then(
() => {
if (suspenseEvent) {
suspenseEvent.duration =
getRelativeTime() - suspenseEvent.timestamp;
suspenseEvent.resolution = 'resolved';
}

if (supportsUserTimingV3) {
markAndClear(`--suspense-resolved-${id}-${componentName}`);
}
},
() => {
if (suspenseEvent) {
suspenseEvent.duration =
getRelativeTime() - suspenseEvent.timestamp;
suspenseEvent.resolution = 'rejected';
}

if (supportsUserTimingV3) {
markAndClear(`--suspense-rejected-${id}-${componentName}`);
}
},
);
}
}

function markLayoutEffectsStarted(lanes: Lanes): void {


if (isProfiling) {
recordReactMeasureStarted('layout-effects', lanes);
}

if (supportsUserTimingV3) {
markAndClear(`--layout-effects-start-${lanes}`);
}
}

function markLayoutEffectsStopped(): void {


if (isProfiling) {
recordReactMeasureCompleted('layout-effects');
}

if (supportsUserTimingV3) {
markAndClear('--layout-effects-stop');
}
}

function markPassiveEffectsStarted(lanes: Lanes): void {


if (isProfiling) {
recordReactMeasureStarted('passive-effects', lanes);
}

if (supportsUserTimingV3) {
markAndClear(`--passive-effects-start-${lanes}`);
}
}

function markPassiveEffectsStopped(): void {


if (isProfiling) {
recordReactMeasureCompleted('passive-effects');
}

if (supportsUserTimingV3) {
markAndClear('--passive-effects-stop');
}
}

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 (supportsUserTimingV3) {
markAndClear(`--render-start-${lanes}`);
}
}

function markRenderYielded(): void {


if (isProfiling) {
recordReactMeasureCompleted('render');
}

if (supportsUserTimingV3) {
markAndClear('--render-yield');
}
}

function markRenderStopped(): void {


if (isProfiling) {
recordReactMeasureCompleted('render');
}
if (supportsUserTimingV3) {
markAndClear('--render-stop');
}
}

function markRenderScheduled(lane: Lane): void {


if (isProfiling) {
if (currentTimelineData) {
currentTimelineData.schedulingEvents.push({
lanes: laneToLanesArray(lane),
timestamp: getRelativeTime(),
type: 'schedule-render',
warning: null,
});
}
}

if (supportsUserTimingV3) {
markAndClear(`--schedule-render-${lane}`);
}
}

function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {


if (isProfiling || 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 (supportsUserTimingV3) {
markAndClear(`--schedule-forced-update-${lane}-${componentName}`);
}
}
}

function getParentFibers(fiber: Fiber): Array<Fiber> {


const parents = [];
let parent: null | Fiber = fiber;
while (parent !== null) {
parents.push(parent);
parent = parent.return;
}
return parents;
}

function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {


if (isProfiling || supportsUserTimingV3) {
const componentName = getDisplayNameForFiber(fiber) || 'Unknown';
if (isProfiling) {
// TODO (timeline) Record and cache component stack
if (currentTimelineData) {
const event: ReactScheduleStateUpdateEvent = {
componentName,
// Store the parent fibers so we can post process
// them after we finish profiling
lanes: laneToLanesArray(lane),
timestamp: getRelativeTime(),
type: 'schedule-state-update',
warning: null,
};
currentFiberStacks.set(event, getParentFibers(fiber));
// $FlowFixMe[incompatible-use] found when upgrading Flow
currentTimelineData.schedulingEvents.push(event);
}
}

if (supportsUserTimingV3) {
markAndClear(`--schedule-state-update-${lane}-${componentName}`);
}
}
}

function toggleProfilingStatus(value: boolean) {


if (isProfiling !== value) {
isProfiling = value;

if (isProfiling) {
const internalModuleSourceToRanges: InternalModuleSourceToRanges =
new Map();

if (supportsUserTimingV3) {
const ranges = getInternalModuleRanges();
if (ranges) {
for (let i = 0; i < ranges.length; i++) {
const range = ranges[i];
if (isArray(range) && range.length === 2) {
const [startStackFrame, stopStackFrame] = ranges[i];

markAndClear(
`--react-internal-module-start-${startStackFrame}`,
);
markAndClear(`--react-internal-module-stop-${stopStackFrame}`);
}
}
}
}

const laneToReactMeasureMap = new Map<ReactLane, ReactMeasure[]>();


let lane = 1;
for (let index = 0; index < REACT_TOTAL_NUM_LANES; index++) {
laneToReactMeasureMap.set(lane, []);
lane *= 2;
}

currentBatchUID = 0;
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,
};
nextRenderShouldStartNewBatch = true;
} else {
// Postprocess Profile data
if (currentTimelineData !== null) {
currentTimelineData.schedulingEvents.forEach(event => {
if (event.type === 'schedule-state-update') {
// TODO(luna): We can optimize this by creating a map of
// fiber to component stack instead of generating the stack
// for every fiber every time
const fiberStack = currentFiberStacks.get(event);
if (fiberStack && currentDispatcherRef != null) {
event.componentStack = fiberStack.reduce((trace, fiber) => {
return (
trace +
describeFiber(workTagMap, fiber, currentDispatcherRef)
);
}, '');
}
}
});
}

// Clear the current fiber stacks so we don't hold onto the fibers
// in memory after profiling finishes
currentFiberStacks.clear();
}
}
}

return {
getTimelineData,
profilingHooks: {
markCommitStarted,
markCommitStopped,
markComponentRenderStarted,
markComponentRenderStopped,
markComponentPassiveEffectMountStarted,
markComponentPassiveEffectMountStopped,
markComponentPassiveEffectUnmountStarted,
markComponentPassiveEffectUnmountStopped,
markComponentLayoutEffectMountStarted,
markComponentLayoutEffectMountStopped,
markComponentLayoutEffectUnmountStarted,
markComponentLayoutEffectUnmountStopped,
markComponentErrored,
markComponentSuspended,
markLayoutEffectsStarted,
markLayoutEffectsStopped,
markPassiveEffectsStarted,
markPassiveEffectsStopped,
markRenderStarted,
markRenderYielded,
markRenderStopped,
markRenderScheduled,
markForceUpdateScheduled,
markStateUpdateScheduled,
},
toggleProfilingStatus,
};
}

You might also like