Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 42 additions & 51 deletions packages/react-native/Libraries/BatchedBridge/MessageQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,54 +393,49 @@ class MessageQueue {
}

__callReactNativeMicrotasks() {
Systrace.beginEvent('JSTimers.callReactNativeMicrotasks()');
try {
Systrace.trace('JSTimers.callReactNativeMicrotasks()', () => {
if (this._reactNativeMicrotasksCallback != null) {
this._reactNativeMicrotasksCallback();
}
} finally {
Systrace.endEvent();
}
});
}

__callFunction(module: string, method: string, args: unknown[]): void {
this._lastFlush = Date.now();
this._eventLoopStartTime = this._lastFlush;
if (__DEV__ || this.__spy) {
Systrace.beginEvent(`${module}.${method}(${stringifySafe(args)})`);
} else {
Systrace.beginEvent(`${module}.${method}(...)`);
}
try {
if (this.__spy) {
this.__spy({type: TO_JS, module, method, args});
}
const moduleMethods = this.getCallableModule(module);
if (!moduleMethods) {
const callableModuleNames = Object.keys(this._lazyCallableModules);
const n = callableModuleNames.length;
const callableModuleNameList = callableModuleNames.join(', ');

// TODO(T122225939): Remove after investigation: Why are we getting to this line in bridgeless mode?
const isBridgelessMode =
global.RN$Bridgeless === true ? 'true' : 'false';
invariant(
false,
`Failed to call into JavaScript module method ${module}.${method}(). Module has not been registered as callable. Bridgeless Mode: ${isBridgelessMode}. Registered callable JavaScript modules (n = ${n}): ${callableModuleNameList}.
Systrace.trace(
__DEV__ || this.__spy
? `${module}.${method}(${stringifySafe(args)})`
: `${module}.${method}(...)`,
() => {
if (this.__spy) {
this.__spy({type: TO_JS, module, method, args});
}
const moduleMethods = this.getCallableModule(module);
if (!moduleMethods) {
const callableModuleNames = Object.keys(this._lazyCallableModules);
const n = callableModuleNames.length;
const callableModuleNameList = callableModuleNames.join(', ');

// TODO(T122225939): Remove after investigation: Why are we getting to this line in bridgeless mode?
const isBridgelessMode =
global.RN$Bridgeless === true ? 'true' : 'false';
invariant(
false,
`Failed to call into JavaScript module method ${module}.${method}(). Module has not been registered as callable. Bridgeless Mode: ${isBridgelessMode}. Registered callable JavaScript modules (n = ${n}): ${callableModuleNameList}.
A frequent cause of the error is that the application entry file path is incorrect. This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.`,
);
}
// $FlowFixMe[invalid-computed-prop]
if (!moduleMethods[method]) {
invariant(
false,
`Failed to call into JavaScript module method ${module}.${method}(). Module exists, but the method is undefined.`,
);
}
moduleMethods[method].apply(moduleMethods, args);
} finally {
Systrace.endEvent();
}
);
}
// $FlowFixMe[invalid-computed-prop]
if (!moduleMethods[method]) {
invariant(
false,
`Failed to call into JavaScript module method ${module}.${method}(). Module exists, but the method is undefined.`,
);
}
moduleMethods[method].apply(moduleMethods, args);
},
);
}

__invokeCallback(cbID: number, args: unknown[]): void {
Expand Down Expand Up @@ -471,28 +466,24 @@ class MessageQueue {
const profileName = debug
? '<callback for ' + module + '.' + method + '>'
: cbID;
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
if (callback && this.__spy) {
if (this.__spy) {
this.__spy({type: TO_JS, module: null, method: profileName, args});
}
Systrace.beginEvent(
Systrace.trace(
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`,
() => {
this._successCallbacks.delete(callID);
this._failureCallbacks.delete(callID);
callback(...args);
},
);
}

try {
} else {
if (!callback) {
return;
}

this._successCallbacks.delete(callID);
this._failureCallbacks.delete(callID);
callback(...args);
} finally {
if (__DEV__) {
Systrace.endEvent();
}
}
}
}
Expand Down
105 changes: 53 additions & 52 deletions packages/react-native/Libraries/Core/Timers/JSTimers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import NativeTiming from './NativeTiming';

