Skip to content

Commit 1a8f92a

Browse files
authored
[DevTools] Track Tree Base Duration of Virtual Instances (#30817)
These don't have their own time since they don't take up any time to render but they show up in the tree for context. However they never render themselves. Their base tree time is the base time of their children. This way they take up the same space as their combined children in the Profiler tree. (Instead of leaving a blank line which they did before this PR.) The frontend doesn't track the difference between a virtual instance and a Fiber that didn't render this update. This might be a bit confusing as to why it didn't render. I add the word "client" to make it a bit clearer and works for both. We should probably have different verbiage here based on it is a Server Component or something else. <img width="1103" alt="Screenshot 2024-08-26 at 5 00 47 PM" src="https://github.com/user-attachments/assets/87b811d4-7024-466a-845d-542493ed3ca2"> I also took the opportunity to remove idToTreeBaseDurationMap and idToRootMap maps. Cloning the Map isn't really all that super fast anyway and it means we have to maintain the map continuously as we render. Instead, we can track it on the instances and then walk the instances to create a snapshot when starting to profile. This isn't as fast but really fast too and requires less bookkeeping while rendering instead which is more sensitive than that one snapshot in the beginning.
1 parent 246d7bf commit 1a8f92a

File tree

3 files changed

+77
-58
lines changed

3 files changed

+77
-58
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

+75-56
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ type FiberInstance = {
164164
source: null | string | Error | Source, // source location of this component function, or owned child stack
165165
errors: null | Map<string, number>, // error messages and count
166166
warnings: null | Map<string, number>, // warning messages and count
167+
treeBaseDuration: number, // the profiled time of the last render of this subtree
167168
data: Fiber, // one of a Fiber pair
168169
};
169170

@@ -179,6 +180,7 @@ function createFiberInstance(fiber: Fiber): FiberInstance {
179180
source: null,
180181
errors: null,
181182
warnings: null,
183+
treeBaseDuration: 0,
182184
data: fiber,
183185
};
184186
}
@@ -202,6 +204,7 @@ type VirtualInstance = {
202204
// that old errors/warnings don't disappear when the instance is refreshed.
203205
errors: null | Map<string, number>, // error messages and count
204206
warnings: null | Map<string, number>, // warning messages and count
207+
treeBaseDuration: number, // the profiled time of the last render of this subtree
205208
// The latest info for this instance. This can be updated over time and the
206209
// same info can appear in more than once ServerComponentInstance.
207210
data: ReactComponentInfo,
@@ -221,6 +224,7 @@ function createVirtualInstance(
221224
source: null,
222225
errors: null,
223226
warnings: null,
227+
treeBaseDuration: 0,
224228
data: debugEntry,
225229
};
226230
}
@@ -1355,16 +1359,6 @@ export function attach(
13551359
}
13561360
}
13571361

1358-
// When profiling is supported, we store the latest tree base durations for each Fiber.
1359-
// This is so that we can quickly capture a snapshot of those values if profiling starts.
1360-
// If we didn't store these values, we'd have to crawl the tree when profiling started,
1361-
// and use a slow path to find each of the current Fibers.
1362-
const idToTreeBaseDurationMap: Map<number, number> = new Map();
1363-
1364-
// When profiling is supported, we store the latest tree base durations for each Fiber.
1365-
// This map enables us to filter these times by root when sending them to the frontend.
1366-
const idToRootMap: Map<number, number> = new Map();
1367-
13681362
// When a mount or update is in progress, this value tracks the root that is being operated on.
13691363
let currentRootID: number = -1;
13701364

@@ -2211,9 +2205,7 @@ export function attach(
22112205
}
22122206

