Skip to content

Commit 6e4f7c7

Browse files
authored
Profiler integration with interaction-tracking package (#13253)
* Updated suspense fixture to use new interaction-tracking API * Integrated Profiler API with interaction-tracking API (and added tests) * Pass interaction Set (rather than Array) to Profiler onRender callback * Removed some :any casts for enableInteractionTracking fields in FiberRoot type * Refactored threadID calculation into a helper method * Errors thrown by interaction tracking hooks use unhandledError to rethrow more safely. Reverted try/finally change to ReactTestRendererScheduling * Added a $FlowFixMe above the FiberRoot :any cast * Reduce overhead from calling work-started hook * Remove interaction-tracking wrap() references from unwind work in favor of managing suspense/interaction continuations in the scheduler * Moved the logic for calling work-started hook from performWorkOnRoot() to renderRoot() * Add interaction-tracking to bundle externals. Set feature flag to __PROFILE__ * Renamed the freezeInteractionCount flag and replaced one use-case with a method param * let -> const * Updated suspense fixture to handle recent API changes
1 parent 2967ebd commit 6e4f7c7

File tree

14 files changed

+2462
-945
lines changed

14 files changed

+2462
-945
lines changed

fixtures/unstable-async/suspense/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"dependencies": {
1111
"clipboard": "^1.7.1",
1212
"github-fork-ribbon-css": "^0.2.1",
13+
"interaction-tracking": "../../../build/node_modules/interaction-tracking",
1314
"react": "../../../build/node_modules/react",
1415
"react-dom": "../../../build/node_modules/react-dom",
1516
"react-draggable": "^3.0.5",

fixtures/unstable-async/suspense/src/components/App.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {track, wrap} from 'interaction-tracking';
12
import React, {Placeholder, PureComponent} from 'react';
23
import {createResource} from 'simple-cache-provider';
34
import {cache} from '../cache';
@@ -27,21 +28,31 @@ export default class App extends PureComponent {
2728
}
2829

2930
handleUserClick = id => {
30-
this.setState({
31-
currentId: id,
32-
});
33-
requestIdleCallback(() => {
34-
this.setState({
35-
showDetail: true,
36-
});
31+
track(`View ${id}`, performance.now(), () => {
32+
track(`View ${id} (high-pri)`, performance.now(), () =>
33+
this.setState({
34+
currentId: id,
35+
})
36+
);
37+
requestIdleCallback(
38+
wrap(() =>
39+
track(`View ${id} (low-pri)`, performance.now(), () =>
40+
this.setState({
41+
showDetail: true,
42+
})
43+
)
44+
)
45+
);
3746
});
3847
};
3948

4049
handleBackClick = () =>
41-
this.setState({
42-
currentId: null,
43-
showDetail: false,
44-
});
50+
track('View list', performance.now(), () =>
51+
this.setState({
52+
currentId: null,
53+
showDetail: false,
54+
})
55+
);
4556

4657
render() {
4758
const {currentId, showDetail} = this.state;

fixtures/unstable-async/suspense/src/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {track} from 'interaction-tracking';
12
import React, {Fragment, PureComponent} from 'react';
23
import {unstable_createRoot, render} from 'react-dom';
34
import {cache} from './cache';
@@ -64,11 +65,13 @@ class Debugger extends PureComponent {
6465
}
6566

6667
handleReset = () => {
67-
cache.invalidate();
68-
this.setState(state => ({
69-
requests: {},
70-
}));
71-
handleReset();
68+
track('Clear cache', () => {
69+
cache.invalidate();
70+
this.setState(state => ({
71+
requests: {},
72+
}));
73+
handleReset();
74+
});
7275
};
7376

7477
handleProgress = (url, progress, isPaused) => {

fixtures/unstable-async/suspense/yarn.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3482,6 +3482,9 @@ [email protected], inquirer@^3.0.6:
34823482
strip-ansi "^4.0.0"
34833483
through "^2.3.6"
34843484

3485+
interaction-tracking@../../../build/node_modules/interaction-tracking:
3486+
version "0.0.1"
3487+
34853488
34863489
version "1.2.0"
34873490
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"

packages/react-reconciler/src/ReactFiber.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {UpdateQueue} from './ReactUpdateQueue';
1717
import type {ContextDependency} from './ReactFiberNewContext';
1818

1919
import invariant from 'shared/invariant';
20+
import warningWithoutStack from 'shared/warningWithoutStack';
2021
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
2122
import {NoEffect} from 'shared/ReactSideEffectTags';
2223
import {
@@ -531,7 +532,7 @@ export function createFiberFromProfiler(
531532
typeof pendingProps.id !== 'string' ||
532533
typeof pendingProps.onRender !== 'function'
533534
) {
534-
invariant(
535+
warningWithoutStack(
535536
false,
536537
'Profiler must specify an "id" string and "onRender" function as props',
537538
);

packages/react-reconciler/src/ReactFiberCommitWork.js

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ import type {FiberRoot} from './ReactFiberRoot';
1919
import type {ExpirationTime} from './ReactFiberExpirationTime';
2020
import type {CapturedValue, CapturedError} from './ReactCapturedValue';
2121

22-
import {enableProfilerTimer, enableSuspense} from 'shared/ReactFeatureFlags';
22+
import {
23+
enableInteractionTracking,
24+
enableProfilerTimer,
25+
enableSuspense,
26+
} from 'shared/ReactFeatureFlags';
2327
import {
2428
ClassComponent,
2529
ClassComponentLazy,
@@ -777,7 +781,11 @@ function commitDeletion(current: Fiber): void {
777781
detachFiber(current);
778782
}
779783

780-
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
784+
function commitWork(
785+
root: FiberRoot,
786+
current: Fiber | null,
787+
finishedWork: Fiber,
788+
): void {
781789
if (!supportsMutation) {
782790
commitContainer(finishedWork);
783791
return;
@@ -836,14 +844,27 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
836844
case Profiler: {
837845
if (enableProfilerTimer) {
838846
const onRender = finishedWork.memoizedProps.onRender;
839-
onRender(
840-
finishedWork.memoizedProps.id,
841-
current === null ? 'mount' : 'update',
842-
finishedWork.actualDuration,
843-
finishedWork.treeBaseDuration,
844-
finishedWork.actualStartTime,
845-
getCommitTime(),
846-
);
847+
848+
if (enableInteractionTracking) {
849+
onRender(
850+
finishedWork.memoizedProps.id,
851+
current === null ? 'mount' : 'update',
852+
finishedWork.actualDuration,
853+
finishedWork.treeBaseDuration,
854+
finishedWork.actualStartTime,
855+
getCommitTime(),
856+
root.memoizedInteractions,
857+
);
858+
} else {
859+
onRender(
860+
finishedWork.memoizedProps.id,
861+
current === null ? 'mount' : 'update',
862+
finishedWork.actualDuration,
863+
finishedWork.treeBaseDuration,
864+
finishedWork.actualStartTime,
865+
getCommitTime(),
866+
);
867+
}
847868
}
848869
return;
849870
}

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 90 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010
import type {Fiber} from './ReactFiber';
1111
import type {ExpirationTime} from './ReactFiberExpirationTime';
1212
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
13+
import type {Interaction} from 'interaction-tracking/src/InteractionTracking';
1314

1415
import {noTimeout} from './ReactFiberHostConfig';
15-
1616
import {createHostRootFiber} from './ReactFiber';
1717
import {NoWork} from './ReactFiberExpirationTime';
18+
import {enableInteractionTracking} from 'shared/ReactFeatureFlags';
19+
import {getThreadID} from 'interaction-tracking';
1820

1921
// TODO: This should be lifted into the renderer.
2022
export type Batch = {
@@ -24,7 +26,9 @@ export type Batch = {
2426
_next: Batch | null,
2527
};
2628

27-
export type FiberRoot = {
29+
export type PendingInteractionMap = Map<ExpirationTime, Set<Interaction>>;
30+
31+
type BaseFiberRootProperties = {|
2832
// Any additional information from the host associated with this root.
2933
containerInfo: any,
3034
// Used only by persistent updates.
@@ -73,6 +77,26 @@ export type FiberRoot = {
7377
firstBatch: Batch | null,
7478
// Linked-list of roots
7579
nextScheduledRoot: FiberRoot | null,
80+
|};
81+
82+
// The following attributes are only used by interaction tracking builds.
83+
// They enable interactions to be associated with their async work,
84+
// And expose interaction metadata to the React DevTools Profiler plugin.
85+
// Note that these attributes are only defined when the enableInteractionTracking flag is enabled.
86+
type ProfilingOnlyFiberRootProperties = {|
87+
interactionThreadID: number,
88+
memoizedInteractions: Set<Interaction>,
89+
pendingInteractionMap: PendingInteractionMap,
90+
|};
91+
92+
// Exported FiberRoot type includes all properties,
93+
// To avoid requiring potentially error-prone :any casts throughout the project.
94+
// Profiling properties are only safe to access in profiling builds (when enableInteractionTracking is true).
95+
// The types are defined separately within this file to ensure they stay in sync.
96+
// (We don't have to use an inline :any cast when enableInteractionTracking is disabled.)
97+
export type FiberRoot = {
98+
...BaseFiberRootProperties,
99+
...ProfilingOnlyFiberRootProperties,
76100
};
77101

78102
export function createFiberRoot(
@@ -83,30 +107,69 @@ export function createFiberRoot(
83107
// Cyclic construction. This cheats the type system right now because
84108
// stateNode is any.
85109
const uninitializedFiber = createHostRootFiber(isAsync);
86-
const root = {
87-
current: uninitializedFiber,
88-
containerInfo: containerInfo,
89-
pendingChildren: null,
90-
91-
earliestPendingTime: NoWork,
92-
latestPendingTime: NoWork,
93-
earliestSuspendedTime: NoWork,
94-
latestSuspendedTime: NoWork,
95-
latestPingedTime: NoWork,
96-
97-
didError: false,
98-
99-
pendingCommitExpirationTime: NoWork,
100-
finishedWork: null,
101-
timeoutHandle: noTimeout,
102-
context: null,
103-
pendingContext: null,
104-
hydrate,
105-
nextExpirationTimeToWorkOn: NoWork,
106-
expirationTime: NoWork,
107-
firstBatch: null,
108-
nextScheduledRoot: null,
109-
};
110+
111+
let root;
112+
if (enableInteractionTracking) {
113+
root = ({
114+
current: uninitializedFiber,
115+
containerInfo: containerInfo,
116+
pendingChildren: null,
117+
118+
earliestPendingTime: NoWork,
119+
latestPendingTime: NoWork,
120+
earliestSuspendedTime: NoWork,
121+
latestSuspendedTime: NoWork,
122+
latestPingedTime: NoWork,
123+
124+
didError: false,
125+
126+
pendingCommitExpirationTime: NoWork,
127+
finishedWork: null,
128+
timeoutHandle: noTimeout,
129+
context: null,
130+
pendingContext: null,
131+
hydrate,
132+
nextExpirationTimeToWorkOn: NoWork,
133+
expirationTime: NoWork,
134+
firstBatch: null,
135+
nextScheduledRoot: null,
136+
137+
interactionThreadID: getThreadID(),
138+
memoizedInteractions: new Set(),
139+
pendingInteractionMap: new Map(),
140+
}: FiberRoot);
141+
} else {
142+
root = ({
143+
current: uninitializedFiber,
144+
containerInfo: containerInfo,
145+
pendingChildren: null,
146+
147+
earliestPendingTime: NoWork,
148+
latestPendingTime: NoWork,
149+
earliestSuspendedTime: NoWork,
150+
latestSuspendedTime: NoWork,
151+
latestPingedTime: NoWork,
152+
153+
didError: false,
154+
155+
pendingCommitExpirationTime: NoWork,
156+
finishedWork: null,
157+
timeoutHandle: noTimeout,
158+
context: null,
159+
pendingContext: null,
160+
hydrate,
161+
nextExpirationTimeToWorkOn: NoWork,
162+
expirationTime: NoWork,
163+
firstBatch: null,
164+
nextScheduledRoot: null,
165+
}: BaseFiberRootProperties);
166+
}
167+
110168
uninitializedFiber.stateNode = root;
111-
return root;
169+
170+
// The reason for the way the Flow types are structured in this file,
171+
// Is to avoid needing :any casts everywhere interaction-tracking fields are used.
172+
// Unfortunately that requires an :any cast for non-interaction-tracking capable builds.
173+
// $FlowFixMe Remove this :any cast and replace it with something better.
174+
return ((root: any): FiberRoot);
112175
}

0 commit comments

Comments
 (0)