const toError = require('../../../src/private/utilities/toError').default;
const BatchedBridge = require('../../BatchedBridge/BatchedBridge').default;
const Systrace = require('../../Performance/Systrace');
const {trace} = require('../../Performance/Systrace');
const invariant = require('invariant');

/**
Expand Down Expand Up @@ -96,47 +96,47 @@ function _callTimer(timerID: number, frameTime: number, didTimeout: ?boolean) {
return;
}

if (__DEV__) {
Systrace.beginEvent(type + ' [invoke]');
}

// Clear the metadata
if (type !== 'setInterval') {
_clearIndex(timerIndex);
}
const doCallTimer = () => {
// Clear the metadata
if (type !== 'setInterval') {
_clearIndex(timerIndex);
}

try {
if (
type === 'setTimeout' ||
type === 'setInterval' ||
type === 'queueReactNativeMicrotask'
) {
callback();
} else if (type === 'requestAnimationFrame') {
callback(global.performance.now());
} else if (type === 'requestIdleCallback') {
callback({
timeRemaining: function () {
// TODO: Optimisation: allow running for longer than one frame if
// there are no pending JS calls on the bridge from native. This
// would require a way to check the bridge queue synchronously.
return Math.max(
0,
FRAME_DURATION - (global.performance.now() - frameTime),
);
},
didTimeout: !!didTimeout,
});
} else {
console.error('Tried to call a callback with invalid type: ' + type);
try {
if (
type === 'setTimeout' ||
type === 'setInterval' ||
type === 'queueReactNativeMicrotask'
) {
callback();
} else if (type === 'requestAnimationFrame') {
callback(global.performance.now());
} else if (type === 'requestIdleCallback') {
callback({
timeRemaining: function () {
// TODO: Optimisation: allow running for longer than one frame if
// there are no pending JS calls on the bridge from native. This
// would require a way to check the bridge queue synchronously.
return Math.max(
0,
FRAME_DURATION - (global.performance.now() - frameTime),
);
},
didTimeout: !!didTimeout,
});
} else {
console.error('Tried to call a callback with invalid type: ' + type);
}
} catch (e: unknown) {
// Don't rethrow so that we can run all timers.
errors.push(toError(e));
}
} catch (e: unknown) {
// Don't rethrow so that we can run all timers.
errors.push(toError(e));
}
};

if (__DEV__) {
Systrace.endEvent();
trace(type + ' [invoke]', doCallTimer);
} else {
doCallTimer();
}
}

Expand All @@ -149,24 +149,25 @@ function _callReactNativeMicrotasksPass() {
return false;
}

if (__DEV__) {
Systrace.beginEvent('callReactNativeMicrotasksPass()');
}

// The main reason to extract a single pass is so that we can track
// in the system trace
const passReactNativeMicrotasks = reactNativeMicrotasks;
reactNativeMicrotasks = [];
const runPass = () => {
// The main reason to extract a single pass is so that we can track
// in the system trace
const passReactNativeMicrotasks = reactNativeMicrotasks;
reactNativeMicrotasks = [];

// Use for loop rather than forEach as per @vjeux's advice
// https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
for (let i = 0; i < passReactNativeMicrotasks.length; ++i) {
_callTimer(passReactNativeMicrotasks[i], 0);
}
// Use for loop rather than forEach as per @vjeux's advice
// https://github.com/facebook/react-native/commit/c8fd9f7588ad02d2293cac7224715f4af7b0f352#commitcomment-14570051
for (let i = 0; i < passReactNativeMicrotasks.length; ++i) {
_callTimer(passReactNativeMicrotasks[i], 0);
}
};

if (__DEV__) {
Systrace.endEvent();
trace('callReactNativeMicrotasksPass()', runPass);
} else {
runPass();
}

return reactNativeMicrotasks.length > 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import type {IEventEmitter} from '../vendor/emitter/EventEmitter';

import {beginEvent, endEvent} from '../Performance/Systrace';
import {trace} from '../Performance/Systrace';
import EventEmitter from '../vendor/emitter/EventEmitter';

// FIXME: use typed events
Expand All @@ -29,12 +29,12 @@ class RCTDeviceEventEmitterImpl extends EventEmitter<RCTDeviceEventDefinitions>
eventType: TEvent,
...args: RCTDeviceEventDefinitions[TEvent]
): void {
beginEvent(() => `RCTDeviceEventEmitter.emit#${eventType}`);
try {
super.emit(eventType, ...args);
} finally {
endEvent();
}
trace(
() => `RCTDeviceEventEmitter.emit#${eventType}`,
() => {
super.emit(eventType, ...args);
},
);
}
}
const RCTDeviceEventEmitter: IEventEmitter<RCTDeviceEventDefinitions> =
Expand Down
28 changes: 28 additions & 0 deletions packages/react-native/Libraries/Performance/Systrace.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,33 @@ export function endEvent(args?: EventArgs): void {
}
}

/**
* Traces the execution of the given function by marking its start with
* `beginEvent` and its end with `endEvent`, even if the function throws.
*
* @example
* Systrace.trace('myEvent', () => {
* // logic to trace
* });
*/
export function trace<T>(
eventName: EventName,
fn: () => T,
args?: EventArgs,
): T {
if (isEnabled()) {
const eventNameString =
typeof eventName === 'function' ? eventName() : eventName;
global.nativeTraceBeginSection(TRACE_TAG_REACT, eventNameString, args);
try {
return fn();
} finally {
global.nativeTraceEndSection(TRACE_TAG_REACT);
}
}
return fn();
}

