@samitouri / QOS-React-2 / commits / 40cddfeeb1

Add user timing marks for scheduling profiler tool (#19223)

High level breakdown of this commit: * Add a enableSchedulingProfiling feature flag. * Add functions that call User Timing APIs to a new SchedulingProfiler file. The file follows DebugTracing's structure. * Add user timing marks to places where DebugTracing logs. * Add user timing marks to most other places where @bvaughn's original draft DebugTracing branch marks. * Tests added * More context (and discussions with @bvaughn) available at our internal PR MLH-Fellowship#11 and issue MLH-Fellowship#5. Similar to DebugTracing, we've only added scheduling profiling calls to the old reconciler fork. Co-authored-by: Kartik Choudhary <kartik.c918@gmail.com> Co-authored-by: Kartik Choudhary <kartikc.918@gmail.com> Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>

E-Liang Tan committed Jul 8, 2020 at 22:36 UTC 40cddfeeb167d4964d82220142deb00f57fe197d
16 files changed +772 -5
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+17
@@ -17,6 +17,7 @@ import {
17 debugRenderPhaseSideEffectsForStrictMode,
18 disableLegacyContext,
19 enableDebugTracing,
20 + enableSchedulingProfiler,
21 warnAboutDeprecatedLifecycles,
22 } from 'shared/ReactFeatureFlags';
23 import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
@@ -59,6 +60,10 @@ import {requestCurrentSuspenseConfig} from './ReactFiberSuspenseConfig';
60 import {logForceUpdateScheduled, logStateUpdateScheduled} from './DebugTracing';
61
62 import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
63 +import {
64 + markForceUpdateScheduled,
65 + markStateUpdateScheduled,
66 +} from './SchedulingProfiler';
67
68 const fakeInternalInstance = {};
69 const isArray = Array.isArray;
@@ -214,6 +219,10 @@ const classComponentUpdater = {
219 }
220 }
221 }
222 +
223 + if (enableSchedulingProfiler) {
224 + markStateUpdateScheduled(fiber, lane);
225 + }
226 },
227 enqueueReplaceState(inst, payload, callback) {
228 const fiber = getInstance(inst);
@@ -243,6 +252,10 @@ const classComponentUpdater = {
252 }
253 }
254 }
255 +
256 + if (enableSchedulingProfiler) {
257 + markStateUpdateScheduled(fiber, lane);
258 + }
259 },
260 enqueueForceUpdate(inst, callback) {
261 const fiber = getInstance(inst);
@@ -271,6 +284,10 @@ const classComponentUpdater = {
284 }
285 }
286 }
287 +
288 + if (enableSchedulingProfiler) {
289 + markForceUpdateScheduled(fiber, lane);
290 + }
291 },
292 };
293
packages/react-reconciler/src/ReactFiberHooks.old.js
+6
@@ -26,6 +26,7 @@ import type {OpaqueIDType} from './ReactFiberHostConfig';
26 import ReactSharedInternals from 'shared/ReactSharedInternals';
27 import {
28 enableDebugTracing,
29 + enableSchedulingProfiler,
30 enableNewReconciler,
31 } from 'shared/ReactFeatureFlags';
32
@@ -92,6 +93,7 @@ import {
93 } from './ReactMutableSource.old';
94 import {getIsRendering} from './ReactCurrentFiber';
95 import {logStateUpdateScheduled} from './DebugTracing';
96 +import {markStateUpdateScheduled} from './SchedulingProfiler';
97
98 const {ReactCurrentDispatcher, ReactCurrentBatchConfig} = ReactSharedInternals;
99
@@ -1764,6 +1766,10 @@ function dispatchAction<S, A>(
1766 }
1767 }
1768 }
1769 +
1770 + if (enableSchedulingProfiler) {
1771 + markStateUpdateScheduled(fiber, lane);
1772 + }
1773 }
1774
1775 export const ContextOnlyDispatcher: Dispatcher = {
packages/react-reconciler/src/ReactFiberReconciler.old.js
+6
@@ -39,6 +39,7 @@ import {
39 } from './ReactWorkTags';
40 import getComponentName from 'shared/getComponentName';
41 import invariant from 'shared/invariant';
42 +import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
43 import ReactSharedInternals from 'shared/ReactSharedInternals';
44 import {getPublicInstance} from './ReactFiberHostConfig';
45 import {
@@ -95,6 +96,7 @@ import {
96 setRefreshHandler,
97 findHostInstancesForRefresh,
98 } from './ReactFiberHotReloading.old';
99 +import {markRenderScheduled} from './SchedulingProfiler';
100
101 export {registerMutableSourceForHydration} from './ReactMutableSource.new';
102 export {createPortal} from './ReactPortal';
@@ -273,6 +275,10 @@ export function updateContainer(
275 const suspenseConfig = requestCurrentSuspenseConfig();
276 const lane = requestUpdateLane(current, suspenseConfig);
277
278 + if (enableSchedulingProfiler) {
279 + markRenderScheduled(lane);
280 + }
281 +
282 const context = getContextForSubtree(parentComponent);
283 if (container.context === null) {
284 container.context = context;
packages/react-reconciler/src/ReactFiberThrow.old.js
+9 -1
@@ -32,7 +32,10 @@ import {
32 } from './ReactSideEffectTags';
33 import {shouldCaptureSuspense} from './ReactFiberSuspenseComponent.old';
34 import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
35 -import {enableDebugTracing} from 'shared/ReactFeatureFlags';
35 +import {
36 + enableDebugTracing,
37 + enableSchedulingProfiler,
38 +} from 'shared/ReactFeatureFlags';
39 import {createCapturedValue} from './ReactCapturedValue';
40 import {
41 enqueueCapturedUpdate,
@@ -56,6 +59,7 @@ import {
59 } from './ReactFiberWorkLoop.old';
60 import {logCapturedError} from './ReactFiberErrorLogger';
61 import {logComponentSuspended} from './DebugTracing';
62 +import {markComponentSuspended} from './SchedulingProfiler';
63
64 import {
65 SyncLane,
@@ -201,6 +205,10 @@ function throwException(
205 }
206 }
207
208 + if (enableSchedulingProfiler) {
209 + markComponentSuspended(sourceFiber, wakeable);
210 + }
211 +
212 if ((sourceFiber.mode & BlockingMode) === NoMode) {
213 // Reset the memoizedState to what it was before we attempted
214 // to render it.
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+63
@@ -28,6 +28,7 @@ import {
28 deferRenderPhaseUpdateToNextBatch,
29 decoupleUpdatePriorityFromScheduler,
30 enableDebugTracing,
31 + enableSchedulingProfiler,
32 } from 'shared/ReactFeatureFlags';
33 import ReactSharedInternals from 'shared/ReactSharedInternals';
34 import invariant from 'shared/invariant';
@@ -57,6 +58,17 @@ import {
58 logRenderStarted,
59 logRenderStopped,
60 } from './DebugTracing';
61 +import {
62 + markCommitStarted,
63 + markCommitStopped,
64 + markLayoutEffectsStarted,
65 + markLayoutEffectsStopped,
66 + markPassiveEffectsStarted,
67 + markPassiveEffectsStopped,
68 + markRenderStarted,
69 + markRenderYielded,
70 + markRenderStopped,
71 +} from './SchedulingProfiler';
72
73 // The scheduler is imported here *only* to detect whether it's been mocked
74 import * as Scheduler from 'scheduler';
@@ -1509,6 +1521,10 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
1521 }
1522 }
1523
1524 + if (enableSchedulingProfiler) {
1525 + markRenderStarted(lanes);
1526 + }
1527 +
1528 do {
1529 try {
1530 workLoopSync();
@@ -1540,6 +1556,10 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
1556 }
1557 }
1558
1559 + if (enableSchedulingProfiler) {
1560 + markRenderStopped();
1561 + }
1562 +
1563 // Set this to null to indicate there's no in-progress render.
1564 workInProgressRoot = null;
1565 workInProgressRootRenderLanes = NoLanes;
@@ -1576,6 +1596,10 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
1596 }
1597 }
1598
1599 + if (enableSchedulingProfiler) {
1600 + markRenderStarted(lanes);
1601 + }
1602 +
1603 do {
1604 try {
1605 workLoopConcurrent();
@@ -1601,9 +1625,16 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
1625 // Check if the tree has completed.
1626 if (workInProgress !== null) {
1627 // Still work remaining.
1628 + if (enableSchedulingProfiler) {
1629 + markRenderYielded();
1630 + }
1631 return RootIncomplete;
1632 } else {
1633 // Completed the tree.
1634 + if (enableSchedulingProfiler) {
1635 + markRenderStopped();
1636 + }
1637 +
1638 // Set this to null to indicate there's no in-progress render.
1639 workInProgressRoot = null;
1640 workInProgressRootRenderLanes = NoLanes;
@@ -1893,6 +1924,10 @@ function commitRootImpl(root, renderPriorityLevel) {
1924 }
1925 }
1926
1927 + if (enableSchedulingProfiler) {
1928 + markCommitStarted(lanes);
1929 + }
1930 +
1931 if (finishedWork === null) {
1932 if (__DEV__) {
1933 if (enableDebugTracing) {
@@ -1900,6 +1935,10 @@ function commitRootImpl(root, renderPriorityLevel) {
1935 }
1936 }
1937
1938 + if (enableSchedulingProfiler) {
1939 + markCommitStopped();
1940 + }
1941 +
1942 return null;
1943 }
1944 root.finishedWork = null;
@@ -2196,6 +2235,10 @@ function commitRootImpl(root, renderPriorityLevel) {
2235 }
2236 }
2237
2238 + if (enableSchedulingProfiler) {
2239 + markCommitStopped();
2240 + }
2241 +
2242 // This is a legacy edge case. We just committed the initial mount of
2243 // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
2244 // synchronously, but layout updates should be deferred until the end
@@ -2212,6 +2255,10 @@ function commitRootImpl(root, renderPriorityLevel) {
2255 }
2256 }
2257
2258 + if (enableSchedulingProfiler) {
2259 + markCommitStopped();
2260 + }
2261 +
2262 return null;
2263 }
2264
@@ -2342,6 +2389,10 @@ function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
2389 }
2390 }
2391
2392 + if (enableSchedulingProfiler) {
2393 + markLayoutEffectsStarted(committedLanes);
2394 + }
2395 +
2396 // TODO: Should probably move the bulk of this function to commitWork.
2397 while (nextEffect !== null) {
2398 setCurrentDebugFiberInDEV(nextEffect);
@@ -2366,6 +2417,10 @@ function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
2417 logLayoutEffectsStopped();
2418 }
2419 }
2420 +
2421 + if (enableSchedulingProfiler) {
2422 + markLayoutEffectsStopped();
2423 + }
2424 }
2425
2426 export function flushPassiveEffects() {
@@ -2461,6 +2516,10 @@ function flushPassiveEffectsImpl() {
2516 }
2517 }
2518
2519 + if (enableSchedulingProfiler) {
2520 + markPassiveEffectsStarted(lanes);
2521 + }
2522 +
2523 if (__DEV__) {
2524 isFlushingPassiveEffects = true;
2525 }
@@ -2623,6 +2682,10 @@ function flushPassiveEffectsImpl() {
2682 }
2683 }
2684
2685 + if (enableSchedulingProfiler) {
2686 + markPassiveEffectsStopped();
2687 + }
2688 +
2689 executionContext = prevExecutionContext;
2690
2691 flushSyncCallbackQueue();
packages/react-reconciler/src/SchedulingProfiler.js new
+187
@@ -0,0 +1,187 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Lane, Lanes} from './ReactFiberLane';
11 +import type {Fiber} from './ReactInternalTypes';
12 +import type {Wakeable} from 'shared/ReactTypes';
13 +
14 +import {enableSchedulingProfiler} from 'shared/ReactFeatureFlags';
15 +import getComponentName from 'shared/getComponentName';
16 +import {getStackByFiberInDevAndProd} from './ReactFiberComponentStack';
17 +
18 +/**
19 + * If performance exists and supports the subset of the User Timing API that we
20 + * require.
21 + */
22 +const supportsUserTiming =
23 + typeof performance !== 'undefined' && typeof performance.mark === 'function';
24 +
25 +function formatLanes(laneOrLanes: Lane | Lanes): string {
26 + return ((laneOrLanes: any): number).toString();
27 +}
28 +
29 +export function markCommitStarted(lanes: Lanes): void {
30 + if (enableSchedulingProfiler) {
31 + if (supportsUserTiming) {
32 + performance.mark(`--commit-start-${formatLanes(lanes)}`);
33 + }
34 + }
35 +}
36 +
37 +export function markCommitStopped(): void {
38 + if (enableSchedulingProfiler) {
39 + if (supportsUserTiming) {
40 + performance.mark('--commit-stop');
41 + }
42 + }
43 +}
44 +
45 +const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
46 +
47 +// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
48 +const wakeableIDs: WeakMap<Wakeable, number> = new PossiblyWeakMap();
49 +let wakeableID: number = 0;
50 +function getWakeableID(wakeable: Wakeable): number {
51 + if (!wakeableIDs.has(wakeable)) {
52 + wakeableIDs.set(wakeable, wakeableID++);
53 + }
54 + return ((wakeableIDs.get(wakeable): any): number);
55 +}
56 +
57 +// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
58 +const cachedFiberStacks: WeakMap<Fiber, string> = new PossiblyWeakMap();
59 +function cacheFirstGetComponentStackByFiber(fiber: Fiber): string {
60 + if (cachedFiberStacks.has(fiber)) {
61 + return ((cachedFiberStacks.get(fiber): any): string);
62 + } else {
63 + const alternate = fiber.alternate;
64 + if (alternate !== null && cachedFiberStacks.has(alternate)) {
65 + return ((cachedFiberStacks.get(alternate): any): string);
66 + }
67 + }
68 + // TODO (brian) Generate and store temporary ID so DevTools can match up a component stack later.
69 + const componentStack = getStackByFiberInDevAndProd(fiber) || '';
70 + cachedFiberStacks.set(fiber, componentStack);
71 + return componentStack;
72 +}
73 +
74 +export function markComponentSuspended(fiber: Fiber, wakeable: Wakeable): void {
75 + if (enableSchedulingProfiler) {
76 + if (supportsUserTiming) {
77 + const id = getWakeableID(wakeable);
78 + const componentName = getComponentName(fiber.type) || 'Unknown';
79 + const componentStack = cacheFirstGetComponentStackByFiber(fiber);
80 + performance.mark(
81 + `--suspense-suspend-${id}-${componentName}-${componentStack}`,
82 + );
83 + wakeable.then(
84 + () =>
85 + performance.mark(
86 + `--suspense-resolved-${id}-${componentName}-${componentStack}`,
87 + ),
88 + () =>
89 + performance.mark(
90 + `--suspense-rejected-${id}-${componentName}-${componentStack}`,
91 + ),
92 + );
93 + }
94 + }
95 +}
96 +
97 +export function markLayoutEffectsStarted(lanes: Lanes): void {
98 + if (enableSchedulingProfiler) {
99 + if (supportsUserTiming) {
100 + performance.mark(`--layout-effects-start-${formatLanes(lanes)}`);
101 + }
102 + }
103 +}
104 +
105 +export function markLayoutEffectsStopped(): void {
106 + if (enableSchedulingProfiler) {
107 + if (supportsUserTiming) {
108 + performance.mark('--layout-effects-stop');
109 + }
110 + }
111 +}
112 +
113 +export function markPassiveEffectsStarted(lanes: Lanes): void {
114 + if (enableSchedulingProfiler) {
115 + if (supportsUserTiming) {
116 + performance.mark(`--passive-effects-start-${formatLanes(lanes)}`);
117 + }
118 + }
119 +}
120 +
121 +export function markPassiveEffectsStopped(): void {
122 + if (enableSchedulingProfiler) {
123 + if (supportsUserTiming) {
124 + performance.mark('--passive-effects-stop');
125 + }
126 + }
127 +}
128 +
129 +export function markRenderStarted(lanes: Lanes): void {
130 + if (enableSchedulingProfiler) {
131 + if (supportsUserTiming) {
132 + performance.mark(`--render-start-${formatLanes(lanes)}`);
133 + }
134 + }
135 +}
136 +
137 +export function markRenderYielded(): void {
138 + if (enableSchedulingProfiler) {
139 + if (supportsUserTiming) {
140 + performance.mark('--render-yield');
141 + }
142 + }
143 +}
144 +
145 +export function markRenderStopped(): void {
146 + if (enableSchedulingProfiler) {
147 + if (supportsUserTiming) {
148 + performance.mark('--render-stop');
149 + }
150 + }
151 +}
152 +
153 +export function markRenderScheduled(lane: Lane): void {
154 + if (enableSchedulingProfiler) {
155 + if (supportsUserTiming) {
156 + performance.mark(`--schedule-render-${formatLanes(lane)}`);
157 + }
158 + }
159 +}
160 +
161 +export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
162 + if (enableSchedulingProfiler) {
163 + if (supportsUserTiming) {
164 + const componentName = getComponentName(fiber.type) || 'Unknown';
165 + const componentStack = cacheFirstGetComponentStackByFiber(fiber);
166 + performance.mark(
167 + `--schedule-forced-update-${formatLanes(
168 + lane,
169 + )}-${componentName}-${componentStack}`,
170 + );
171 + }
172 + }
173 +}
174 +
175 +export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
176 + if (enableSchedulingProfiler) {
177 + if (supportsUserTiming) {
178 + const componentName = getComponentName(fiber.type) || 'Unknown';
179 + const componentStack = cacheFirstGetComponentStackByFiber(fiber);
180 + performance.mark(
181 + `--schedule-state-update-${formatLanes(
182 + lane,
183 + )}-${componentName}-${componentStack}`,
184 + );
185 + }
186 + }
187 +}
packages/react-reconciler/src/__tests__/SchedulingProfiler-test.internal.js new
+468
@@ -0,0 +1,468 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + * @jest-environment node
9 + */
10 +
11 +'use strict';
12 +
13 +function normalizeCodeLocInfo(str) {
14 + return (
15 + str &&
16 + str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
17 + return '\n in ' + name + ' (at **)';
18 + })
19 + );
20 +}
21 +
22 +describe('SchedulingProfiler', () => {
23 + let React;
24 + let ReactTestRenderer;
25 + let ReactNoop;
26 + let Scheduler;
27 +
28 + let marks;
29 +
30 + function createUserTimingPolyfill() {
31 + // This is not a true polyfill, but it gives us enough to capture marks.
32 + // Reference: https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API
33 + return {
34 + mark(markName) {
35 + marks.push(markName);
36 + },
37 + };
38 + }
39 +
40 + beforeEach(() => {
41 + jest.resetModules();
42 + global.performance = createUserTimingPolyfill();
43 +
44 + React = require('react');
45 +
46 + // ReactNoop must be imported after ReactTestRenderer!
47 + ReactTestRenderer = require('react-test-renderer');
48 + ReactNoop = require('react-noop-renderer');
49 +
50 + Scheduler = require('scheduler');
51 +
52 + marks = [];
53 + });
54 +
55 + afterEach(() => {
56 + delete global.performance;
57 + });
58 +
59 + // @gate !enableSchedulingProfiler
60 + it('should not mark if enableSchedulingProfiler is false', () => {
61 + ReactTestRenderer.create(<div />);
62 + expect(marks).toEqual([]);
63 + });
64 +
65 + // @gate enableSchedulingProfiler
66 + it('should mark sync render without suspends or state updates', () => {
67 + ReactTestRenderer.create(<div />);
68 +
69 + expect(marks).toEqual([
70 + '--schedule-render-1',
71 + '--render-start-1',
72 + '--render-stop',
73 + '--commit-start-1',
74 + '--layout-effects-start-1',
75 + '--layout-effects-stop',
76 + '--commit-stop',
77 + ]);
78 + });
79 +
80 + // @gate enableSchedulingProfiler
81 + it('should mark concurrent render without suspends or state updates', () => {
82 + ReactTestRenderer.create(<div />, {unstable_isConcurrent: true});
83 +
84 + expect(marks).toEqual(['--schedule-render-512']);
85 +
86 + marks.splice(0);
87 +
88 + expect(Scheduler).toFlushUntilNextPaint([]);
89 +
90 + expect(marks).toEqual([
91 + '--render-start-512',
92 + '--render-stop',
93 + '--commit-start-512',
94 + '--layout-effects-start-512',
95 + '--layout-effects-stop',
96 + '--commit-stop',
97 + ]);
98 + });
99 +
100 + // @gate enableSchedulingProfiler
101 + it('should mark render yields', async () => {
102 + function Bar() {
103 + Scheduler.unstable_yieldValue('Bar');
104 + return null;
105 + }
106 +
107 + function Foo() {
108 + Scheduler.unstable_yieldValue('Foo');
109 + return <Bar />;
110 + }
111 +
112 + ReactNoop.render(<Foo />);
113 + // Do one step of work.
114 + expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
115 +
116 + expect(marks).toEqual([
117 + '--schedule-render-512',
118 + '--render-start-512',
119 + '--render-yield',
120 + ]);
121 + });
122 +
123 + // @gate enableSchedulingProfiler
124 + it('should mark sync render with suspense that resolves', async () => {
125 + const fakeSuspensePromise = Promise.resolve(true);
126 + function Example() {
127 + throw fakeSuspensePromise;
128 + }
129 +
130 + ReactTestRenderer.create(
131 + <React.Suspense fallback={null}>
132 + <Example />
133 + </React.Suspense>,
134 + );
135 +
136 + expect(marks).toEqual([
137 + '--schedule-render-1',
138 + '--render-start-1',
139 + '--suspense-suspend-0-Example-\n at Example\n at Suspense',
140 + '--render-stop',
141 + '--commit-start-1',
142 + '--layout-effects-start-1',
143 + '--layout-effects-stop',
144 + '--commit-stop',
145 + ]);
146 +
147 + marks.splice(0);
148 +
149 + await fakeSuspensePromise;
150 + expect(marks).toEqual([
151 + '--suspense-resolved-0-Example-\n at Example\n at Suspense',
152 + ]);
153 + });
154 +
155 + // @gate enableSchedulingProfiler
156 + it('should mark sync render with suspense that rejects', async () => {
157 + const fakeSuspensePromise = Promise.reject(new Error('error'));
158 + function Example() {
159 + throw fakeSuspensePromise;
160 + }
161 +
162 + ReactTestRenderer.create(
163 + <React.Suspense fallback={null}>
164 + <Example />
165 + </React.Suspense>,
166 + );
167 +
168 + expect(marks).toEqual([
169 + '--schedule-render-1',
170 + '--render-start-1',
171 + '--suspense-suspend-0-Example-\n at Example\n at Suspense',
172 + '--render-stop',
173 + '--commit-start-1',
174 + '--layout-effects-start-1',
175 + '--layout-effects-stop',
176 + '--commit-stop',
177 + ]);
178 +
179 + marks.splice(0);
180 +
181 + await expect(fakeSuspensePromise).rejects.toThrow();
182 + expect(marks).toEqual([
183 + '--suspense-rejected-0-Example-\n at Example\n at Suspense',
184 + ]);
185 + });
186 +
187 + // @gate enableSchedulingProfiler
188 + it('should mark concurrent render with suspense that resolves', async () => {
189 + const fakeSuspensePromise = Promise.resolve(true);
190 + function Example() {
191 + throw fakeSuspensePromise;
192 + }
193 +
194 + ReactTestRenderer.create(
195 + <React.Suspense fallback={null}>
196 + <Example />
197 + </React.Suspense>,
198 + {unstable_isConcurrent: true},
199 + );
200 +
201 + expect(marks).toEqual(['--schedule-render-512']);
202 +
203 + marks.splice(0);
204 +
205 + expect(Scheduler).toFlushUntilNextPaint([]);
206 +
207 + expect(marks).toEqual([
208 + '--render-start-512',
209 + '--suspense-suspend-0-Example-\n at Example\n at Suspense',
210 + '--render-stop',
211 + '--commit-start-512',
212 + '--layout-effects-start-512',
213 + '--layout-effects-stop',
214 + '--commit-stop',
215 + ]);
216 +
217 + marks.splice(0);
218 +
219 + await fakeSuspensePromise;
220 + expect(marks).toEqual([
221 + '--suspense-resolved-0-Example-\n at Example\n at Suspense',
222 + ]);
223 + });
224 +
225 + // @gate enableSchedulingProfiler
226 + it('should mark concurrent render with suspense that rejects', async () => {
227 + const fakeSuspensePromise = Promise.reject(new Error('error'));
228 + function Example() {
229 + throw fakeSuspensePromise;
230 + }
231 +
232 + ReactTestRenderer.create(
233 + <React.Suspense fallback={null}>
234 + <Example />
235 + </React.Suspense>,
236 + {unstable_isConcurrent: true},
237 + );
238 +
239 + expect(marks).toEqual(['--schedule-render-512']);
240 +
241 + marks.splice(0);
242 +
243 + expect(Scheduler).toFlushUntilNextPaint([]);
244 +
245 + expect(marks).toEqual([
246 + '--render-start-512',
247 + '--suspense-suspend-0-Example-\n at Example\n at Suspense',
248 + '--render-stop',
249 + '--commit-start-512',
250 + '--layout-effects-start-512',
251 + '--layout-effects-stop',
252 + '--commit-stop',
253 + ]);
254 +
255 + marks.splice(0);
256 +
257 + await expect(fakeSuspensePromise).rejects.toThrow();
258 + expect(marks).toEqual([
259 + '--suspense-rejected-0-Example-\n at Example\n at Suspense',
260 + ]);
261 + });
262 +
263 + // @gate enableSchedulingProfiler
264 + it('should mark cascading class component state updates', () => {
265 + class Example extends React.Component {
266 + state = {didMount: false};
267 + componentDidMount() {
268 + this.setState({didMount: true});
269 + }
270 + render() {
271 + return null;
272 + }
273 + }
274 +
275 + ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
276 +
277 + expect(marks).toEqual(['--schedule-render-512']);
278 +
279 + marks.splice(0);
280 +
281 + expect(Scheduler).toFlushUntilNextPaint([]);
282 +
283 + expect(marks.map(normalizeCodeLocInfo)).toEqual([
284 + '--render-start-512',
285 + '--render-stop',
286 + '--commit-start-512',
287 + '--layout-effects-start-512',
288 + '--schedule-state-update-1-Example-\n in Example (at **)',
289 + '--layout-effects-stop',
290 + '--render-start-1',
291 + '--render-stop',
292 + '--commit-start-1',
293 + '--commit-stop',
294 + '--commit-stop',
295 + ]);
296 + });
297 +
298 + // @gate enableSchedulingProfiler
299 + it('should mark cascading class component force updates', () => {
300 + class Example extends React.Component {
301 + componentDidMount() {
302 + this.forceUpdate();
303 + }
304 + render() {
305 + return null;
306 + }
307 + }
308 +
309 + ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
310 +
311 + expect(marks).toEqual(['--schedule-render-512']);
312 +
313 + marks.splice(0);
314 +
315 + expect(Scheduler).toFlushUntilNextPaint([]);
316 +
317 + expect(marks.map(normalizeCodeLocInfo)).toEqual([
318 + '--render-start-512',
319 + '--render-stop',
320 + '--commit-start-512',
321 + '--layout-effects-start-512',
322 + '--schedule-forced-update-1-Example-\n in Example (at **)',
323 + '--layout-effects-stop',
324 + '--render-start-1',
325 + '--render-stop',
326 + '--commit-start-1',
327 + '--commit-stop',
328 + '--commit-stop',
329 + ]);
330 + });
331 +
332 + // @gate enableSchedulingProfiler
333 + it('should mark render phase state updates for class component', () => {
334 + class Example extends React.Component {
335 + state = {didRender: false};
336 + render() {
337 + if (this.state.didRender === false) {
338 + this.setState({didRender: true});
339 + }
340 + return null;
341 + }
342 + }
343 +
344 + ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
345 +
346 + expect(marks).toEqual(['--schedule-render-512']);
347 +
348 + marks.splice(0);
349 +
350 + expect(() => {
351 + expect(Scheduler).toFlushUntilNextPaint([]);
352 + }).toErrorDev('Cannot update during an existing state transition');
353 +
354 + expect(marks.map(normalizeCodeLocInfo)).toContain(
355 + '--schedule-state-update-1024-Example-\n in Example (at **)',
356 + );
357 + });
358 +
359 + // @gate enableSchedulingProfiler
360 + it('should mark render phase force updates for class component', () => {
361 + class Example extends React.Component {
362 + state = {didRender: false};
363 + render() {
364 + if (this.state.didRender === false) {
365 + this.forceUpdate(() => this.setState({didRender: true}));
366 + }
367 + return null;
368 + }
369 + }
370 +
371 + ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
372 +
373 + expect(marks).toEqual(['--schedule-render-512']);
374 +
375 + marks.splice(0);
376 +
377 + expect(() => {
378 + expect(Scheduler).toFlushUntilNextPaint([]);
379 + }).toErrorDev('Cannot update during an existing state transition');
380 +
381 + expect(marks.map(normalizeCodeLocInfo)).toContain(
382 + '--schedule-forced-update-1024-Example-\n in Example (at **)',
383 + );
384 + });
385 +
386 + // @gate enableSchedulingProfiler
387 + it('should mark cascading layout updates', () => {
388 + function Example() {
389 + const [didMount, setDidMount] = React.useState(false);
390 + React.useLayoutEffect(() => {
391 + setDidMount(true);
392 + }, []);
393 + return didMount;
394 + }
395 +
396 + ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
397 +
398 + expect(marks).toEqual(['--schedule-render-512']);
399 +
400 + marks.splice(0);
401 +
402 + expect(Scheduler).toFlushUntilNextPaint([]);
403 +
404 + expect(marks.map(normalizeCodeLocInfo)).toEqual([
405 + '--render-start-512',
406 + '--render-stop',
407 + '--commit-start-512',
408 + '--layout-effects-start-512',
409 + '--schedule-state-update-1-Example-\n in Example (at **)',
410 + '--layout-effects-stop',
411 + '--render-start-1',
412 + '--render-stop',
413 + '--commit-start-1',
414 + '--commit-stop',
415 + '--commit-stop',
416 + ]);
417 + });
418 +
419 + // @gate enableSchedulingProfiler
420 + it('should mark cascading passive updates', () => {
421 + function Example() {
422 + const [didMount, setDidMount] = React.useState(false);
423 + React.useEffect(() => {
424 + setDidMount(true);
425 + }, []);
426 + return didMount;
427 + }
428 +
429 + ReactTestRenderer.act(() => {
430 + ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
431 + });
432 + expect(marks.map(normalizeCodeLocInfo)).toEqual([
433 + '--schedule-render-512',
434 + '--render-start-512',
435 + '--render-stop',
436 + '--commit-start-512',
437 + '--layout-effects-start-512',
438 + '--layout-effects-stop',
439 + '--commit-stop',
440 + '--passive-effects-start-512',
441 + '--schedule-state-update-1024-Example-\n in Example (at **)',
442 + '--passive-effects-stop',
443 + '--render-start-1024',
444 + '--render-stop',
445 + '--commit-start-1024',
446 + '--commit-stop',
447 + ]);
448 + });
449 +
450 + // @gate enableSchedulingProfiler
451 + it('should mark render phase updates', () => {
452 + function Example() {
453 + const [didRender, setDidRender] = React.useState(false);
454 + if (!didRender) {
455 + setDidRender(true);
456 + }
457 + return didRender;
458 + }
459 +
460 + ReactTestRenderer.act(() => {
461 + ReactTestRenderer.create(<Example />, {unstable_isConcurrent: true});
462 + });
463 +
464 + expect(marks.map(normalizeCodeLocInfo)).toContain(
465 + '--schedule-state-update-1024-Example-\n in Example (at **)',
466 + );
467 + });
468 +});
packages/shared/ReactFeatureFlags.js
+4
@@ -15,6 +15,10 @@ export const enableFilterEmptyStringAttributesDOM = false;
15 // Intended to enable React core members to more easily debug scheduling issues in DEV builds.
16 export const enableDebugTracing = false;
17
18 +// Adds user timing marks for e.g. state updates, suspense, and work loop stuff,
19 +// for an experimental scheduling profiler tool.
20 +export const enableSchedulingProfiler = false;
21 +
22 // Helps identify side effects in render-phase lifecycle hooks and setState
23 // reducers by double invoking them in Strict Mode.
24 export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.native-fb';
12
13 // The rest of the flags are static for better dead code elimination.
14 export const enableDebugTracing = false;
15 +export const enableSchedulingProfiler = false;
16 export const enableProfilerTimer = __PROFILE__;
17 export const enableProfilerCommitHooks = false;
18 export const enableSchedulerTracing = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.native-oss';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 export const enableDebugTracing = false;
15 +export const enableSchedulingProfiler = false;
16 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
17 export const warnAboutDeprecatedLifecycles = true;
18 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.test-renderer';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 export const enableDebugTracing = false;
15 +export const enableSchedulingProfiler = false;
16 export const warnAboutDeprecatedLifecycles = true;
17 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
18 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.test-renderer.www';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 export const enableDebugTracing = false;
15 +export const enableSchedulingProfiler = false;
16 export const warnAboutDeprecatedLifecycles = true;
17 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
18 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.testing.js
+1
@@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.testing';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 export const enableDebugTracing = false;
15 +export const enableSchedulingProfiler = false;
16 export const warnAboutDeprecatedLifecycles = true;
17 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
18 export const enableProfilerTimer = __PROFILE__;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1
@@ -12,6 +12,7 @@ import typeof * as ExportsType from './ReactFeatureFlags.testing.www';
12
13 export const debugRenderPhaseSideEffectsForStrictMode = false;
14 export const enableDebugTracing = false;
15 +export const enableSchedulingProfiler = false;
16 export const warnAboutDeprecatedLifecycles = true;
17 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
18 export const enableProfilerTimer = false;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+4 -1
@@ -17,9 +17,12 @@ export const warnAboutSpreadingKeyToJSX = __VARIANT__;
17 export const disableInputAttributeSyncing = __VARIANT__;
18 export const enableFilterEmptyStringAttributesDOM = __VARIANT__;
19 export const enableLegacyFBSupport = __VARIANT__;
20 -export const enableDebugTracing = !__VARIANT__;
20 export const decoupleUpdatePriorityFromScheduler = __VARIANT__;
21
22 +// TODO: These features do not currently exist in the new reconciler fork.
23 +export const enableDebugTracing = !__VARIANT__;
24 +export const enableSchedulingProfiler = !__VARIANT__ && __PROFILE__;
25 +
26 // This only has an effect in the new reconciler. But also, the new reconciler
27 // is only enabled when __VARIANT__ is true. So this is set to the opposite of
28 // __VARIANT__ so that it's `false` when running against the new reconciler.
packages/shared/forks/ReactFeatureFlags.www.js
+2 -3
@@ -25,6 +25,8 @@ export const {
25 enableLegacyFBSupport,
26 deferRenderPhaseUpdateToNextBatch,
27 decoupleUpdatePriorityFromScheduler,
28 + enableDebugTracing,
29 + enableSchedulingProfiler,
30 } = dynamicFeatureFlags;
31
32 // On WWW, __EXPERIMENTAL__ is used for a new modern build.
@@ -77,9 +79,6 @@ export const warnUnstableRenderSubtreeIntoContainer = false;
79 // to the correct value.
80 export const enableNewReconciler = __VARIANT__;
81
80 -// TODO: This does not currently exist in the new reconciler fork.
81 -export const enableDebugTracing = !__VARIANT__;
82 -
82 // Flow magic to verify the exports of this file match the original version.
83 // eslint-disable-next-line no-unused-vars
84 type Check<_X, Y: _X, X: Y = _X> = null;