@samitouri / QOS-React-2 / commits / d8919a0a68

[Flight] Log "Server Requests" Track (#33394)

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".

Sebastian Markbåge committed Jun 3, 2025 at 15:31 UTC d8919a0a6854715a4a77db24ed7a94a124487d86
2 files changed +67 -3
packages/react-client/src/ReactFlightClient.js
+4
@@ -78,6 +78,7 @@ import {
78 logComponentRender,
79 logDedupedComponentRender,
80 logComponentErrored,
81 + logIOInfo,
82 } from './ReactFlightPerformanceTrack';
83
84 import {
@@ -2769,6 +2770,8 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void {
2770 ioInfo.start += response._timeOrigin;
2771 // $FlowFixMe[cannot-write]
2772 ioInfo.end += response._timeOrigin;
2773 +
2774 + logIOInfo(ioInfo);
2775 }
2776
2777 function resolveIOInfo(
@@ -2890,6 +2893,7 @@ function flushComponentPerformance(
2893 trackIdx,
2894 parentEndTime,
2895 previousEndTime,
2896 + response._rootEnvironmentName,
2897 );
2898 }
2899 // Since we didn't bump the track this time, we just return the same track.
packages/react-client/src/ReactFlightPerformanceTrack.js
+63 -3
@@ -9,7 +9,7 @@
9
10 /* eslint-disable react-internal/no-production-logging */
11
12 -import type {ReactComponentInfo} from 'shared/ReactTypes';
12 +import type {ReactComponentInfo, ReactIOInfo} from 'shared/ReactTypes';
13
14 import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
15
@@ -18,6 +18,7 @@ const supportsUserTiming =
18 typeof console !== 'undefined' &&
19 typeof console.timeStamp === 'function';
20
21 +const IO_TRACK = 'Server Requests ⚛';
22 const COMPONENTS_TRACK = 'Server Components ⚛';
23
24 export function markAllTracksInOrder() {
@@ -25,6 +26,14 @@ export function markAllTracksInOrder() {
26 // Ensure we create the Server Component track groups earlier than the Client Scheduler
27 // and Client Components. We can always add the 0 time slot even if it's in the past.
28 // That's still considered for ordering.
29 + console.timeStamp(
30 + 'Server Requests Track',
31 + 0.001,
32 + 0.001,
33 + IO_TRACK,
34 + undefined,
35 + 'primary-light',
36 + );
37 console.timeStamp(
38 'Server Components Track',
39 0.001,
@@ -166,9 +175,13 @@ export function logDedupedComponentRender(
175 trackIdx: number,
176 startTime: number,
177 endTime: number,
178 + rootEnv: string,
179 ): void {
180 if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
181 + const env = componentInfo.env;
182 const name = componentInfo.name;
183 + const isPrimaryEnv = env === rootEnv;
184 + const color = isPrimaryEnv ? 'primary-light' : 'secondary-light';
185 const entryName = name + ' [deduped]';
186 const debugTask = componentInfo.debugTask;
187 if (__DEV__ && debugTask) {
@@ -181,7 +194,7 @@ export function logDedupedComponentRender(
194 endTime,
195 trackNames[trackIdx],
196 COMPONENTS_TRACK,
184 - 'tertiary-light',
197 + color,
198 ),
199 );
200 } else {
@@ -191,7 +204,54 @@ export function logDedupedComponentRender(
204 endTime,
205 trackNames[trackIdx],
206 COMPONENTS_TRACK,
194 - 'tertiary-light',
207 + color,
208 + );
209 + }
210 + }
211 +}
212 +
213 +function getIOColor(
214 + functionName: string,
215 +): 'tertiary-light' | 'tertiary' | 'tertiary-dark' {
216 + // Add some color variation to be able to distinguish various sources.
217 + switch (functionName.charCodeAt(0) % 3) {
218 + case 0:
219 + return 'tertiary-light';
220 + case 1:
221 + return 'tertiary';
222 + default:
223 + return 'tertiary-dark';
224 + }
225 +}
226 +
227 +export function logIOInfo(ioInfo: ReactIOInfo): void {
228 + const startTime = ioInfo.start;
229 + const endTime = ioInfo.end;
230 + if (supportsUserTiming && endTime >= 0) {
231 + const name = ioInfo.name;
232 + const debugTask = ioInfo.debugTask;
233 + const color = getIOColor(name);
234 + if (__DEV__ && debugTask) {
235 + debugTask.run(
236 + // $FlowFixMe[method-unbinding]
237 + console.timeStamp.bind(
238 + console,
239 + name,
240 + startTime < 0 ? 0 : startTime,
241 + endTime,
242 + IO_TRACK,
243 + undefined,
244 + color,
245 + ),
246 + );
247 + } else {
248 + console.timeStamp(
249 + name,
250 + startTime < 0 ? 0 : startTime,
251 + endTime,
252 + IO_TRACK,
253 + undefined,
254 + color,
255 );
256 }
257 }