/**
* Marks the start of a potentially asynchronous event. The end of this event
* should be marked calling the `endAsyncEvent` function with the cookie
Expand Down Expand Up @@ -128,6 +155,7 @@ if (__DEV__) {
setEnabled,
beginEvent,
endEvent,
trace,
beginAsyncEvent,
endAsyncEvent,
counterEvent,
Expand Down
10 changes: 8 additions & 2 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<7f88fbd0bea021db8f52ca8ef3709d9e>>
* @generated SignedSource<<08dd369849273136812ea5edbda6e1df>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -5041,6 +5041,7 @@ declare namespace Systrace {
setEnabled,
beginEvent,
endEvent,
trace,
beginAsyncEvent,
endAsyncEvent,
counterEvent,
Expand Down Expand Up @@ -5628,6 +5629,11 @@ declare type TouchEventProps = {
readonly onTouchStart?: (e: GestureResponderEvent) => void
readonly onTouchStartCapture?: (e: GestureResponderEvent) => void
}
declare function trace<T>(
eventName: EventName,
fn: () => T,
args?: EventArgs,
): T
declare type TransformsStyle = ____TransformStyle_Internal
declare interface TurboModule extends DEPRECATED_RCTExport<void> {}
declare namespace TurboModuleRegistry {
Expand Down Expand Up @@ -6217,7 +6223,7 @@ export {
Switch, // 015be3f7
SwitchChangeEvent, // 63e9c50b
SwitchProps, // 0dbf23ea
Systrace, // b5aa21fc
Systrace, // 626d178c
TVViewPropsIOS, // 330ce7b5
TargetedEvent, // 16e98910
TaskProvider, // 266dedf2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type IntersectionObserver, {
import type IntersectionObserverEntry from '../IntersectionObserverEntry';
import type {NativeIntersectionObserverToken} from '../specs/NativeIntersectionObserver';

import * as Systrace from '../../../../../Libraries/Performance/Systrace';
import {trace} from '../../../../../Libraries/Performance/Systrace';
import {
getInstanceHandle,
getNativeNodeReference,
Expand Down Expand Up @@ -219,14 +219,10 @@ export function unobserve(
* entries to dispatch.
*/
function notifyIntersectionObservers(): void {
Systrace.beginEvent(
trace(
'IntersectionObserverManager.notifyIntersectionObservers',
doNotifyIntersectionObservers,
);
try {
doNotifyIntersectionObservers();
} finally {
Systrace.endEvent();
}
}

function doNotifyIntersectionObservers(): void {
Expand Down
Loading
Loading