Skip to content

Commit d2a288f

Browse files
authored
Include Component Props in Performance Track (#33655)
Similar to how we can include a Promise resolved value we can include Component Props. For now I left out props for Client Components for perf unless they error. I'll try it for Client Components in general in a separate PR. <img width="730" alt="Screenshot 2025-06-26 at 5 54 29 PM" src="https://github.com/user-attachments/assets/f0c86911-2899-4b5f-b45f-5326bdbc630f" /> <img width="762" alt="Screenshot 2025-06-26 at 5 54 12 PM" src="https://github.com/user-attachments/assets/97540d19-5950-4346-99e6-066af086040e" />
1 parent 4db4b21 commit d2a288f

File tree

5 files changed

+245
-139
lines changed

5 files changed

+245
-139
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack'
100100

101101
import {injectInternals} from './ReactFlightClientDevToolsHook';
102102

103-
import {OMITTED_PROP_ERROR} from './ReactFlightPropertyAccess';
103+
import {OMITTED_PROP_ERROR} from 'shared/ReactFlightPropertyAccess';
104104

105105
import ReactVersion from 'shared/ReactVersion';
106106

packages/react-client/src/ReactFlightPerformanceTrack.js

Lines changed: 35 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import type {
1717

1818
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
1919

20-
import {OMITTED_PROP_ERROR} from './ReactFlightPropertyAccess';
21-
22-
import hasOwnProperty from 'shared/hasOwnProperty';
23-
import isArray from 'shared/isArray';
20+
import {
21+
addValueToProperties,
22+
addObjectToProperties,
23+
} from 'shared/ReactPerformanceTrackProperties';
2424

2525
const supportsUserTiming =
2626
enableProfilerTimer &&
@@ -33,127 +33,6 @@ const supportsUserTiming =
3333
const IO_TRACK = 'Server Requests ⚛';
3434
const COMPONENTS_TRACK = 'Server Components ⚛';
3535

36-
const EMPTY_ARRAY = 0;
37-
const COMPLEX_ARRAY = 1;
38-
const PRIMITIVE_ARRAY = 2; // Primitive values only
39-
const ENTRIES_ARRAY = 3; // Tuple arrays of string and value (like Headers, Map, etc)
40-
function getArrayKind(array: Object): 0 | 1 | 2 | 3 {
41-
let kind = EMPTY_ARRAY;
42-
for (let i = 0; i < array.length; i++) {
43-
const value = array[i];
44-
if (typeof value === 'object' && value !== null) {
45-
if (
46-
isArray(value) &&
47-
value.length === 2 &&
48-
typeof value[0] === 'string'
49-
) {
50-
// Key value tuple
51-
if (kind !== EMPTY_ARRAY && kind !== ENTRIES_ARRAY) {
52-
return COMPLEX_ARRAY;
53-
}
54-
kind = ENTRIES_ARRAY;
55-
} else {
56-
return COMPLEX_ARRAY;
57-
}
58-
} else if (typeof value === 'function') {
59-
return COMPLEX_ARRAY;
60-
} else if (typeof value === 'string' && value.length > 50) {
61-
return COMPLEX_ARRAY;
62-
} else if (kind !== EMPTY_ARRAY && kind !== PRIMITIVE_ARRAY) {
63-
return COMPLEX_ARRAY;
64-
} else {
65-
kind = PRIMITIVE_ARRAY;
66-
}
67-
}
68-
return kind;
69-
}
70-
71-
function addObjectToProperties(
72-
object: Object,
73-
properties: Array<[string, string]>,
74-
indent: number,
75-
): void {
76-
for (const key in object) {
77-
if (hasOwnProperty.call(object, key) && key[0] !== '_') {
78-
const value = object[key];
79-
addValueToProperties(key, value, properties, indent);
80-
}
81-
}
82-
}
83-
84-
function addValueToProperties(
85-
propertyName: string,
86-
value: mixed,
87-
properties: Array<[string, string]>,
88-
indent: number,
89-
): void {
90-
let desc;
91-
switch (typeof value) {
92-
case 'object':
93-
if (value === null) {
94-
desc = 'null';
95-
break;
96-
} else {
97-
// $FlowFixMe[method-unbinding]
98-
const objectToString = Object.prototype.toString.call(value);
99-
let objectName = objectToString.slice(8, objectToString.length - 1);
100-
if (objectName === 'Array') {
101-
const array: Array<any> = (value: any);
102-
const kind = getArrayKind(array);
103-
if (kind === PRIMITIVE_ARRAY || kind === EMPTY_ARRAY) {
104-
desc = JSON.stringify(array);
105-
break;
106-
} else if (kind === ENTRIES_ARRAY) {
107-
properties.push(['\xa0\xa0'.repeat(indent) + propertyName, '']);
108-
for (let i = 0; i < array.length; i++) {
109-
const entry = array[i];
110-
addValueToProperties(entry[0], entry[1], properties, indent + 1);
111-
}
112-
return;
113-
}
114-
}
115-
if (objectName === 'Object') {
116-
const proto: any = Object.getPrototypeOf(value);
117-
if (proto && typeof proto.constructor === 'function') {
118-
objectName = proto.constructor.name;
119-
}
120-
}
121-
properties.push([
122-
'\xa0\xa0'.repeat(indent) + propertyName,
123-
objectName === 'Object' ? '' : objectName,
124-
]);
125-
if (indent < 3) {
126-
addObjectToProperties(value, properties, indent + 1);
127-
}
128-
return;
129-
}
130-
case 'function':
131-
if (value.name === '') {
132-
desc = '() => {}';
133-
} else {
134-
desc = value.name + '() {}';
135-
}
136-
break;
137-
case 'string':
138-
if (value === OMITTED_PROP_ERROR) {
139-
desc = '...';
140-
} else {
141-
desc = JSON.stringify(value);
142-
}
143-
break;
144-
case 'undefined':
145-
desc = 'undefined';
146-
break;
147-
case 'boolean':
148-
desc = value ? 'true' : 'false';
149-
break;
150-
default:
151-
// eslint-disable-next-line react-internal/safe-string-coercion
152-
desc = String(value);
153-
}
154-
properties.push(['\xa0\xa0'.repeat(indent) + propertyName, desc]);
155-
}
156-
15736
export function markAllTracksInOrder() {
15837
if (supportsUserTiming) {
15938
// Ensure we create the Server Component track groups earlier than the Client Scheduler
@@ -222,17 +101,27 @@ export function logComponentRender(
222101
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
223102
const debugTask = componentInfo.debugTask;
224103
if (__DEV__ && debugTask) {
104+
const properties: Array<[string, string]> = [];
105+
if (componentInfo.key != null) {
106+
addValueToProperties('key', componentInfo.key, properties, 0);
107+
}
108+
if (componentInfo.props != null) {
109+
addObjectToProperties(componentInfo.props, properties, 0);
110+
}
225111
debugTask.run(
226112
// $FlowFixMe[method-unbinding]
227-
console.timeStamp.bind(
228-
console,
229-
entryName,
230-
startTime < 0 ? 0 : startTime,
231-
childrenEndTime,
232-
trackNames[trackIdx],
233-
COMPONENTS_TRACK,
234-
color,
235-
),
113+
performance.measure.bind(performance, entryName, {
114+
start: startTime < 0 ? 0 : startTime,
115+
end: childrenEndTime,
116+
detail: {
117+
devtools: {
118+
color: color,
119+
track: trackNames[trackIdx],
120+
trackGroup: COMPONENTS_TRACK,
121+
properties,
122+
},
123+
},
124+
}),
236125
);
237126
} else {
238127
console.timeStamp(
@@ -268,6 +157,12 @@ export function logComponentAborted(
268157
'The stream was aborted before this Component finished rendering.',
269158
],
270159
];
160+
if (componentInfo.key != null) {
161+
addValueToProperties('key', componentInfo.key, properties, 0);
162+
}
163+
if (componentInfo.props != null) {
164+
addObjectToProperties(componentInfo.props, properties, 0);
165+
}
271166
performance.measure(entryName, {
272167
start: startTime < 0 ? 0 : startTime,
273168
end: childrenEndTime,
@@ -319,6 +214,12 @@ export function logComponentErrored(
319214
: // eslint-disable-next-line react-internal/safe-string-coercion
320215
String(error);
321216
const properties = [['Error', message]];
217+
if (componentInfo.key != null) {
218+
addValueToProperties('key', componentInfo.key, properties, 0);
219+
}
220+
if (componentInfo.props != null) {
221+
addObjectToProperties(componentInfo.props, properties, 0);
222+
}
322223
performance.measure(entryName, {
323224
start: startTime < 0 ? 0 : startTime,
324225
end: childrenEndTime,

packages/react-reconciler/src/ReactFiberPerformanceTrack.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ import {
2626
includesOnlyHydrationOrOffscreenLanes,
2727
} from './ReactFiberLane';
2828

29+
import {
30+
addValueToProperties,
31+
addObjectToProperties,
32+
} from 'shared/ReactPerformanceTrackProperties';
33+
2934
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
3035

3136
const supportsUserTiming =
@@ -239,7 +244,7 @@ export function logComponentErrored(
239244
typeof performance.measure === 'function'
240245
) {
241246
let debugTask: ?ConsoleTask = null;
242-
const properties = [];
247+
const properties: Array<[string, string]> = [];
243248
for (let i = 0; i < errors.length; i++) {
244249
const capturedValue = errors[i];
245250
if (debugTask == null && capturedValue.source !== null) {
@@ -261,6 +266,12 @@ export function logComponentErrored(
261266
String(error);
262267
properties.push(['Error', message]);
263268
}
269+
if (fiber.key !== null) {
270+
addValueToProperties('key', fiber.key, properties, 0);
271+
}
272+
if (fiber.memoizedProps !== null) {
273+
addObjectToProperties(fiber.memoizedProps, properties, 0);
274+
}
264275
if (debugTask == null) {
265276
// If the captured values don't have a debug task, fallback to the
266277
// error boundary itself.
@@ -320,7 +331,7 @@ function logComponentEffectErrored(
320331
// $FlowFixMe[method-unbinding]
321332
typeof performance.measure === 'function'
322333
) {
323-
const properties = [];
334+
const properties: Array<[string, string]> = [];
324335
for (let i = 0; i < errors.length; i++) {
325336
const capturedValue = errors[i];
326337
const error = capturedValue.value;
@@ -334,6 +345,12 @@ function logComponentEffectErrored(
334345
String(error);
335346
properties.push(['Error', message]);
336347
}
348+
if (fiber.key !== null) {
349+
addValueToProperties('key', fiber.key, properties, 0);
350+
}
351+
if (fiber.memoizedProps !== null) {
352+
addObjectToProperties(fiber.memoizedProps, properties, 0);
353+
}
337354
const options = {
338355
start: startTime,
339356
end: endTime,
@@ -804,7 +821,7 @@ export function logRecoveredRenderPhase(
804821
// $FlowFixMe[method-unbinding]
805822
typeof performance.measure === 'function'
806823
) {
807-
const properties = [];
824+
const properties: Array<[string, string]> = [];
808825
for (let i = 0; i < recoverableErrors.length; i++) {
809826
const capturedValue = recoverableErrors[i];
810827
const error = capturedValue.value;
@@ -928,7 +945,7 @@ export function logCommitErrored(
928945
// $FlowFixMe[method-unbinding]
929946
typeof performance.measure === 'function'
930947
) {
931-
const properties = [];
948+
const properties: Array<[string, string]> = [];
932949
for (let i = 0; i < errors.length; i++) {
933950
const capturedValue = errors[i];
934951
const error = capturedValue.value;

0 commit comments

Comments
 (0)