@@ -71,6 +71,8 @@ import {createBoundServerReference} from './ReactFlightReplyClient';
7171
7272import {readTemporaryReference} from './ReactFlightTemporaryReferences';
7373
74+ import {logComponentRender} from './ReactFlightPerformanceTrack';
75+
7476import {
7577 REACT_LAZY_TYPE,
7678 REACT_ELEMENT_TYPE,
@@ -124,6 +126,10 @@ export type JSONValue =
124126 | {+[key: string]: JSONValue}
125127 | $ReadOnlyArray<JSONValue>;
126128
129+ type ProfilingResult = {
130+ endTime: number,
131+ };
132+
127133const ROW_ID = 0;
128134const ROW_TAG = 1;
129135const ROW_LENGTH = 2;
@@ -144,39 +150,44 @@ type PendingChunk<T> = {
144150 value: null | Array<(T) => mixed>,
145151 reason: null | Array<(mixed) => mixed>,
146152 _response: Response,
147- _debugInfo?: null | ReactDebugInfo,
153+ _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
154+ _debugInfo?: null | ReactDebugInfo, // DEV-only
148155 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
149156};
150157type BlockedChunk<T> = {
151158 status: 'blocked',
152159 value: null | Array<(T) => mixed>,
153160 reason: null | Array<(mixed) => mixed>,
154161 _response: Response,
155- _debugInfo?: null | ReactDebugInfo,
162+ _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
163+ _debugInfo?: null | ReactDebugInfo, // DEV-only
156164 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
157165};
158166type ResolvedModelChunk<T> = {
159167 status: 'resolved_model',
160168 value: UninitializedModel,
161169 reason: null,
162170 _response: Response,
163- _debugInfo?: null | ReactDebugInfo,
171+ _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
172+ _debugInfo?: null | ReactDebugInfo, // DEV-only
164173 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
165174};
166175type ResolvedModuleChunk<T> = {
167176 status: 'resolved_module',
168177 value: ClientReference<T>,
169178 reason: null,
170179 _response: Response,
171- _debugInfo?: null | ReactDebugInfo,
180+ _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
181+ _debugInfo?: null | ReactDebugInfo, // DEV-only
172182 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
173183};
174184type InitializedChunk<T> = {
175185 status: 'fulfilled',
176186 value: T,
177187 reason: null | FlightStreamController,
178188 _response: Response,
179- _debugInfo?: null | ReactDebugInfo,
189+ _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
190+ _debugInfo?: null | ReactDebugInfo, // DEV-only
180191 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
181192};
182193type InitializedStreamChunk<
@@ -186,15 +197,17 @@ type InitializedStreamChunk<
186197 value: T,
187198 reason: FlightStreamController,
188199 _response: Response,
189- _debugInfo?: null | ReactDebugInfo,
200+ _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
201+ _debugInfo?: null | ReactDebugInfo, // DEV-only
190202 then(resolve: (ReadableStream) => mixed, reject?: (mixed) => mixed): void,
191203};
192204type ErroredChunk<T> = {
193205 status: 'rejected',
194206 value: null,
195207 reason: mixed,
196208 _response: Response,
197- _debugInfo?: null | ReactDebugInfo,
209+ _children: Array<SomeChunk<any>> | ProfilingResult, // Profiling-only
210+ _debugInfo?: null | ReactDebugInfo, // DEV-only
198211 then(resolve: (T) => mixed, reject?: (mixed) => mixed): void,
199212};
200213type SomeChunk<T> =
@@ -216,6 +229,9 @@ function ReactPromise(
216229 this.value = value;
217230 this.reason = reason;
218231 this._response = response;
232+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
233+ this._children = [];
234+ }
219235 if (__DEV__) {
220236 this._debugInfo = null;
221237 }
@@ -548,9 +564,11 @@ type InitializationHandler = {
548564 errored: boolean,
549565};
550566let initializingHandler: null | InitializationHandler = null;
567+ let initializingChunk: null | BlockedChunk<any> = null;
551568
552569function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
553570 const prevHandler = initializingHandler;
571+ const prevChunk = initializingChunk;
554572 initializingHandler = null;
555573
556574 const resolvedModel = chunk.value;
@@ -563,6 +581,10 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
563581 cyclicChunk.value = null;
564582 cyclicChunk.reason = null;
565583
584+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
585+ initializingChunk = cyclicChunk;
586+ }
587+
566588 try {
567589 const value: T = parseModel(chunk._response, resolvedModel);
568590 // Invoke any listeners added while resolving this model. I.e. cyclic
@@ -595,6 +617,9 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
595617 erroredChunk.reason = error;
596618 } finally {
597619 initializingHandler = prevHandler;
620+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
621+ initializingChunk = prevChunk;
622+ }
598623 }
599624}
600625
@@ -622,6 +647,9 @@ export function reportGlobalError(response: Response, error: Error): void {
622647 triggerErrorOnChunk(chunk, error);
623648 }
624649 });
650+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
651+ flushComponentPerformance(getChunk(response, 0));
652+ }
625653}
626654
627655function nullRefGetter() {
@@ -1210,6 +1238,11 @@ function getOutlinedModel<T>(
12101238 const path = reference.split(':');
12111239 const id = parseInt(path[0], 16);
12121240 const chunk = getChunk(response, id);
1241+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
1242+ if (initializingChunk !== null && isArray(initializingChunk._children)) {
1243+ initializingChunk._children.push(chunk);
1244+ }
1245+ }
12131246 switch (chunk.status) {
12141247 case RESOLVED_MODEL:
12151248 initializeModelChunk(chunk);
@@ -1359,6 +1392,14 @@ function parseModelString(
13591392 // Lazy node
13601393 const id = parseInt(value.slice(2), 16);
13611394 const chunk = getChunk(response, id);
1395+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
1396+ if (
1397+ initializingChunk !== null &&
1398+ isArray(initializingChunk._children)
1399+ ) {
1400+ initializingChunk._children.push(chunk);
1401+ }
1402+ }
13621403 // We create a React.lazy wrapper around any lazy values.
13631404 // When passed into React, we'll know how to suspend on this.
13641405 return createLazyChunkWrapper(chunk);
@@ -1371,6 +1412,14 @@ function parseModelString(
13711412 }
13721413 const id = parseInt(value.slice(2), 16);
13731414 const chunk = getChunk(response, id);
1415+ if (enableProfilerTimer && enableComponentPerformanceTrack) {
1416+ if (
1417+ initializingChunk !== null &&
1418+ isArray(initializingChunk._children)
1419+ ) {
1420+ initializingChunk._children.push(chunk);
1421+ }
1422+ }
13741423 return chunk;
13751424 }
13761425 case 'S': {
@@ -2704,6 +2753,67 @@ function resolveTypedArray(
27042753 resolveBuffer(response, id, view);
27052754}
27062755
2756+ function flushComponentPerformance(root: SomeChunk<any>): number {
2757+ if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
2758+ return 0;
2759+ }
2760+ // Write performance.measure() entries for Server Components in tree order.
2761+ // This must be done at the end to collect the end time from the whole tree.
2762+ if (!isArray(root._children)) {
2763+ // We have already written this chunk. If this was a cycle, then this will
2764+ // be -Infinity and it won't contribute to the parent end time.
2765+ // If this was already emitted by another sibling then we reused the same
2766+ // chunk in two places. We should extend the current end time as if it was
2767+ // rendered as part of this tree.
2768+ const previousResult: ProfilingResult = root._children;
2769+ return previousResult.endTime;
2770+ }
2771+ const children = root._children;
2772+ if (root.status === RESOLVED_MODEL) {
2773+ // If the model is not initialized by now, do that now so we can find its
2774+ // children. This part is a little sketchy since it significantly changes
2775+ // the performance characteristics of the app by profiling.
2776+ initializeModelChunk(root);
2777+ }
2778+ const result: ProfilingResult = {endTime: -Infinity};
2779+ root._children = result;
2780+ let childrenEndTime = -Infinity;
2781+ for (let i = 0; i < children.length; i++) {
2782+ const childEndTime = flushComponentPerformance(children[i]);
2783+ if (childEndTime > childrenEndTime) {
2784+ childrenEndTime = childEndTime;
2785+ }
2786+ }
2787+ const debugInfo = root._debugInfo;
2788+ if (debugInfo) {
2789+ let endTime = 0;
2790+ for (let i = debugInfo.length - 1; i >= 0; i--) {
2791+ const info = debugInfo[i];
2792+ if (typeof info.time === 'number') {
2793+ endTime = info.time;
2794+ if (endTime > childrenEndTime) {
2795+ childrenEndTime = endTime;
2796+ }
2797+ }
2798+ if (typeof info.name === 'string' && i > 0) {
2799+ // $FlowFixMe: Refined.
2800+ const componentInfo: ReactComponentInfo = info;
2801+ const startTimeInfo = debugInfo[i - 1];
2802+ if (typeof startTimeInfo.time === 'number') {
2803+ const startTime = startTimeInfo.time;
2804+ logComponentRender(
2805+ componentInfo,
2806+ startTime,
2807+ endTime,
2808+ childrenEndTime,
2809+ );
2810+ }
2811+ }
2812+ }
2813+ }
2814+ return (result.endTime = childrenEndTime);
2815+ }
2816+
27072817function processFullBinaryRow(
27082818 response: Response,
27092819 id: number,
0 commit comments