Professional Documents
Culture Documents
Profiling Hooks
Profiling Hooks
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';
// 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';
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;
}
type Response = {
getTimelineData: GetTimelineData,
profilingHooks: DevToolsProfilingHooks,
toggleProfilingStatus: ToggleProfilingStatus,
};
function getRelativeTime() {
const currentTime = getCurrentTime();
if (currentTimelineData) {
if (currentTimelineData.startTime === 0) {
currentTimelineData.startTime = currentTime - TIME_OFFSET;
}
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();
return null;
}
return lanesArray;
}
function markMetadata() {
markAndClear(`--react-version-${reactVersion}`);
markAndClear(`--profiler-version-${SCHEDULING_PROFILER_VERSION}`);
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 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);
currentReactMeasuresStack.push(reactMeasure);
if (currentTimelineData) {
const {batchUIDToMeasuresMap, laneToReactMeasureMap} =
currentTimelineData;
lanesArray.forEach(lane => {
reactMeasures = laneToReactMeasureMap.get(lane);
if (reactMeasures) {
reactMeasures.push(reactMeasure);
}
});
}
}
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;
}
if (currentTimelineData) {
currentTimelineData.duration = getRelativeTime() + TIME_OFFSET;
}
}
if (supportsUserTimingV3) {
markAndClear(`--commit-start-${lanes}`);
if (supportsUserTimingV3) {
markAndClear('--commit-stop');
}
}
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}`);
}
}
}
if (supportsUserTimingV3) {
markAndClear('--component-render-stop');
}
}
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}`);
}
}
}
if (supportsUserTimingV3) {
markAndClear('--component-layout-effect-mount-stop');
}
}
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}`,
);
}
}
}
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}`);
}
}
}
if (supportsUserTimingV3) {
markAndClear('--component-passive-effect-mount-stop');
}
}
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}`,
);
}
}
}
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';
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}`);
}
}
}
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';
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}`);
}
},
);
}
}
if (supportsUserTimingV3) {
markAndClear(`--layout-effects-start-${lanes}`);
}
}
if (supportsUserTimingV3) {
markAndClear('--layout-effects-stop');
}
}
if (supportsUserTimingV3) {
markAndClear(`--passive-effects-start-${lanes}`);
}
}
if (supportsUserTimingV3) {
markAndClear('--passive-effects-stop');
}
}
recordReactMeasureStarted('render', lanes);
}
if (supportsUserTimingV3) {
markAndClear(`--render-start-${lanes}`);
}
}
if (supportsUserTimingV3) {
markAndClear('--render-yield');
}
}
if (supportsUserTimingV3) {
markAndClear(`--schedule-render-${lane}`);
}
}
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}`);
}
}
}
if (supportsUserTimingV3) {
markAndClear(`--schedule-state-update-${lane}-${componentName}`);
}
}
}
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}`);
}
}
}
}
currentBatchUID = 0;
currentReactComponentMeasure = null;
currentReactMeasuresStack = [];
currentFiberStacks = new Map();
currentTimelineData = {
// Session wide metadata; only collected once.
internalModuleSourceToRanges,
laneToLabelMap: laneToLabelMap || new Map(),
reactVersion,
// 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,
};
}