@samitouri / QOS-React-2 / commits / 99be14c883

[Flight] Promote enableAsyncDebugInfo to stable without enableComponentPerformanceTrack (#33996)

There's a lot of overlap between `enableComponentPerformanceTrack` and `enableAsyncDebugInfo` because they both rely on timing information. The former is mainly emit timestamps for how long server components and awaits took. The latter how long I/O took. `enableAsyncDebugInfo` is currently primarily for the component performance track but its meta data is useful for other debug tools too. This promotes that flag to stable. However, `enableComponentPerformanceTrack` needs more work due to performance concerns with Chrome DevTools so I need to separate them. This keeps doing most of the timing tracking on the server but doesn't emit the per-server component time stamps when `enableComponentPerformanceTrack` is false.

Sebastian Markbåge committed Jul 25, 2025 at 04:59 UTC 99be14c883c5c83c9a087d37e19d93d6afb839ed
4 files changed +83 -50
packages/react-client/src/ReactFlightClient.js
+6 -7
@@ -3647,7 +3647,7 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
3647 // $FlowFixMe[cannot-write]
3648 ioInfo.end += response._timeOrigin;
3649
3650 - if (response._replayConsole) {
3650 + if (enableComponentPerformanceTrack && response._replayConsole) {
3651 const env = response._rootEnvironmentName;
3652 const promise = ioInfo.value;
3653 if (promise) {
@@ -4149,7 +4149,10 @@ function processFullStringRow(
4149 return;
4150 }
4151 case 78 /* "N" */: {
4152 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
4152 + if (
4153 + enableProfilerTimer &&
4154 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
4155 + ) {
4156 // Track the time origin for future debug info. We track it relative
4157 // to the current environment's time space.
4158 const timeOrigin: number = +row;
@@ -4169,11 +4172,7 @@ function processFullStringRow(
4172 // Fallthrough to share the error with Console entries.
4173 }
4174 case 74 /* "J" */: {
4172 - if (
4173 - enableProfilerTimer &&
4174 - enableComponentPerformanceTrack &&
4175 - enableAsyncDebugInfo
4176 - ) {
4175 + if (enableProfilerTimer && enableAsyncDebugInfo) {
4176 resolveIOInfo(response, id, row);
4177 return;
4178 }
packages/react-client/src/__tests__/ReactFlight-test.js
+2 -2
@@ -2898,7 +2898,7 @@ describe('ReactFlight', () => {
2898 );
2899 });
2900
2901 - // @gate enableAsyncIterableChildren
2901 + // @gate enableAsyncIterableChildren && enableComponentPerformanceTrack
2902 it('preserves debug info for server-to-server pass through of async iterables', async () => {
2903 let resolve;
2904 const iteratorPromise = new Promise(r => (resolve = r));
@@ -3727,7 +3727,7 @@ describe('ReactFlight', () => {
3727 expect(caughtError.digest).toBe('digest("my-error")');
3728 });
3729
3730 - // @gate __DEV__ && enableComponentPerformanceTrack
3730 + // @gate __DEV__ && enableComponentPerformanceTrack
3731 it('can render deep but cut off JSX in debug info', async () => {
3732 function createDeepJSX(n) {
3733 if (n <= 0) {
packages/react-server/src/ReactFlightServer.js
+74 -40
@@ -731,7 +731,10 @@ function RequestInstance(
731 }
732
733 let timeOrigin: number;
734 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
734 + if (
735 + enableProfilerTimer &&
736 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
737 + ) {
738 // We start by serializing the time origin. Any future timestamps will be
739 // emitted relatively to this origin. Instead of using performance.timeOrigin
740 // as this origin, we use the timestamp at the start of the request.
@@ -978,7 +981,10 @@ function serializeThenable(
981 task.keyPath, // the server component sequence continues through Promise-as-a-child.
982 task.implicitSlot,
983 request.abortableTasks,
981 - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0,
984 + enableProfilerTimer &&
985 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
986 + ? task.time
987 + : 0,
988 __DEV__ ? task.debugOwner : null,
989 __DEV__ ? task.debugStack : null,
990 __DEV__ ? task.debugTask : null,
@@ -1048,7 +1054,10 @@ function serializeThenable(
1054 },
1055 reason => {
1056 if (newTask.status === PENDING) {
1051 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
1057 + if (
1058 + enableProfilerTimer &&
1059 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1060 + ) {
1061 // If this is async we need to time when this task finishes.
1062 newTask.timed = true;
1063 }
@@ -1094,7 +1103,10 @@ function serializeReadableStream(
1103 task.keyPath,
1104 task.implicitSlot,
1105 request.abortableTasks,
1097 - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0,
1106 + enableProfilerTimer &&
1107 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1108 + ? task.time
1109 + : 0,
1110 __DEV__ ? task.debugOwner : null,
1111 __DEV__ ? task.debugStack : null,
1112 __DEV__ ? task.debugTask : null,
@@ -1186,7 +1198,10 @@ function serializeAsyncIterable(
1198 task.keyPath,
1199 task.implicitSlot,
1200 request.abortableTasks,
1189 - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0,
1201 + enableProfilerTimer &&
1202 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1203 + ? task.time
1204 + : 0,
1205 __DEV__ ? task.debugOwner : null,
1206 __DEV__ ? task.debugStack : null,
1207 __DEV__ ? task.debugTask : null,
@@ -1616,7 +1631,10 @@ function renderFunctionComponent<Props>(
1631 outlineComponentInfo(request, componentDebugInfo);
1632
1633 // Track when we started rendering this component.
1619 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
1634 + if (
1635 + enableProfilerTimer &&
1636 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
1637 + ) {
1638 advanceTaskTime(request, task, performance.now());
1639 }
1640
@@ -1686,12 +1704,7 @@ function renderFunctionComponent<Props>(
1704 throw null;
1705 }
1706
1689 - if (
1690 - __DEV__ ||
1691 - (enableProfilerTimer &&
1692 - enableComponentPerformanceTrack &&
1693 - enableAsyncDebugInfo)
1694 - ) {
1707 + if (__DEV__ || (enableProfilerTimer && enableAsyncDebugInfo)) {
1708 // Forward any debug information for any Promises that we use():ed during the render.
1709 // We do this at the end so that we don't keep doing this for each retry.
1710 const trackedThenables = getTrackedThenablesAfterRendering();
@@ -2016,7 +2029,10 @@ function deferTask(request: Request, task: Task): ReactJSONValue {
2029 task.keyPath, // unlike outlineModel this one carries along context
2030 task.implicitSlot,
2031 request.abortableTasks,
2019 - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0,
2032 + enableProfilerTimer &&
2033 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2034 + ? task.time
2035 + : 0,
2036 __DEV__ ? task.debugOwner : null,
2037 __DEV__ ? task.debugStack : null,
2038 __DEV__ ? task.debugTask : null,
@@ -2033,7 +2049,10 @@ function outlineTask(request: Request, task: Task): ReactJSONValue {
2049 task.keyPath, // unlike outlineModel this one carries along context
2050 task.implicitSlot,
2051 request.abortableTasks,
2036 - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0,
2052 + enableProfilerTimer &&
2053 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2054 + ? task.time
2055 + : 0,
2056 __DEV__ ? task.debugOwner : null,
2057 __DEV__ ? task.debugStack : null,
2058 __DEV__ ? task.debugTask : null,
@@ -2482,7 +2501,10 @@ function emitAsyncSequence(
2501 }
2502
2503 function pingTask(request: Request, task: Task): void {
2485 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
2504 + if (
2505 + enableProfilerTimer &&
2506 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2507 + ) {
2508 // If this was async we need to emit the time when it completes.
2509 task.timed = true;
2510 }
@@ -2587,7 +2609,10 @@ function createTask(
2609 | 'debugStack'
2610 | 'debugTask',
2611 >): any);
2590 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
2612 + if (
2613 + enableProfilerTimer &&
2614 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2615 + ) {
2616 task.timed = false;
2617 task.time = lastTimestamp;
2618 }
@@ -2795,7 +2820,8 @@ function outlineModel(request: Request, value: ReactClientValue): number {
2820 null, // The way we use outlining is for reusing an object.
2821 false, // It makes no sense for that use case to be contextual.
2822 request.abortableTasks,
2798 - enableProfilerTimer && enableComponentPerformanceTrack
2823 + enableProfilerTimer &&
2824 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
2825 ? performance.now() // TODO: This should really inherit the time from the task.
2826 : 0,
2827 null, // TODO: Currently we don't associate any debug information with
@@ -3041,7 +3067,8 @@ function serializeBlob(request: Request, blob: Blob): string {
3067 null,
3068 false,
3069 request.abortableTasks,
3044 - enableProfilerTimer && enableComponentPerformanceTrack
3070 + enableProfilerTimer &&
3071 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
3072 ? performance.now() // TODO: This should really inherit the time from the task.
3073 : 0,
3074 null, // TODO: Currently we don't associate any debug information with
@@ -3177,7 +3204,8 @@ function renderModel(
3204 task.keyPath,
3205 task.implicitSlot,
3206 request.abortableTasks,
3180 - enableProfilerTimer && enableComponentPerformanceTrack
3207 + enableProfilerTimer &&
3208 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
3209 ? task.time
3210 : 0,
3211 __DEV__ ? task.debugOwner : null,
@@ -5130,11 +5158,7 @@ function forwardDebugInfoFromThenable(
5158 forwardDebugInfo(request, task, debugInfo);
5159 }
5160 }
5133 - if (
5134 - enableProfilerTimer &&
5135 - enableComponentPerformanceTrack &&
5136 - enableAsyncDebugInfo
5137 - ) {
5161 + if (enableProfilerTimer && enableAsyncDebugInfo) {
5162 const sequence = getAsyncSequenceFromPromise(thenable);
5163 if (sequence !== null) {
5164 emitAsyncSequence(request, task, sequence, debugInfo, owner, stack);
@@ -5155,11 +5179,7 @@ function forwardDebugInfoFromCurrentContext(
5179 forwardDebugInfo(request, task, debugInfo);
5180 }
5181 }
5158 - if (
5159 - enableProfilerTimer &&
5160 - enableComponentPerformanceTrack &&
5161 - enableAsyncDebugInfo
5162 - ) {
5182 + if (enableProfilerTimer && enableAsyncDebugInfo) {
5183 const sequence = getCurrentAsyncSequence();
5184 if (sequence !== null) {
5185 emitAsyncSequence(request, task, sequence, debugInfo, null, null);
@@ -5182,11 +5202,7 @@ function forwardDebugInfoFromAbortedTask(request: Request, task: Task): void {
5202 forwardDebugInfo(request, task, debugInfo);
5203 }
5204 }
5185 - if (
5186 - enableProfilerTimer &&
5187 - enableComponentPerformanceTrack &&
5188 - enableAsyncDebugInfo
5189 - ) {
5205 + if (enableProfilerTimer && enableAsyncDebugInfo) {
5206 let thenable: null | Thenable<any> = null;
5207 if (typeof model.then === 'function') {
5208 thenable = (model: any);
@@ -5262,7 +5278,10 @@ function advanceTaskTime(
5278 task: Task,
5279 timestamp: number,
5280 ): void {
5265 - if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
5281 + if (
5282 + !enableProfilerTimer ||
5283 + (!enableComponentPerformanceTrack && !enableAsyncDebugInfo)
5284 + ) {
5285 return;
5286 }
5287 // Emits a timing chunk, if the new timestamp is higher than the previous timestamp of this task.
@@ -5278,7 +5297,10 @@ function advanceTaskTime(
5297 }
5298
5299 function markOperationEndTime(request: Request, task: Task, timestamp: number) {
5281 - if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
5300 + if (
5301 + !enableProfilerTimer ||
5302 + (!enableComponentPerformanceTrack && !enableAsyncDebugInfo)
5303 + ) {
5304 return;
5305 }
5306 // This is like advanceTaskTime() but always emits a timing chunk even if it doesn't advance.
@@ -5384,7 +5406,10 @@ function emitChunk(
5406 }
5407
5408 function erroredTask(request: Request, task: Task, error: mixed): void {
5387 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
5409 + if (
5410 + enableProfilerTimer &&
5411 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
5412 + ) {
5413 if (task.timed) {
5414 markOperationEndTime(request, task, performance.now());
5415 }
@@ -5467,7 +5492,10 @@ function retryTask(request: Request, task: Task): void {
5492 }
5493 }
5494 // We've finished rendering. Log the end time.
5470 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
5495 + if (
5496 + enableProfilerTimer &&
5497 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
5498 + ) {
5499 if (task.timed) {
5500 markOperationEndTime(request, task, performance.now());
5501 }
@@ -5605,7 +5633,10 @@ function finishAbortedTask(
5633 }
5634 forwardDebugInfoFromAbortedTask(request, task);
5635 // Track when we aborted this task as its end time.
5608 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
5636 + if (
5637 + enableProfilerTimer &&
5638 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
5639 + ) {
5640 if (task.timed) {
5641 markOperationEndTime(request, task, request.abortTime);
5642 }
@@ -5921,7 +5952,10 @@ export function abort(request: Request, reason: mixed): void {
5952 }
5953 try {
5954 request.status = ABORTING;
5924 - if (enableProfilerTimer && enableComponentPerformanceTrack) {
5955 + if (
5956 + enableProfilerTimer &&
5957 + (enableComponentPerformanceTrack || enableAsyncDebugInfo)
5958 + ) {
5959 request.abortTime = performance.now();
5960 }
5961 request.cacheController.abort(reason);
packages/shared/ReactFeatureFlags.js
+1 -1
@@ -247,7 +247,7 @@ export const enableProfilerCommitHooks = __PROFILE__;
247 // Phase param passed to onRender callback differentiates between an "update" and a "cascading-update".
248 export const enableProfilerNestedUpdatePhase = __PROFILE__;
249
250 -export const enableAsyncDebugInfo = __EXPERIMENTAL__;
250 +export const enableAsyncDebugInfo = true;
251
252 // Track which Fiber(s) schedule render work.
253 export const enableUpdaterTracking = __PROFILE__;