[Scheduler] Profiling features (#16145)
* [Scheduler] Mark user-timing events Marks when Scheduler starts and stops running a task. Also marks when a task is initially scheduled, and when Scheduler is waiting for a callback, which can't be inferred from a sample-based JavaScript CPU profile alone. The plan is to use the user-timing events to build a Scheduler profiler that shows how the lifetime of tasks interact with each other and with unscheduled main thread work. The test suite works by printing an text representation of a Scheduler flamegraph. * Expose shared array buffer with profiling info Array contains - the priority Scheduler is currently running - the size of the queue - the id of the currently running task * Replace user-timing calls with event log Events are written to an array buffer using a custom instruction format. For now, this is only meant to be used during page start up, before the profiler worker has a chance to start up. Once the worker is ready, call `stopLoggingProfilerEvents` to return the log up to that point, then send the array buffer to the worker. Then switch to the sampling based approach. * Record the current run ID Each synchronous block of Scheduler work is given a unique run ID. This is different than a task ID because a single task will have more than one run if it yields with a continuation.
This commit is contained in:
parent
56636353d8
commit
a34ca7bce6
|
@ -140,6 +140,8 @@ module.exports = {
|
|||
],
|
||||
|
||||
globals: {
|
||||
SharedArrayBuffer: true,
|
||||
|
||||
spyOnDev: true,
|
||||
spyOnDevAndProd: true,
|
||||
spyOnProd: true,
|
||||
|
|
|
@ -136,6 +136,7 @@ describe('ReactDebugFiberPerf', () => {
|
|||
require('shared/ReactFeatureFlags').enableProfilerTimer = false;
|
||||
require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
require('scheduler/src/SchedulerFeatureFlags').enableProfiling = false;
|
||||
|
||||
// Import after the polyfill is set up:
|
||||
React = require('react');
|
||||
|
|
|
@ -110,6 +110,20 @@
|
|||
);
|
||||
}
|
||||
|
||||
function unstable_startLoggingProfilingEvents() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_startLoggingProfilingEvents.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_stopLoggingProfilingEvents() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_stopLoggingProfilingEvents.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
unstable_now: unstable_now,
|
||||
unstable_scheduleCallback: unstable_scheduleCallback,
|
||||
|
@ -124,6 +138,8 @@
|
|||
unstable_pauseExecution: unstable_pauseExecution,
|
||||
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
|
||||
unstable_forceFrameRate: unstable_forceFrameRate,
|
||||
unstable_startLoggingProfilingEvents: unstable_startLoggingProfilingEvents,
|
||||
unstable_stopLoggingProfilingEvents: unstable_stopLoggingProfilingEvents,
|
||||
get unstable_IdlePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_IdlePriority;
|
||||
|
@ -144,5 +160,9 @@
|
|||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
get unstable_sharedProfilingBuffer() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_getFirstCallbackNode;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -104,6 +104,20 @@
|
|||
);
|
||||
}
|
||||
|
||||
function unstable_startLoggingProfilingEvents() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_startLoggingProfilingEvents.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_stopLoggingProfilingEvents() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_stopLoggingProfilingEvents.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
unstable_now: unstable_now,
|
||||
unstable_scheduleCallback: unstable_scheduleCallback,
|
||||
|
@ -118,6 +132,8 @@
|
|||
unstable_pauseExecution: unstable_pauseExecution,
|
||||
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
|
||||
unstable_forceFrameRate: unstable_forceFrameRate,
|
||||
unstable_startLoggingProfilingEvents: unstable_startLoggingProfilingEvents,
|
||||
unstable_stopLoggingProfilingEvents: unstable_stopLoggingProfilingEvents,
|
||||
get unstable_IdlePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_IdlePriority;
|
||||
|
@ -138,5 +154,9 @@
|
|||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
get unstable_sharedProfilingBuffer() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_getFirstCallbackNode;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -104,6 +104,20 @@
|
|||
);
|
||||
}
|
||||
|
||||
function unstable_startLoggingProfilingEvents() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_startLoggingProfilingEvents.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
function unstable_stopLoggingProfilingEvents() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Scheduler.unstable_stopLoggingProfilingEvents.apply(
|
||||
this,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
unstable_now: unstable_now,
|
||||
unstable_scheduleCallback: unstable_scheduleCallback,
|
||||
|
@ -118,6 +132,8 @@
|
|||
unstable_pauseExecution: unstable_pauseExecution,
|
||||
unstable_getFirstCallbackNode: unstable_getFirstCallbackNode,
|
||||
unstable_forceFrameRate: unstable_forceFrameRate,
|
||||
unstable_startLoggingProfilingEvents: unstable_startLoggingProfilingEvents,
|
||||
unstable_stopLoggingProfilingEvents: unstable_stopLoggingProfilingEvents,
|
||||
get unstable_IdlePriority() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_IdlePriority;
|
||||
|
@ -138,5 +154,9 @@
|
|||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
get unstable_sharedProfilingBuffer() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_getFirstCallbackNode;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,10 +8,14 @@
|
|||
|
||||
/* eslint-disable no-var */
|
||||
|
||||
import {enableSchedulerDebugging} from './SchedulerFeatureFlags';
|
||||
import {
|
||||
requestHostCallback,
|
||||
enableSchedulerDebugging,
|
||||
enableProfiling,
|
||||
} from './SchedulerFeatureFlags';
|
||||
import {
|
||||
requestHostCallback as requestHostCallbackWithoutProfiling,
|
||||
requestHostTimeout,
|
||||
cancelHostCallback,
|
||||
cancelHostTimeout,
|
||||
shouldYieldToHost,
|
||||
getCurrentTime,
|
||||
|
@ -21,11 +25,26 @@ import {
|
|||
import {push, pop, peek} from './SchedulerMinHeap';
|
||||
|
||||
// TODO: Use symbols?
|
||||
var ImmediatePriority = 1;
|
||||
var UserBlockingPriority = 2;
|
||||
var NormalPriority = 3;
|
||||
var LowPriority = 4;
|
||||
var IdlePriority = 5;
|
||||
import {
|
||||
ImmediatePriority,
|
||||
UserBlockingPriority,
|
||||
NormalPriority,
|
||||
LowPriority,
|
||||
IdlePriority,
|
||||
} from './SchedulerPriorities';
|
||||
import {
|
||||
sharedProfilingBuffer,
|
||||
markTaskRun,
|
||||
markTaskYield,
|
||||
markTaskCompleted,
|
||||
markTaskCanceled,
|
||||
markTaskErrored,
|
||||
markSchedulerSuspended,
|
||||
markSchedulerUnsuspended,
|
||||
markTaskStart,
|
||||
stopLoggingProfilingEvents,
|
||||
startLoggingProfilingEvents,
|
||||
} from './SchedulerProfiling';
|
||||
|
||||
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
|
||||
// Math.pow(2, 30) - 1
|
||||
|
@ -60,15 +79,17 @@ var isPerformingWork = false;
|
|||
var isHostCallbackScheduled = false;
|
||||
var isHostTimeoutScheduled = false;
|
||||
|
||||
function flushTask(task, callback, currentTime) {
|
||||
currentPriorityLevel = task.priorityLevel;
|
||||
var didUserCallbackTimeout = task.expirationTime <= currentTime;
|
||||
var continuationCallback = callback(didUserCallbackTimeout);
|
||||
return typeof continuationCallback === 'function'
|
||||
? continuationCallback
|
||||
: null;
|
||||
function requestHostCallbackWithProfiling(cb, time) {
|
||||
if (enableProfiling) {
|
||||
markSchedulerSuspended(time);
|
||||
requestHostCallbackWithoutProfiling(cb);
|
||||
}
|
||||
}
|
||||
|
||||
const requestHostCallback = enableProfiling
|
||||
? requestHostCallbackWithProfiling
|
||||
: requestHostCallbackWithoutProfiling;
|
||||
|
||||
function advanceTimers(currentTime) {
|
||||
// Check for tasks that are no longer delayed and add them to the queue.
|
||||
let timer = peek(timerQueue);
|
||||
|
@ -81,6 +102,10 @@ function advanceTimers(currentTime) {
|
|||
pop(timerQueue);
|
||||
timer.sortIndex = timer.expirationTime;
|
||||
push(taskQueue, timer);
|
||||
if (enableProfiling) {
|
||||
markTaskStart(timer);
|
||||
timer.isQueued = true;
|
||||
}
|
||||
} else {
|
||||
// Remaining timers are pending.
|
||||
return;
|
||||
|
@ -96,7 +121,7 @@ function handleTimeout(currentTime) {
|
|||
if (!isHostCallbackScheduled) {
|
||||
if (peek(taskQueue) !== null) {
|
||||
isHostCallbackScheduled = true;
|
||||
requestHostCallback(flushWork);
|
||||
requestHostCallback(flushWork, currentTime);
|
||||
} else {
|
||||
const firstTimer = peek(timerQueue);
|
||||
if (firstTimer !== null) {
|
||||
|
@ -107,6 +132,10 @@ function handleTimeout(currentTime) {
|
|||
}
|
||||
|
||||
function flushWork(hasTimeRemaining, initialTime) {
|
||||
if (isHostCallbackScheduled) {
|
||||
markSchedulerUnsuspended(initialTime);
|
||||
}
|
||||
|
||||
// We'll need a host callback the next time work is scheduled.
|
||||
isHostCallbackScheduled = false;
|
||||
if (isHostTimeoutScheduled) {
|
||||
|
@ -135,15 +164,24 @@ function flushWork(hasTimeRemaining, initialTime) {
|
|||
const callback = currentTask.callback;
|
||||
if (callback !== null) {
|
||||
currentTask.callback = null;
|
||||
const continuation = flushTask(currentTask, callback, currentTime);
|
||||
if (continuation !== null) {
|
||||
currentTask.callback = continuation;
|
||||
currentPriorityLevel = currentTask.priorityLevel;
|
||||
const didUserCallbackTimeout =
|
||||
currentTask.expirationTime <= currentTime;
|
||||
markTaskRun(currentTask, currentTime);
|
||||
const continuationCallback = callback(didUserCallbackTimeout);
|
||||
currentTime = getCurrentTime();
|
||||
if (typeof continuationCallback === 'function') {
|
||||
currentTask.callback = continuationCallback;
|
||||
markTaskYield(currentTask, currentTime);
|
||||
} else {
|
||||
if (enableProfiling) {
|
||||
markTaskCompleted(currentTask, currentTime);
|
||||
currentTask.isQueued = false;
|
||||
}
|
||||
if (currentTask === peek(taskQueue)) {
|
||||
pop(taskQueue);
|
||||
}
|
||||
}
|
||||
currentTime = getCurrentTime();
|
||||
advanceTimers(currentTime);
|
||||
} else {
|
||||
pop(taskQueue);
|
||||
|
@ -152,6 +190,8 @@ function flushWork(hasTimeRemaining, initialTime) {
|
|||
}
|
||||
// Return whether there's additional work
|
||||
if (currentTask !== null) {
|
||||
markSchedulerSuspended(currentTime);
|
||||
isHostCallbackScheduled = true;
|
||||
return true;
|
||||
} else {
|
||||
let firstTimer = peek(timerQueue);
|
||||
|
@ -160,6 +200,18 @@ function flushWork(hasTimeRemaining, initialTime) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
if (currentTask !== null) {
|
||||
if (enableProfiling) {
|
||||
const currentTime = getCurrentTime();
|
||||
markTaskErrored(currentTask, currentTime);
|
||||
currentTask.isQueued = false;
|
||||
}
|
||||
if (currentTask === peek(taskQueue)) {
|
||||
pop(taskQueue);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
currentTask = null;
|
||||
currentPriorityLevel = previousPriorityLevel;
|
||||
|
@ -250,6 +302,8 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
|
|||
|
||||
var startTime;
|
||||
var timeout;
|
||||
// TODO: Expose the current label when profiling, somehow
|
||||
// var label;
|
||||
if (typeof options === 'object' && options !== null) {
|
||||
var delay = options.delay;
|
||||
if (typeof delay === 'number' && delay > 0) {
|
||||
|
@ -261,6 +315,12 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
|
|||
typeof options.timeout === 'number'
|
||||
? options.timeout
|
||||
: timeoutForPriorityLevel(priorityLevel);
|
||||
// if (enableProfiling) {
|
||||
// var _label = options.label;
|
||||
// if (typeof _label === 'string') {
|
||||
// label = _label;
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
timeout = timeoutForPriorityLevel(priorityLevel);
|
||||
startTime = currentTime;
|
||||
|
@ -269,7 +329,7 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
|
|||
var expirationTime = startTime + timeout;
|
||||
|
||||
var newTask = {
|
||||
id: taskIdCounter++,
|
||||
id: ++taskIdCounter,
|
||||
callback,
|
||||
priorityLevel,
|
||||
startTime,
|
||||
|
@ -277,6 +337,13 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
|
|||
sortIndex: -1,
|
||||
};
|
||||
|
||||
if (enableProfiling) {
|
||||
newTask.isQueued = false;
|
||||
// if (typeof options === 'object' && options !== null) {
|
||||
// newTask.label = label;
|
||||
// }
|
||||
}
|
||||
|
||||
if (startTime > currentTime) {
|
||||
// This is a delayed task.
|
||||
newTask.sortIndex = startTime;
|
||||
|
@ -295,11 +362,15 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
|
|||
} else {
|
||||
newTask.sortIndex = expirationTime;
|
||||
push(taskQueue, newTask);
|
||||
if (enableProfiling) {
|
||||
markTaskStart(newTask, currentTime);
|
||||
newTask.isQueued = true;
|
||||
}
|
||||
// Schedule a host callback, if needed. If we're already performing work,
|
||||
// wait until the next time we yield.
|
||||
if (!isHostCallbackScheduled && !isPerformingWork) {
|
||||
isHostCallbackScheduled = true;
|
||||
requestHostCallback(flushWork);
|
||||
requestHostCallback(flushWork, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,7 +385,12 @@ function unstable_continueExecution() {
|
|||
isSchedulerPaused = false;
|
||||
if (!isHostCallbackScheduled && !isPerformingWork) {
|
||||
isHostCallbackScheduled = true;
|
||||
requestHostCallback(flushWork);
|
||||
if (enableProfiling) {
|
||||
const currentTime = getCurrentTime();
|
||||
requestHostCallbackWithProfiling(flushWork, currentTime);
|
||||
} else {
|
||||
requestHostCallback(flushWork);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -323,10 +399,26 @@ function unstable_getFirstCallbackNode() {
|
|||
}
|
||||
|
||||
function unstable_cancelCallback(task) {
|
||||
// Null out the callback to indicate the task has been canceled. (Can't remove
|
||||
// from the queue because you can't remove arbitrary nodes from an array based
|
||||
// heap, only the first one.)
|
||||
task.callback = null;
|
||||
if (enableProfiling && task.isQueued) {
|
||||
const currentTime = getCurrentTime();
|
||||
markTaskCanceled(task, currentTime);
|
||||
task.isQueued = false;
|
||||
}
|
||||
if (task !== null && task === peek(taskQueue)) {
|
||||
pop(taskQueue);
|
||||
if (enableProfiling && !isPerformingWork && taskQueue.length === 0) {
|
||||
// The queue is now empty.
|
||||
const currentTime = getCurrentTime();
|
||||
markSchedulerUnsuspended(currentTime);
|
||||
isHostCallbackScheduled = false;
|
||||
cancelHostCallback();
|
||||
}
|
||||
} else {
|
||||
// Null out the callback to indicate the task has been canceled. (Can't
|
||||
// remove from the queue because you can't remove arbitrary nodes from an
|
||||
// array based heap, only the first one.)
|
||||
task.callback = null;
|
||||
}
|
||||
}
|
||||
|
||||
function unstable_getCurrentPriorityLevel() {
|
||||
|
@ -370,3 +462,16 @@ export {
|
|||
getCurrentTime as unstable_now,
|
||||
forceFrameRate as unstable_forceFrameRate,
|
||||
};
|
||||
|
||||
export const unstable_startLoggingProfilingEvents = enableProfiling
|
||||
? startLoggingProfilingEvents
|
||||
: null;
|
||||
|
||||
export const unstable_stopLoggingProfilingEvents = enableProfiling
|
||||
? stopLoggingProfilingEvents
|
||||
: null;
|
||||
|
||||
// Expose a shared array buffer that contains profiling information.
|
||||
export const unstable_sharedProfilingBuffer = enableProfiling
|
||||
? sharedProfilingBuffer
|
||||
: null;
|
||||
|
|
|
@ -11,3 +11,4 @@ export const enableIsInputPending = false;
|
|||
export const requestIdleCallbackBeforeFirstFrame = false;
|
||||
export const requestTimerEventBeforeFirstFrame = false;
|
||||
export const enableMessageLoopImplementation = false;
|
||||
export const enableProfiling = __PROFILE__;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
// TODO: Use symbols?
|
||||
export const NoPriority = 0;
|
||||
export const ImmediatePriority = 1;
|
||||
export const UserBlockingPriority = 2;
|
||||
export const NormalPriority = 3;
|
||||
export const LowPriority = 4;
|
||||
export const IdlePriority = 5;
|
|
@ -0,0 +1,210 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {PriorityLevel} from './SchedulerPriorities';
|
||||
import {enableProfiling} from './SchedulerFeatureFlags';
|
||||
|
||||
import {NoPriority} from './SchedulerPriorities';
|
||||
|
||||
let runIdCounter: number = 0;
|
||||
let mainThreadIdCounter: number = 0;
|
||||
|
||||
const profilingStateSize = 4;
|
||||
export const sharedProfilingBuffer =
|
||||
// $FlowFixMe Flow doesn't know about SharedArrayBuffer
|
||||
typeof SharedArrayBuffer === 'function'
|
||||
? new SharedArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT)
|
||||
: // $FlowFixMe Flow doesn't know about ArrayBuffer
|
||||
new ArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT);
|
||||
|
||||
const profilingState = enableProfiling
|
||||
? new Int32Array(sharedProfilingBuffer)
|
||||
: null;
|
||||
|
||||
const PRIORITY = 0;
|
||||
const CURRENT_TASK_ID = 1;
|
||||
const CURRENT_RUN_ID = 2;
|
||||
const QUEUE_SIZE = 3;
|
||||
|
||||
if (enableProfiling && profilingState !== null) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
// This is maintained with a counter, because the size of the priority queue
|
||||
// array might include canceled tasks.
|
||||
profilingState[QUEUE_SIZE] = 0;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
}
|
||||
|
||||
const INITIAL_EVENT_LOG_SIZE = 1000;
|
||||
|
||||
let eventLogSize = 0;
|
||||
let eventLogBuffer = null;
|
||||
let eventLog = null;
|
||||
let eventLogIndex = 0;
|
||||
|
||||
const TaskStartEvent = 1;
|
||||
const TaskCompleteEvent = 2;
|
||||
const TaskErrorEvent = 3;
|
||||
const TaskCancelEvent = 4;
|
||||
const TaskRunEvent = 5;
|
||||
const TaskYieldEvent = 6;
|
||||
const SchedulerSuspendEvent = 7;
|
||||
const SchedulerResumeEvent = 8;
|
||||
|
||||
function logEvent(entries) {
|
||||
if (eventLog !== null) {
|
||||
const offset = eventLogIndex;
|
||||
eventLogIndex += entries.length;
|
||||
if (eventLogIndex + 1 > eventLogSize) {
|
||||
eventLogSize = eventLogIndex + 1;
|
||||
const newEventLog = new Int32Array(
|
||||
eventLogSize * Int32Array.BYTES_PER_ELEMENT,
|
||||
);
|
||||
newEventLog.set(eventLog);
|
||||
eventLogBuffer = newEventLog.buffer;
|
||||
eventLog = newEventLog;
|
||||
}
|
||||
eventLog.set(entries, offset);
|
||||
}
|
||||
}
|
||||
|
||||
export function startLoggingProfilingEvents(): void {
|
||||
eventLogSize = INITIAL_EVENT_LOG_SIZE;
|
||||
eventLogBuffer = new ArrayBuffer(eventLogSize * Int32Array.BYTES_PER_ELEMENT);
|
||||
eventLog = new Int32Array(eventLogBuffer);
|
||||
eventLogIndex = 0;
|
||||
}
|
||||
|
||||
export function stopLoggingProfilingEvents(): ArrayBuffer | null {
|
||||
const buffer = eventLogBuffer;
|
||||
eventLogBuffer = eventLog = null;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function markTaskStart(
|
||||
task: {id: number, priorityLevel: PriorityLevel},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
if (profilingState !== null) {
|
||||
profilingState[QUEUE_SIZE]++;
|
||||
}
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskStartEvent, time, task.id, task.priorityLevel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskCompleted(
|
||||
task: {
|
||||
id: number,
|
||||
priorityLevel: PriorityLevel,
|
||||
},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
if (profilingState !== null) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
profilingState[QUEUE_SIZE]--;
|
||||
}
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskCompleteEvent, time, task.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskCanceled(
|
||||
task: {
|
||||
id: number,
|
||||
priorityLevel: PriorityLevel,
|
||||
},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
if (profilingState !== null) {
|
||||
profilingState[QUEUE_SIZE]--;
|
||||
}
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskCancelEvent, time, task.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskErrored(
|
||||
task: {
|
||||
id: number,
|
||||
priorityLevel: PriorityLevel,
|
||||
},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
if (profilingState !== null) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
profilingState[QUEUE_SIZE]--;
|
||||
}
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskErrorEvent, time, task.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskRun(
|
||||
task: {id: number, priorityLevel: PriorityLevel},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
runIdCounter++;
|
||||
|
||||
if (profilingState !== null) {
|
||||
profilingState[PRIORITY] = task.priorityLevel;
|
||||
profilingState[CURRENT_TASK_ID] = task.id;
|
||||
profilingState[CURRENT_RUN_ID] = runIdCounter;
|
||||
}
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskRunEvent, time, task.id, runIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskYield(task: {id: number}, time: number) {
|
||||
if (enableProfiling) {
|
||||
if (profilingState !== null) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
profilingState[CURRENT_RUN_ID] = 0;
|
||||
}
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskYieldEvent, time, task.id, runIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markSchedulerSuspended(time: number) {
|
||||
if (enableProfiling) {
|
||||
mainThreadIdCounter++;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([SchedulerSuspendEvent, time, mainThreadIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markSchedulerUnsuspended(time: number) {
|
||||
if (enableProfiling) {
|
||||
if (eventLog !== null) {
|
||||
logEvent([SchedulerResumeEvent, time, mainThreadIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -59,11 +59,15 @@ describe('SchedulerDOM', () => {
|
|||
runPostMessageCallbacks(config);
|
||||
}
|
||||
|
||||
let frameSize = 33;
|
||||
let startOfLatestFrame = 0;
|
||||
let currentTime = 0;
|
||||
let frameSize;
|
||||
let startOfLatestFrame;
|
||||
let currentTime;
|
||||
|
||||
beforeEach(() => {
|
||||
frameSize = 33;
|
||||
startOfLatestFrame = 0;
|
||||
currentTime = 0;
|
||||
|
||||
delete global.performance;
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return rAFCallbacks.push(() => {
|
||||
|
|
|
@ -0,0 +1,449 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
/* eslint-disable no-for-of-loops/no-for-of-loops */
|
||||
|
||||
'use strict';
|
||||
|
||||
let Scheduler;
|
||||
let sharedProfilingArray;
|
||||
// let runWithPriority;
|
||||
let ImmediatePriority;
|
||||
let UserBlockingPriority;
|
||||
let NormalPriority;
|
||||
let LowPriority;
|
||||
let IdlePriority;
|
||||
let scheduleCallback;
|
||||
let cancelCallback;
|
||||
// let wrapCallback;
|
||||
// let getCurrentPriorityLevel;
|
||||
// let shouldYield;
|
||||
|
||||
function priorityLevelToString(priorityLevel) {
|
||||
switch (priorityLevel) {
|
||||
case ImmediatePriority:
|
||||
return 'Immediate';
|
||||
case UserBlockingPriority:
|
||||
return 'User-blocking';
|
||||
case NormalPriority:
|
||||
return 'Normal';
|
||||
case LowPriority:
|
||||
return 'Low';
|
||||
case IdlePriority:
|
||||
return 'Idle';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
describe('Scheduler', () => {
|
||||
if (!__PROFILE__) {
|
||||
// The tests in this suite only apply when profiling is on
|
||||
it('profiling APIs are not available', () => {
|
||||
Scheduler = require('scheduler');
|
||||
expect(Scheduler.unstable_stopLoggingProfilingEvents).toBe(null);
|
||||
expect(Scheduler.unstable_sharedProfilingBuffer).toBe(null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.mock('scheduler', () => require('scheduler/unstable_mock'));
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
sharedProfilingArray = new Int32Array(
|
||||
Scheduler.unstable_sharedProfilingBuffer,
|
||||
);
|
||||
|
||||
// runWithPriority = Scheduler.unstable_runWithPriority;
|
||||
ImmediatePriority = Scheduler.unstable_ImmediatePriority;
|
||||
UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
|
||||
NormalPriority = Scheduler.unstable_NormalPriority;
|
||||
LowPriority = Scheduler.unstable_LowPriority;
|
||||
IdlePriority = Scheduler.unstable_IdlePriority;
|
||||
scheduleCallback = Scheduler.unstable_scheduleCallback;
|
||||
cancelCallback = Scheduler.unstable_cancelCallback;
|
||||
// wrapCallback = Scheduler.unstable_wrapCallback;
|
||||
// getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
|
||||
// shouldYield = Scheduler.unstable_shouldYield;
|
||||
});
|
||||
|
||||
const PRIORITY = 0;
|
||||
const CURRENT_TASK_ID = 1;
|
||||
const CURRENT_RUN_ID = 2;
|
||||
const QUEUE_SIZE = 3;
|
||||
|
||||
afterEach(() => {
|
||||
if (sharedProfilingArray[QUEUE_SIZE] !== 0) {
|
||||
throw Error(
|
||||
'Test exited, but the shared profiling buffer indicates that a task ' +
|
||||
'is still running',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const TaskStartEvent = 1;
|
||||
const TaskCompleteEvent = 2;
|
||||
const TaskErrorEvent = 3;
|
||||
const TaskCancelEvent = 4;
|
||||
const TaskRunEvent = 5;
|
||||
const TaskYieldEvent = 6;
|
||||
const SchedulerSuspendEvent = 7;
|
||||
const SchedulerResumeEvent = 8;
|
||||
|
||||
function stopProfilingAndPrintFlamegraph() {
|
||||
const eventLog = new Int32Array(
|
||||
Scheduler.unstable_stopLoggingProfilingEvents(),
|
||||
);
|
||||
|
||||
const tasks = new Map();
|
||||
const mainThreadRuns = [];
|
||||
|
||||
let i = 0;
|
||||
processLog: while (i < eventLog.length) {
|
||||
const instruction = eventLog[i];
|
||||
const time = eventLog[i + 1];
|
||||
switch (instruction) {
|
||||
case 0: {
|
||||
break processLog;
|
||||
}
|
||||
case TaskStartEvent: {
|
||||
const taskId = eventLog[i + 2];
|
||||
const priorityLevel = eventLog[i + 3];
|
||||
const task = {
|
||||
id: taskId,
|
||||
priorityLevel,
|
||||
label: null,
|
||||
start: time,
|
||||
end: -1,
|
||||
exitStatus: null,
|
||||
runs: [],
|
||||
};
|
||||
tasks.set(taskId, task);
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
case TaskCompleteEvent: {
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.end = time;
|
||||
task.exitStatus = 'completed';
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
case TaskErrorEvent: {
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.end = time;
|
||||
task.exitStatus = 'errored';
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
case TaskCancelEvent: {
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.end = time;
|
||||
task.exitStatus = 'canceled';
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
case TaskRunEvent:
|
||||
case TaskYieldEvent: {
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.runs.push(time);
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
case SchedulerSuspendEvent:
|
||||
case SchedulerResumeEvent: {
|
||||
mainThreadRuns.push(time);
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw Error('Unknown instruction type: ' + instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can render the tasks as a flamegraph.
|
||||
const labelColumnWidth = 30;
|
||||
const msPerChar = 50;
|
||||
|
||||
let result = '';
|
||||
|
||||
const mainThreadLabelColumn = '!!! Main thread ';
|
||||
let mainThreadTimelineColumn = '';
|
||||
let isMainThreadBusy = false;
|
||||
for (const time of mainThreadRuns) {
|
||||
const index = time / msPerChar;
|
||||
mainThreadTimelineColumn += (isMainThreadBusy ? '█' : ' ').repeat(
|
||||
index - mainThreadTimelineColumn.length,
|
||||
);
|
||||
isMainThreadBusy = !isMainThreadBusy;
|
||||
}
|
||||
result += `${mainThreadLabelColumn}│${mainThreadTimelineColumn}\n`;
|
||||
|
||||
const tasksByPriority = Array.from(tasks.values()).sort(
|
||||
(t1, t2) => t1.priorityLevel - t2.priorityLevel,
|
||||
);
|
||||
|
||||
for (const task of tasksByPriority) {
|
||||
let label = task.label;
|
||||
if (label === undefined) {
|
||||
label = 'Task';
|
||||
}
|
||||
let labelColumn = `Task ${task.id} [${priorityLevelToString(
|
||||
task.priorityLevel,
|
||||
)}]`;
|
||||
labelColumn += ' '.repeat(labelColumnWidth - labelColumn.length - 1);
|
||||
|
||||
// Add empty space up until the start mark
|
||||
let timelineColumn = ' '.repeat(task.start / msPerChar);
|
||||
|
||||
let isRunning = false;
|
||||
for (const time of task.runs) {
|
||||
const index = time / msPerChar;
|
||||
timelineColumn += (isRunning ? '█' : '░').repeat(
|
||||
index - timelineColumn.length,
|
||||
);
|
||||
isRunning = !isRunning;
|
||||
}
|
||||
|
||||
const endIndex = task.end / msPerChar;
|
||||
timelineColumn += (isRunning ? '█' : '░').repeat(
|
||||
endIndex - timelineColumn.length,
|
||||
);
|
||||
|
||||
if (task.exitStatus !== 'completed') {
|
||||
timelineColumn += `🡐 ${task.exitStatus}`;
|
||||
}
|
||||
|
||||
result += `${labelColumn}│${timelineColumn}\n`;
|
||||
}
|
||||
|
||||
return '\n' + result;
|
||||
}
|
||||
|
||||
function getProfilingInfo() {
|
||||
const queueSize = sharedProfilingArray[QUEUE_SIZE];
|
||||
if (queueSize === 0) {
|
||||
return 'Empty Queue';
|
||||
}
|
||||
const priorityLevel = sharedProfilingArray[PRIORITY];
|
||||
if (priorityLevel === 0) {
|
||||
return 'Suspended, Queue Size: ' + queueSize;
|
||||
}
|
||||
return (
|
||||
`Task: ${sharedProfilingArray[CURRENT_TASK_ID]}, ` +
|
||||
`Run: ${sharedProfilingArray[CURRENT_RUN_ID]}, ` +
|
||||
`Priority: ${priorityLevelToString(priorityLevel)}, ` +
|
||||
`Queue Size: ${sharedProfilingArray[QUEUE_SIZE]}`
|
||||
);
|
||||
}
|
||||
|
||||
it('creates a basic flamegraph', () => {
|
||||
Scheduler.unstable_startLoggingProfilingEvents();
|
||||
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
scheduleCallback(
|
||||
NormalPriority,
|
||||
() => {
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
scheduleCallback(
|
||||
UserBlockingPriority,
|
||||
() => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
},
|
||||
{label: 'Bar'},
|
||||
);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
Scheduler.unstable_yieldValue('Yield');
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
};
|
||||
},
|
||||
{label: 'Foo'},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'Task: 1, Run: 1, Priority: Normal, Queue Size: 1',
|
||||
'Yield',
|
||||
]);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Task: 2, Run: 2, Priority: User-blocking, Queue Size: 2',
|
||||
'Task: 1, Run: 3, Priority: Normal, Queue Size: 1',
|
||||
]);
|
||||
|
||||
expect(getProfilingInfo()).toEqual('Empty Queue');
|
||||
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │ ██
|
||||
Task 2 [User-blocking] │ ░░░░██████
|
||||
Task 1 [Normal] │ ████████░░░░░░░░██████
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when a task is canceled', () => {
|
||||
Scheduler.unstable_startLoggingProfilingEvents();
|
||||
|
||||
const task = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
Scheduler.unstable_yieldValue('Yield');
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue('Continuation');
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'Task: 1, Run: 1, Priority: Normal, Queue Size: 1',
|
||||
'Yield',
|
||||
]);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
cancelCallback(task);
|
||||
|
||||
// Advance more time. This should not affect the size of the main
|
||||
// thread row, since the Scheduler queue is empty.
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
|
||||
// The main thread row should end when the callback is cancelled.
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │ ██
|
||||
Task 1 [Normal] │██████░░🡐 canceled
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when a task errors', () => {
|
||||
Scheduler.unstable_startLoggingProfilingEvents();
|
||||
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
throw Error('Oops');
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndThrow('Oops');
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
// Advance more time. This should not affect the size of the main
|
||||
// thread row, since the Scheduler queue is empty.
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
|
||||
// The main thread row should end when the callback is cancelled.
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │
|
||||
Task 1 [Normal] │██████🡐 errored
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a task that already finished', () => {
|
||||
Scheduler.unstable_startLoggingProfilingEvents();
|
||||
|
||||
const task = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue('A');
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
cancelCallback(task);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │
|
||||
Task 1 [Normal] │████████████████████
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a task multiple times', () => {
|
||||
Scheduler.unstable_startLoggingProfilingEvents();
|
||||
|
||||
scheduleCallback(
|
||||
NormalPriority,
|
||||
() => {
|
||||
Scheduler.unstable_yieldValue('A');
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
},
|
||||
{label: 'A'},
|
||||
);
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
const task = scheduleCallback(
|
||||
NormalPriority,
|
||||
() => {
|
||||
Scheduler.unstable_yieldValue('B');
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
},
|
||||
{label: 'B'},
|
||||
);
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
cancelCallback(task);
|
||||
cancelCallback(task);
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │████████████
|
||||
Task 1 [Normal] │░░░░░░░░░░░░████████████████████
|
||||
Task 2 [Normal] │ ░░░░░░░░🡐 canceled
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a delayed task', () => {
|
||||
Scheduler.unstable_startLoggingProfilingEvents();
|
||||
const task = scheduleCallback(
|
||||
NormalPriority,
|
||||
() => Scheduler.unstable_yieldValue('A'),
|
||||
{delay: 1000},
|
||||
);
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('resizes event log buffer if there are many events', () => {
|
||||
const tasks = [];
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
tasks.push(scheduleCallback(NormalPriority, () => {}));
|
||||
}
|
||||
expect(getProfilingInfo()).toEqual('Suspended, Queue Size: 5000');
|
||||
tasks.forEach(task => cancelCallback(task));
|
||||
expect(getProfilingInfo()).toEqual('Empty Queue');
|
||||
});
|
||||
});
|
|
@ -13,3 +13,5 @@ export const {
|
|||
requestTimerEventBeforeFirstFrame,
|
||||
enableMessageLoopImplementation,
|
||||
} = require('SchedulerFeatureFlags');
|
||||
|
||||
export const enableProfiling = __PROFILE__;
|
||||
|
|
|
@ -53,8 +53,9 @@ if (
|
|||
}
|
||||
}
|
||||
};
|
||||
const initialTime = Date.now();
|
||||
getCurrentTime = function() {
|
||||
return Date.now();
|
||||
return Date.now() - initialTime;
|
||||
};
|
||||
requestHostCallback = function(cb) {
|
||||
if (_callback !== null) {
|
||||
|
@ -111,10 +112,15 @@ if (
|
|||
typeof requestIdleCallback === 'function' &&
|
||||
typeof cancelIdleCallback === 'function';
|
||||
|
||||
getCurrentTime =
|
||||
typeof performance === 'object' && typeof performance.now === 'function'
|
||||
? () => performance.now()
|
||||
: () => Date.now();
|
||||
if (
|
||||
typeof performance === 'object' &&
|
||||
typeof performance.now === 'function'
|
||||
) {
|
||||
getCurrentTime = () => performance.now();
|
||||
} else {
|
||||
const initialTime = Date.now();
|
||||
getCurrentTime = () => Date.now() - initialTime;
|
||||
}
|
||||
|
||||
let isRAFLoopRunning = false;
|
||||
let isMessageLoopRunning = false;
|
||||
|
|
|
@ -411,7 +411,13 @@ const bundles = [
|
|||
|
||||
/******* React Scheduler (experimental) *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
|
||||
bundleTypes: [
|
||||
NODE_DEV,
|
||||
NODE_PROD,
|
||||
FB_WWW_DEV,
|
||||
FB_WWW_PROD,
|
||||
FB_WWW_PROFILING,
|
||||
],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'scheduler',
|
||||
global: 'Scheduler',
|
||||
|
|
|
@ -21,6 +21,11 @@ module.exports = {
|
|||
process: true,
|
||||
setImmediate: true,
|
||||
Buffer: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
|
|
@ -22,6 +22,11 @@ module.exports = {
|
|||
// Node.js Server Rendering
|
||||
setImmediate: true,
|
||||
Buffer: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
|
|
@ -21,6 +21,11 @@ module.exports = {
|
|||
// Fabric. See https://github.com/facebook/react/pull/15490
|
||||
// for more information
|
||||
nativeFabricUIManager: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
|
|
@ -24,6 +24,11 @@ module.exports = {
|
|||
define: true,
|
||||
require: true,
|
||||
global: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
|
Loading…
Reference in New Issue