Repository: facebook/react
Pull request: [Flight] Log "Server Requests" Track

Stacked on #33392.

This adds another track to the Performance Track called `"Server Requests"`.

<img width="1015" alt="Screenshot 2025-06-01 at 12 02 14 AM" src="https://github.com/user-attachments/assets/c4d164c4-cfdf-4e14-9a87-3f011f65fd20" />

This logs the flat list of I/O awaited on by Server Components. There will be other views that are more focused on what data blocks a specific Component or Suspense boundary but this is just the list of all the I/O basically so you can get an overview of those waterfalls without the noise of all the Component trees and rendering. It's similar to what the "Network" track is on the client.

I've been going back and forth on what to call this track but I went with `"Server Requests"` for now. The idea is that the name should communicate that this is something that happens on the server and is a pairing with the `"Server Components"` track. Although we don't use that feature, since it's missing granularity, it's also similar to "Server Timings".

Changed files:
filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -78,6 +78,7 @@ import {
   logComponentRender,
   logDedupedComponentRender,
   logComponentErrored,
+  logIOInfo,
 } from './ReactFlightPerformanceTrack';
 
 import {
@@ -2769,6 +2770,8 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
   ioInfo.start += response._timeOrigin;
   // $FlowFixMe[cannot-write]
   ioInfo.end += response._timeOrigin;
+
+  logIOInfo(ioInfo);
 }
 
 function resolveIOInfo(
@@ -2890,6 +2893,7 @@ function flushComponentPerformance(
         trackIdx,
         parentEndTime,
         previousEndTime,
+        response._rootEnvironmentName,
       );
     }
     // Since we didn't bump the track this time, we just return the same track.

filename: packages/react-client/src/ReactFlightPerformanceTrack.js
patch: @@ -9,7 +9,7 @@
 
 /* eslint-disable react-internal/no-production-logging */
 
-import type {ReactComponentInfo} from 'shared/ReactTypes';
+import type {ReactComponentInfo, ReactIOInfo} from 'shared/ReactTypes';
 
 import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
 
@@ -18,13 +18,22 @@ const supportsUserTiming =
   typeof console !== 'undefined' &&
   typeof console.timeStamp === 'function';
 
+const IO_TRACK = 'Server Requests ⚛';
 const COMPONENTS_TRACK = 'Server Components ⚛';
 
 export function markAllTracksInOrder() {
   if (supportsUserTiming) {
     // Ensure we create the Server Component track groups earlier than the Client Scheduler
     // and Client Components. We can always add the 0 time slot even if it's in the past.
     // That's still considered for ordering.
+    console.timeStamp(
+      'Server Requests Track',
+      0.001,
+      0.001,
+      IO_TRACK,
+      undefined,
+      'primary-light',
+    );
     console.timeStamp(
       'Server Components Track',
       0.001,
@@ -166,9 +175,13 @@ export function logDedupedComponentRender(
   trackIdx: number,
   startTime: number,
   endTime: number,
+  rootEnv: string,
 ): void {
   if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
+    const env = componentInfo.env;
     const name = componentInfo.name;
+    const isPrimaryEnv = env === rootEnv;
+    const color = isPrimaryEnv ? 'primary-light' : 'secondary-light';
     const entryName = name + ' [deduped]';
     const debugTask = componentInfo.debugTask;
     if (__DEV__ && debugTask) {
@@ -181,7 +194,7 @@ export function logDedupedComponentRender(
           endTime,
           trackNames[trackIdx],
           COMPONENTS_TRACK,
-          'tertiary-light',
+          color,
         ),
       );
     } else {
@@ -191,7 +204,54 @@ export function logDedupedComponentRender(
         endTime,
         trackNames[trackIdx],
         COMPONENTS_TRACK,
-        'tertiary-light',
+        color,
+      );
+    }
+  }
+}
+
+function getIOColor(
+  functionName: string,
+): 'tertiary-light' | 'tertiary' | 'tertiary-dark' {
+  // Add some color variation to be able to distinguish various sources.
+  switch (functionName.charCodeAt(0) % 3) {
+    case 0:
+      return 'tertiary-light';
+    case 1:
+      return 'tertiary';
+    default:
+      return 'tertiary-dark';
+  }
+}
+
+export function logIOInfo(ioInfo: ReactIOInfo): void {
+  const startTime = ioInfo.start;
+  const endTime = ioInfo.end;
+  if (supportsUserTiming && endTime >= 0) {
+    const name = ioInfo.name;
+    const debugTask = ioInfo.debugTask;
+    const color = getIOColor(name);
+    if (__DEV__ && debugTask) {
+      debugTask.run(
+        // $FlowFixMe[method-unbinding]
+        console.timeStamp.bind(
+          console,
+          name,
+          startTime < 0 ? 0 : startTime,
+          endTime,
+          IO_TRACK,
+          undefined,
+          color,
+        ),
+      );
+    } else {
+      console.timeStamp(
+        name,
+        startTime < 0 ? 0 : startTime,
+        endTime,
+        IO_TRACK,
+        undefined,
+        color,
       );
     }
   }