diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 4d5204ab00220..38685437a0bbb 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -127,6 +127,7 @@ export type JSONValue = | $ReadOnlyArray; type ProfilingResult = { + track: number, endTime: number, }; @@ -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); } } @@ -2753,9 +2754,16 @@ function resolveTypedArray( resolveBuffer(response, id, view); } -function flushComponentPerformance(root: SomeChunk): number { +function flushComponentPerformance( + root: SomeChunk, + 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. @@ -2766,7 +2774,9 @@ function flushComponentPerformance(root: SomeChunk): 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) { @@ -2775,16 +2785,49 @@ function flushComponentPerformance(root: SomeChunk): 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--) { @@ -2803,6 +2846,7 @@ function flushComponentPerformance(root: SomeChunk): number { const startTime = startTimeInfo.time; logComponentRender( componentInfo, + trackIdx, startTime, endTime, childrenEndTime, @@ -2811,7 +2855,8 @@ function flushComponentPerformance(root: SomeChunk): number { } } } - return (result.endTime = childrenEndTime); + result.endTime = childrenEndTime; + return result; } function processFullBinaryRow( diff --git a/packages/react-client/src/ReactFlightPerformanceTrack.js b/packages/react-client/src/ReactFlightPerformanceTrack.js index f1e7b30280722..b8bd9ca2be935 100644 --- a/packages/react-client/src/ReactFlightPerformanceTrack.js +++ b/packages/react-client/src/ReactFlightPerformanceTrack.js @@ -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, @@ -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 = @@ -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);