22132207
if (isProfilingSupported) {
2214-
idToRootMap.set(id, currentRootID);
2215-
2216-
recordProfilingDurations(fiber);
2208+
recordProfilingDurations(fiberInstance);
22172209
}
22182210
return fiberInstance;
22192211
}
@@ -2226,8 +2218,6 @@ export function attach(
22262218

22272219
idToDevToolsInstanceMap.set(id, instance);
22282220

2229-
const isProfilingSupported = false; // TODO: Support Tree Base Duration Based on Children.
2230-
22312221
const componentInfo = instance.data;
22322222

22332223
const key =
@@ -2274,12 +2264,6 @@ export function attach(
22742264
pushOperation(ownerID);
22752265
pushOperation(displayNameStringID);
22762266
pushOperation(keyStringID);
2277-
2278-
if (isProfilingSupported) {
2279-
idToRootMap.set(id, currentRootID);
2280-
// TODO: Include tree base duration of children somehow.
2281-
// recordProfilingDurations(...);
2282-
}
22832267
}
22842268

22852269
function recordUnmount(fiberInstance: FiberInstance): void {
@@ -2314,12 +2298,6 @@ export function attach(
23142298
}
23152299

23162300
untrackFiber(fiberInstance);
2317-
2318-
const isProfilingSupported = fiber.hasOwnProperty('treeBaseDuration');
2319-
if (isProfilingSupported) {
2320-
idToRootMap.delete(id);
2321-
idToTreeBaseDurationMap.delete(id);
2322-
}
23232301
}
23242302

23252303
// Running state of the remaining children from the previous version of this parent that
@@ -2430,6 +2408,8 @@ export function attach(
24302408
traceNearestHostComponentUpdate,
24312409
virtualLevel + 1,
24322410
);
2411+
// Must be called after all children have been appended.
2412+
recordVirtualProfilingDurations(virtualInstance);
24332413
} finally {
24342414
reconcilingParent = stashedParent;
24352415
previouslyReconciledSibling = stashedPrevious;
@@ -2445,12 +2425,6 @@ export function attach(
24452425

24462426
const id = instance.id;
24472427
pendingRealUnmountedIDs.push(id);
2448-
2449-
const isProfilingSupported = false; // TODO: Profiling support.
2450-
if (isProfilingSupported) {
2451-
idToRootMap.delete(id);
2452-
idToTreeBaseDurationMap.delete(id);
2453-
}
24542428
}
24552429

24562430
function mountVirtualChildrenRecursively(
@@ -2684,11 +2658,12 @@ export function attach(
26842658
removeChild(instance);
26852659
}
26862660

2687-
function recordProfilingDurations(fiber: Fiber) {
2688-
const id = getFiberIDThrows(fiber);
2661+
function recordProfilingDurations(fiberInstance: FiberInstance) {
2662+
const id = fiberInstance.id;
2663+
const fiber = fiberInstance.data;
26892664
const {actualDuration, treeBaseDuration} = fiber;
26902665

2691-
idToTreeBaseDurationMap.set(id, treeBaseDuration || 0);
2666+
fiberInstance.treeBaseDuration = treeBaseDuration || 0;
26922667

26932668
if (isProfiling) {
26942669
const {alternate} = fiber;
@@ -2751,6 +2726,38 @@ export function attach(
27512726
}
27522727
}
27532728

2729+
function recordVirtualProfilingDurations(virtualInstance: VirtualInstance) {
2730+
const id = virtualInstance.id;
2731+
2732+
let treeBaseDuration = 0;
2733+
// Add up the base duration of the child instances. The virtual base duration
2734+
// will be the same as children's duration since we don't take up any render
2735+
// time in the virtual instance.
2736+
for (
2737+
let child = virtualInstance.firstChild;
2738+
child !== null;
2739+
child = child.nextSibling
2740+
) {
2741+
treeBaseDuration += child.treeBaseDuration;
2742+
}
2743+
2744+
if (isProfiling) {
2745+
const previousTreeBaseDuration = virtualInstance.treeBaseDuration;
2746+
if (treeBaseDuration !== previousTreeBaseDuration) {
2747+
// Tree base duration updates are included in the operations typed array.
2748+
// So we have to convert them from milliseconds to microseconds so we can send them as ints.
2749+
const convertedTreeBaseDuration = Math.floor(
2750+
(treeBaseDuration || 0) * 1000,
2751+
);
2752+
pushOperation(TREE_OPERATION_UPDATE_TREE_BASE_DURATION);
2753+
pushOperation(id);
2754+
pushOperation(convertedTreeBaseDuration);
2755+
}
2756+
}
2757+
2758+
virtualInstance.treeBaseDuration = treeBaseDuration;
2759+
}
2760+
27542761
function recordResetChildren(parentInstance: DevToolsInstance) {
27552762
if (__DEBUG__) {
27562763
if (
@@ -2818,6 +2825,8 @@ export function attach(
28182825
) {
28192826
recordResetChildren(virtualInstance);
28202827
}
2828+
// Must be called after all children have been appended.
2829+
recordVirtualProfilingDurations(virtualInstance);
28212830
} finally {
28222831
unmountRemainingChildren();
28232832
reconcilingParent = stashedParent;
@@ -3265,11 +3274,11 @@ export function attach(
32653274
}
32663275
}
32673276

3268-
if (shouldIncludeInTree) {
3277+
if (fiberInstance !== null) {
32693278
const isProfilingSupported =
32703279
nextFiber.hasOwnProperty('treeBaseDuration');
32713280
if (isProfilingSupported) {
3272-
recordProfilingDurations(nextFiber);
3281+
recordProfilingDurations(fiberInstance);
32733282
}
32743283
}
32753284
if (shouldResetChildren) {
@@ -5121,8 +5130,8 @@ export function attach(
51215130
let currentCommitProfilingMetadata: CommitProfilingData | null = null;
51225131
let displayNamesByRootID: DisplayNamesByRootID | null = null;
51235132
let idToContextsMap: Map<number, any> | null = null;
5124-
let initialTreeBaseDurationsMap: Map<number, number> | null = null;
5125-
let initialIDToRootMap: Map<number, number> | null = null;
5133+
let initialTreeBaseDurationsMap: Map<number, Array<[number, number]>> | null =
5134+
null;
51265135
let isProfiling: boolean = false;
51275136
let profilingStartTime: number = 0;
51285137
let recordChangeDescriptions: boolean = false;
@@ -5141,24 +5150,15 @@ export function attach(
51415150
rootToCommitProfilingMetadataMap.forEach(
51425151
(commitProfilingMetadata, rootID) => {
51435152
const commitData: Array<CommitDataBackend> = [];
5144-
const initialTreeBaseDurations: Array<[number, number]> = [];
51455153

51465154
const displayName =
51475155
(displayNamesByRootID !== null && displayNamesByRootID.get(rootID)) ||
51485156
'Unknown';
51495157

5150-
if (initialTreeBaseDurationsMap != null) {
5151-
initialTreeBaseDurationsMap.forEach((treeBaseDuration, id) => {
5152-
if (
5153-
initialIDToRootMap != null &&
5154-
initialIDToRootMap.get(id) === rootID
5155-
) {
5156-
// We don't need to convert milliseconds to microseconds in this case,
5157-
// because the profiling summary is JSON serialized.
5158-
initialTreeBaseDurations.push([id, treeBaseDuration]);
5159-
}
5160-
});
5161-
}
5158+
const initialTreeBaseDurations: Array<[number, number]> =
5159+
(initialTreeBaseDurationsMap !== null &&
5160+
initialTreeBaseDurationsMap.get(rootID)) ||
5161+
[];
51625162

51635163
commitProfilingMetadata.forEach((commitProfilingData, commitIndex) => {
51645164
const {
@@ -5245,6 +5245,22 @@ export function attach(
52455245
};
52465246
}
52475247

5248+
function snapshotTreeBaseDurations(
5249+
instance: DevToolsInstance,
5250+
target: Array<[number, number]>,
5251+
) {
5252+
// We don't need to convert milliseconds to microseconds in this case,
5253+
// because the profiling summary is JSON serialized.
5254+
target.push([instance.id, instance.treeBaseDuration]);
5255+
for (
5256+
let child = instance.firstChild;
5257+
child !== null;
5258+
child = child.nextSibling
5259+
) {
5260+
snapshotTreeBaseDurations(child, target);
5261+
}
5262+
}
5263+
52485264
function startProfiling(shouldRecordChangeDescriptions: boolean) {
52495265
if (isProfiling) {
52505266
return;
@@ -5257,16 +5273,19 @@ export function attach(
52575273
// since either of these may change during the profiling session
52585274
// (e.g. when a fiber is re-rendered or when a fiber gets removed).
52595275
displayNamesByRootID = new Map();
5260-
initialTreeBaseDurationsMap = new Map(idToTreeBaseDurationMap);
5261-
initialIDToRootMap = new Map(idToRootMap);
5276+
initialTreeBaseDurationsMap = new Map();
52625277
idToContextsMap = new Map();
52635278

52645279
hook.getFiberRoots(rendererID).forEach(root => {
5265-
const rootID = getFiberIDThrows(root.current);
5280+
const rootInstance = getFiberInstanceThrows(root.current);
5281+
const rootID = rootInstance.id;
52665282
((displayNamesByRootID: any): DisplayNamesByRootID).set(
52675283
rootID,
52685284
getDisplayNameForRoot(root.current),
52695285
);
5286+
const initialTreeBaseDurations: Array<[number, number]> = [];
5287+
snapshotTreeBaseDurations(rootInstance, initialTreeBaseDurations);
5288+
(initialTreeBaseDurationsMap: any).set(rootID, initialTreeBaseDurations);
52705289

52715290
if (shouldRecordChangeDescriptions) {
52725291
// Record all contexts at the time profiling is started.

packages/react-devtools-shared/src/devtools/views/Profiler/HoveredFiberInfo.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export default function HoveredFiberInfo({fiberData}: Props): React.Node {
9393
)}
9494

9595
<div className={styles.Content}>
96-
{renderDurationInfo || <div>Did not render.</div>}
96+
{renderDurationInfo || <div>Did not client render.</div>}
9797

9898
<WhatChanged fiberID={id} />
9999
</div>

packages/react-devtools-shared/src/devtools/views/Profiler/SidebarSelectedFiberInfo.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ export default function SidebarSelectedFiberInfo(): React.Node {
142142
</div>
143143
)}
144144
{listItems.length === 0 && (
145-
<div>Did not render during this profiling session.</div>
145+
<div>Did not render on the client during this profiling session.</div>
146146
)}
147147
</div>
148148
</Fragment>

0 commit comments

Comments
 (0)