Skip to content

Commit

Permalink
Bump parallel components into a separate track
Browse files Browse the repository at this point in the history
  • Loading branch information
sebmarkbage committed Dec 11, 2024
1 parent f468d08 commit cf39c17
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 10 deletions.
61 changes: 53 additions & 8 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export type JSONValue =
| $ReadOnlyArray<JSONValue>;

type ProfilingResult = {
track: number,
endTime: number,
};

Expand Down Expand Up @@ -648,7 +649,7 @@ export function reportGlobalError(response: Response, error: Error): void {
}
});
if (enableProfilerTimer && enableComponentPerformanceTrack) {
flushComponentPerformance(getChunk(response, 0));
flushComponentPerformance(getChunk(response, 0), 0, -Infinity);
}
}

Expand Down Expand Up @@ -2753,9 +2754,16 @@ function resolveTypedArray(
resolveBuffer(response, id, view);
}

function flushComponentPerformance(root: SomeChunk<any>): number {
function flushComponentPerformance(
root: SomeChunk<any>,
trackIdx: number, // Next available track
trackTime: number, // The time after which it is available
): ProfilingResult {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return 0;
// eslint-disable-next-line react-internal/prod-error-codes
throw new Error(
'flushComponentPerformance should never be called in production mode. This is a bug in React.',
);
}
// Write performance.measure() entries for Server Components in tree order.
// This must be done at the end to collect the end time from the whole tree.
Expand All @@ -2766,7 +2774,9 @@ function flushComponentPerformance(root: SomeChunk<any>): number {
// chunk in two places. We should extend the current end time as if it was
// rendered as part of this tree.
const previousResult: ProfilingResult = root._children;
return previousResult.endTime;
// Since we didn't bump the track this time, we just return the same track.
previousResult.track = trackIdx;
return previousResult;
}
const children = root._children;
if (root.status === RESOLVED_MODEL) {
Expand All @@ -2775,16 +2785,49 @@ function flushComponentPerformance(root: SomeChunk<any>): number {
// the performance characteristics of the app by profiling.
initializeModelChunk(root);
}
const result: ProfilingResult = {endTime: -Infinity};

// First find the start time of the first component to know if it was running
// in parallel with the previous.
const debugInfo = root._debugInfo;
if (debugInfo) {
for (let i = 1; i < debugInfo.length; i++) {
const info = debugInfo[i];
if (typeof info.name === 'string') {
// $FlowFixMe: Refined.
const startTimeInfo = debugInfo[i - 1];
if (typeof startTimeInfo.time === 'number') {
const startTime = startTimeInfo.time;
if (startTime < trackTime) {
// The start time of this component is before the end time of the previous
// component on this track so we need to bump the next one to a parallel track.
trackIdx++;
trackTime = startTime;
}
break;
}
}
}
}

const result: ProfilingResult = {track: trackIdx, endTime: -Infinity};
root._children = result;
let childrenEndTime = -Infinity;
let childTrackIdx = trackIdx;
let childTrackTime = trackTime;
for (let i = 0; i < children.length; i++) {
const childEndTime = flushComponentPerformance(children[i]);
const childResult = flushComponentPerformance(
children[i],
childTrackIdx,
childTrackTime,
);
childTrackIdx = childResult.track;
const childEndTime = childResult.endTime;
childTrackTime = childEndTime;
if (childEndTime > childrenEndTime) {
childrenEndTime = childEndTime;
}
}
const debugInfo = root._debugInfo;

if (debugInfo) {
let endTime = 0;
for (let i = debugInfo.length - 1; i >= 0; i--) {
Expand All @@ -2803,6 +2846,7 @@ function flushComponentPerformance(root: SomeChunk<any>): number {
const startTime = startTimeInfo.time;
logComponentRender(
componentInfo,
trackIdx,
startTime,
endTime,
childrenEndTime,
Expand All @@ -2811,7 +2855,8 @@ function flushComponentPerformance(root: SomeChunk<any>): number {
}
}
}
return (result.endTime = childrenEndTime);
result.endTime = childrenEndTime;
return result;
}

function processFullBinaryRow(
Expand Down
20 changes: 18 additions & 2 deletions packages/react-client/src/ReactFlightPerformanceTrack.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const COMPONENTS_TRACK = 'Server Components ⚛';
// Reused to avoid thrashing the GC.
const reusableComponentDevToolDetails = {
color: 'primary',
track: COMPONENTS_TRACK,
track: '',
trackGroup: COMPONENTS_TRACK,
};
const reusableComponentOptions = {
start: -0,
Expand All @@ -32,13 +33,27 @@ const reusableComponentOptions = {
},
};

const trackNames = [
'Primary',
'Parallel',
'Parallel\u200b', // Padded with zero-width space to give each track a unique name.
'Parallel\u200b\u200b',
'Parallel\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b\u200b\u200b',
'Parallel\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b',
];

export function logComponentRender(
componentInfo: ReactComponentInfo,
trackIdx: number,
startTime: number,
endTime: number,
childrenEndTime: number,
): void {
if (supportsUserTiming && childrenEndTime >= 0) {
if (supportsUserTiming && childrenEndTime >= 0 && trackIdx < 10) {
const name = componentInfo.name;
const selfTime = endTime - startTime;
reusableComponentDevToolDetails.color =
Expand All @@ -49,6 +64,7 @@ export function logComponentRender(
: selfTime < 500
? 'primary-dark'
: 'error';
reusableComponentDevToolDetails.track = trackNames[trackIdx];
reusableComponentOptions.start = startTime < 0 ? 0 : startTime;
reusableComponentOptions.end = childrenEndTime;
performance.measure(name, reusableComponentOptions);
Expand Down

0 comments on commit cf39c17

Please sign in to comment.