Skip to content

Commit

Permalink
Profiler integration with interaction-tracking package (#13253)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bvaughn authored Aug 29, 2018
1 parent 2967ebd commit 6e4f7c7
Show file tree
Hide file tree
Showing 14 changed files with 2,462 additions and 945 deletions.
1 change: 1 addition & 0 deletions fixtures/unstable-async/suspense/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"dependencies": {
"clipboard": "^1.7.1",
"github-fork-ribbon-css": "^0.2.1",
"interaction-tracking": "../../../build/node_modules/interaction-tracking",
"react": "../../../build/node_modules/react",
"react-dom": "../../../build/node_modules/react-dom",
"react-draggable": "^3.0.5",
Expand Down
33 changes: 22 additions & 11 deletions fixtures/unstable-async/suspense/src/components/App.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {track, wrap} from 'interaction-tracking';
import React, {Placeholder, PureComponent} from 'react';
import {createResource} from 'simple-cache-provider';
import {cache} from '../cache';
Expand Down Expand Up @@ -27,21 +28,31 @@ export default class App extends PureComponent {
}

handleUserClick = id => {
this.setState({
currentId: id,
});
requestIdleCallback(() => {
this.setState({
showDetail: true,
});
track(`View ${id}`, performance.now(), () => {
track(`View ${id} (high-pri)`, performance.now(), () =>
this.setState({
currentId: id,
})
);
requestIdleCallback(
wrap(() =>
track(`View ${id} (low-pri)`, performance.now(), () =>
this.setState({
showDetail: true,
})
)
)
);
});
};

handleBackClick = () =>
this.setState({
currentId: null,
showDetail: false,
});
track('View list', performance.now(), () =>
this.setState({
currentId: null,
showDetail: false,
})
);

render() {
const {currentId, showDetail} = this.state;
Expand Down
13 changes: 8 additions & 5 deletions fixtures/unstable-async/suspense/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {track} from 'interaction-tracking';
import React, {Fragment, PureComponent} from 'react';
import {unstable_createRoot, render} from 'react-dom';
import {cache} from './cache';
Expand Down Expand Up @@ -64,11 +65,13 @@ class Debugger extends PureComponent {
}

handleReset = () => {
cache.invalidate();
this.setState(state => ({
requests: {},
}));
handleReset();
track('Clear cache', () => {
cache.invalidate();
this.setState(state => ({
requests: {},
}));
handleReset();
});
};

handleProgress = (url, progress, isPaused) => {
Expand Down
3 changes: 3 additions & 0 deletions fixtures/unstable-async/suspense/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3482,6 +3482,9 @@ [email protected], inquirer@^3.0.6:
strip-ansi "^4.0.0"
through "^2.3.6"

interaction-tracking@../../../build/node_modules/interaction-tracking:
version "0.0.1"

[email protected]:
version "1.2.0"
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c"
Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {UpdateQueue} from './ReactUpdateQueue';
import type {ContextDependency} from './ReactFiberNewContext';

import invariant from 'shared/invariant';
import warningWithoutStack from 'shared/warningWithoutStack';
import {enableProfilerTimer} from 'shared/ReactFeatureFlags';
import {NoEffect} from 'shared/ReactSideEffectTags';
import {
Expand Down Expand Up @@ -531,7 +532,7 @@ export function createFiberFromProfiler(
typeof pendingProps.id !== 'string' ||
typeof pendingProps.onRender !== 'function'
) {
invariant(
warningWithoutStack(
false,
'Profiler must specify an "id" string and "onRender" function as props',
);
Expand Down
41 changes: 31 additions & 10 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {CapturedValue, CapturedError} from './ReactCapturedValue';

import {enableProfilerTimer, enableSuspense} from 'shared/ReactFeatureFlags';
import {
enableInteractionTracking,
enableProfilerTimer,
enableSuspense,
} from 'shared/ReactFeatureFlags';
import {
ClassComponent,
ClassComponentLazy,
Expand Down Expand Up @@ -777,7 +781,11 @@ function commitDeletion(current: Fiber): void {
detachFiber(current);
}

function commitWork(current: Fiber | null, finishedWork: Fiber): void {
function commitWork(
root: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
): void {
if (!supportsMutation) {
commitContainer(finishedWork);
return;
Expand Down Expand Up @@ -836,14 +844,27 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
case Profiler: {
if (enableProfilerTimer) {
const onRender = finishedWork.memoizedProps.onRender;
onRender(
finishedWork.memoizedProps.id,
current === null ? 'mount' : 'update',
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
getCommitTime(),
);

if (enableInteractionTracking) {
onRender(
finishedWork.memoizedProps.id,
current === null ? 'mount' : 'update',
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
getCommitTime(),
root.memoizedInteractions,
);
} else {
onRender(
finishedWork.memoizedProps.id,
current === null ? 'mount' : 'update',
finishedWork.actualDuration,
finishedWork.treeBaseDuration,
finishedWork.actualStartTime,
getCommitTime(),
);
}
}
return;
}
Expand Down
117 changes: 90 additions & 27 deletions packages/react-reconciler/src/ReactFiberRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {TimeoutHandle, NoTimeout} from './ReactFiberHostConfig';
import type {Interaction} from 'interaction-tracking/src/InteractionTracking';

import {noTimeout} from './ReactFiberHostConfig';

import {createHostRootFiber} from './ReactFiber';
import {NoWork} from './ReactFiberExpirationTime';
import {enableInteractionTracking} from 'shared/ReactFeatureFlags';
import {getThreadID} from 'interaction-tracking';

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

export type FiberRoot = {
export type PendingInteractionMap = Map<ExpirationTime, Set<Interaction>>;

type BaseFiberRootProperties = {|
// Any additional information from the host associated with this root.
containerInfo: any,
// Used only by persistent updates.
Expand Down Expand Up @@ -73,6 +77,26 @@ export type FiberRoot = {
firstBatch: Batch | null,
// Linked-list of roots
nextScheduledRoot: FiberRoot | null,
|};

// The following attributes are only used by interaction tracking builds.
// They enable interactions to be associated with their async work,
// And expose interaction metadata to the React DevTools Profiler plugin.
// Note that these attributes are only defined when the enableInteractionTracking flag is enabled.
type ProfilingOnlyFiberRootProperties = {|
interactionThreadID: number,
memoizedInteractions: Set<Interaction>,
pendingInteractionMap: PendingInteractionMap,
|};

// Exported FiberRoot type includes all properties,
// To avoid requiring potentially error-prone :any casts throughout the project.
// Profiling properties are only safe to access in profiling builds (when enableInteractionTracking is true).
// The types are defined separately within this file to ensure they stay in sync.
// (We don't have to use an inline :any cast when enableInteractionTracking is disabled.)
export type FiberRoot = {
...BaseFiberRootProperties,
...ProfilingOnlyFiberRootProperties,
};

export function createFiberRoot(
Expand All @@ -83,30 +107,69 @@ export function createFiberRoot(
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(isAsync);
const root = {
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,

earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,

didError: false,

pendingCommitExpirationTime: NoWork,
finishedWork: null,
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork,
expirationTime: NoWork,
firstBatch: null,
nextScheduledRoot: null,
};

let root;
if (enableInteractionTracking) {
root = ({
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,

earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,

didError: false,

pendingCommitExpirationTime: NoWork,
finishedWork: null,
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork,
expirationTime: NoWork,
firstBatch: null,
nextScheduledRoot: null,

interactionThreadID: getThreadID(),
memoizedInteractions: new Set(),
pendingInteractionMap: new Map(),
}: FiberRoot);
} else {
root = ({
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,

earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,

didError: false,

pendingCommitExpirationTime: NoWork,
finishedWork: null,
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork,
expirationTime: NoWork,
firstBatch: null,
nextScheduledRoot: null,
}: BaseFiberRootProperties);
}

uninitializedFiber.stateNode = root;
return root;

// The reason for the way the Flow types are structured in this file,
// Is to avoid needing :any casts everywhere interaction-tracking fields are used.
// Unfortunately that requires an :any cast for non-interaction-tracking capable builds.
// $FlowFixMe Remove this :any cast and replace it with something better.
return ((root: any): FiberRoot);
}
Loading

0 comments on commit 6e4f7c7

Please sign in to comment.