Skip to content

Commit 9175f4d

Browse files
author
Brian Vaughn
authoredSep 28, 2021
Scheduling Profiler: Show Suspense resource .displayName (#22451)
1 parent eba248c commit 9175f4d

File tree

12 files changed

+406
-329
lines changed

12 files changed

+406
-329
lines changed
 

‎packages/react-devtools-scheduling-profiler/src/EventTooltip.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
white-space: nowrap;
4747
}
4848

49-
.DetailsGridURL {
49+
.DetailsGridLongValue {
5050
word-break: break-all;
5151
max-height: 50vh;
5252
overflow: hidden;

‎packages/react-devtools-scheduling-profiler/src/EventTooltip.js

+7
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ const TooltipSuspenseEvent = ({
335335
componentName,
336336
duration,
337337
phase,
338+
promiseName,
338339
resolution,
339340
timestamp,
340341
warning,
@@ -356,6 +357,12 @@ const TooltipSuspenseEvent = ({
356357
{label}
357358
<div className={styles.Divider} />
358359
<div className={styles.DetailsGrid}>
360+
{promiseName !== null && (
361+
<>
362+
<div className={styles.DetailsGridLabel}>Resource:</div>
363+
<div className={styles.DetailsGridLongValue}>{promiseName}</div>
364+
</>
365+
)}
359366
<div className={styles.DetailsGridLabel}>Status:</div>
360367
<div>{resolution}</div>
361368
<div className={styles.DetailsGridLabel}>Timestamp:</div>

‎packages/react-devtools-scheduling-profiler/src/content-views/SuspenseEventsView.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export class SuspenseEventsView extends View {
8080
this._intrinsicSize = {
8181
width: duration,
8282
height: (this._maxDepth + 1) * ROW_WITH_BORDER_HEIGHT,
83+
hideScrollBarIfLessThanHeight: ROW_WITH_BORDER_HEIGHT,
8384
maxInitialHeight: ROW_WITH_BORDER_HEIGHT * MAX_ROWS_TO_SHOW_INITIALLY,
8485
};
8586
}
@@ -113,6 +114,7 @@ export class SuspenseEventsView extends View {
113114
depth,
114115
duration,
115116
phase,
117+
promiseName,
116118
resolution,
117119
timestamp,
118120
warning,
@@ -208,7 +210,9 @@ export class SuspenseEventsView extends View {
208210
);
209211

210212
let label = 'suspended';
211-
if (componentName != null) {
213+
if (promiseName != null) {
214+
label = promiseName;
215+
} else if (componentName != null) {
212216
label = `${componentName} ${label}`;
213217
}
214218
if (phase !== null) {

‎packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js

+40
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,46 @@ describe('preprocessData', () => {
11951195
`);
11961196
});
11971197

1198+
it('should include a suspended resource "displayName" if one is set', async () => {
1199+
let promise = null;
1200+
let resolvedValue = null;
1201+
function readValue(value) {
1202+
if (resolvedValue !== null) {
1203+
return resolvedValue;
1204+
} else if (promise === null) {
1205+
promise = Promise.resolve(true).then(() => {
1206+
resolvedValue = value;
1207+
});
1208+
promise.displayName = 'Testing displayName';
1209+
}
1210+
throw promise;
1211+
}
1212+
1213+
function Component() {
1214+
const value = readValue(123);
1215+
return value;
1216+
}
1217+
1218+
if (gate(flags => flags.enableSchedulingProfiler)) {
1219+
const testMarks = [creactCpuProfilerSample()];
1220+
1221+
const root = ReactDOM.createRoot(document.createElement('div'));
1222+
act(() =>
1223+
root.render(
1224+
<React.Suspense fallback="Loading...">
1225+
<Component />
1226+
</React.Suspense>,
1227+
),
1228+
);
1229+
1230+
testMarks.push(...createUserTimingData(clearedMarks));
1231+
1232+
const data = await preprocessData(testMarks);
1233+
expect(data.suspenseEvents).toHaveLength(1);
1234+
expect(data.suspenseEvents[0].promiseName).toBe('Testing displayName');
1235+
}
1236+
});
1237+
11981238
describe('warnings', () => {
11991239
describe('long event handlers', () => {
12001240
it('should not warn when React scedules a (sync) update inside of a short event handler', async () => {

‎packages/react-devtools-scheduling-profiler/src/import-worker/preprocessData.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -564,9 +564,13 @@ function processTimelineEvent(
564564

565565
// React Events - suspense
566566
else if (name.startsWith('--suspense-suspend-')) {
567-
const [id, componentName, phase, laneBitmaskString] = name
568-
.substr(19)
569-
.split('-');
567+
const [
568+
id,
569+
componentName,
570+
phase,
571+
laneBitmaskString,
572+
promiseName,
573+
] = name.substr(19).split('-');
570574
const lanes = getLanesFromTransportDecimalBitmask(laneBitmaskString);
571575

572576
const availableDepths = new Array(
@@ -595,6 +599,7 @@ function processTimelineEvent(
595599
duration: null,
596600
id,
597601
phase: ((phase: any): Phase),
602+
promiseName: promiseName || null,
598603
resolution: 'unresolved',
599604
resuspendTimestamps: null,
600605
timestamp: startTime,

‎packages/react-devtools-scheduling-profiler/src/types.js

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export type SuspenseEvent = {|
6060
duration: number | null,
6161
+id: string,
6262
+phase: Phase | null,
63+
promiseName: string | null,
6364
resolution: 'rejected' | 'resolved' | 'unresolved',
6465
resuspendTimestamps: Array<number> | null,
6566
+type: 'suspense',

‎packages/react-devtools-shared/src/devtools/views/ErrorBoundary/cache.js

+3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
7070
then(callback) {
7171
callbacks.add(callback);
7272
},
73+
74+
// Optional property used by Scheduling Profiler:
75+
displayName: `Searching GitHub issues for error "${errorMessage}"`,
7376
};
7477
const wake = () => {
7578
// This assumes they won't throw.

‎packages/react-devtools-shared/src/dynamicImportCache.js

+3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ export function loadModule(moduleLoaderFunction: ModuleLoaderFunction): Module {
7373
then(callback) {
7474
callbacks.add(callback);
7575
},
76+
77+
// Optional property used by Scheduling Profiler:
78+
displayName: `Loading module "${moduleLoaderFunction.name}"`,
7679
};
7780

7881
const wake = () => {

‎packages/react-devtools-shared/src/hookNamesCache.js

+3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ export function loadHookNames(
9292
then(callback) {
9393
callbacks.add(callback);
9494
},
95+
96+
// Optional property used by Scheduling Profiler:
97+
displayName: `Loading hook names for ${element.displayName || 'Unknown'}`,
9598
};
9699

97100
let timeoutID;

‎packages/react-devtools-shared/src/inspectedElementCache.js

+4
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,11 @@ export function inspectElement(
9494
then(callback) {
9595
callbacks.add(callback);
9696
},
97+
98+
// Optional property used by Scheduling Profiler:
99+
displayName: `Inspecting ${element.displayName || 'Unknown'}`,
97100
};
101+
98102
const wake = () => {
99103
// This assumes they won't throw.
100104
callbacks.forEach(callback => callback());

‎packages/react-reconciler/src/SchedulingProfiler.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,16 @@ export function markComponentSuspended(
194194
const id = getWakeableID(wakeable);
195195
const componentName = getComponentNameFromFiber(fiber) || 'Unknown';
196196
const phase = fiber.alternate === null ? 'mount' : 'update';
197+
198+
// Following the non-standard fn.displayName convention,
199+
// frameworks like Relay may also annotate Promises with a displayName,
200+
// describing what operation/data the thrown Promise is related to.
201+
// When this is available we should pass it along to the Scheduling Profiler.
202+
const displayName = (wakeable: any).displayName || '';
203+
197204
// TODO (scheduling profiler) Add component stack id
198205
markAndClear(
199-
`--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}`,
206+
`--suspense-${eventType}-${id}-${componentName}-${phase}-${lanes}-${displayName}`,
200207
);
201208
wakeable.then(
202209
() => markAndClear(`--suspense-resolved-${id}-${componentName}`),

0 commit comments

Comments
 (0)