diff --git a/packages/react-native/Libraries/BatchedBridge/MessageQueue.js b/packages/react-native/Libraries/BatchedBridge/MessageQueue.js index b177b30c1c1d..137233be1dfa 100644 --- a/packages/react-native/Libraries/BatchedBridge/MessageQueue.js +++ b/packages/react-native/Libraries/BatchedBridge/MessageQueue.js @@ -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 { @@ -471,28 +466,24 @@ class MessageQueue { const profileName = debug ? '' : 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(); - } } } } diff --git a/packages/react-native/Libraries/Core/Timers/JSTimers.js b/packages/react-native/Libraries/Core/Timers/JSTimers.js index 8dd62b7e21e6..82bd3c81de00 100644 --- a/packages/react-native/Libraries/Core/Timers/JSTimers.js +++ b/packages/react-native/Libraries/Core/Timers/JSTimers.js @@ -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'); /** @@ -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(); } } @@ -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; } diff --git a/packages/react-native/Libraries/EventEmitter/RCTDeviceEventEmitter.js b/packages/react-native/Libraries/EventEmitter/RCTDeviceEventEmitter.js index c1f6db1c5ee4..9b371bdb5b31 100644 --- a/packages/react-native/Libraries/EventEmitter/RCTDeviceEventEmitter.js +++ b/packages/react-native/Libraries/EventEmitter/RCTDeviceEventEmitter.js @@ -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 @@ -29,12 +29,12 @@ class RCTDeviceEventEmitterImpl extends EventEmitter 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 = diff --git a/packages/react-native/Libraries/Performance/Systrace.js b/packages/react-native/Libraries/Performance/Systrace.js index 4e1be11ce6f5..8b636f13f345 100644 --- a/packages/react-native/Libraries/Performance/Systrace.js +++ b/packages/react-native/Libraries/Performance/Systrace.js @@ -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( + 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 @@ -128,6 +155,7 @@ if (__DEV__) { setEnabled, beginEvent, endEvent, + trace, beginAsyncEvent, endAsyncEvent, counterEvent, diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 12b2066109a4..abd52ddb4370 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -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. */ @@ -5041,6 +5041,7 @@ declare namespace Systrace { setEnabled, beginEvent, endEvent, + trace, beginAsyncEvent, endAsyncEvent, counterEvent, @@ -5628,6 +5629,11 @@ declare type TouchEventProps = { readonly onTouchStart?: (e: GestureResponderEvent) => void readonly onTouchStartCapture?: (e: GestureResponderEvent) => void } +declare function trace( + eventName: EventName, + fn: () => T, + args?: EventArgs, +): T declare type TransformsStyle = ____TransformStyle_Internal declare interface TurboModule extends DEPRECATED_RCTExport {} declare namespace TurboModuleRegistry { @@ -6217,7 +6223,7 @@ export { Switch, // 015be3f7 SwitchChangeEvent, // 63e9c50b SwitchProps, // 0dbf23ea - Systrace, // b5aa21fc + Systrace, // 626d178c TVViewPropsIOS, // 330ce7b5 TargetedEvent, // 16e98910 TaskProvider, // 266dedf2 diff --git a/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js b/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js index 7e69617b1d66..97e0fcf49051 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/internals/IntersectionObserverManager.js @@ -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, @@ -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 { diff --git a/packages/react-native/src/private/webapis/mutationobserver/internals/MutationObserverManager.js b/packages/react-native/src/private/webapis/mutationobserver/internals/MutationObserverManager.js index 21c7698db84d..84cc0df65381 100644 --- a/packages/react-native/src/private/webapis/mutationobserver/internals/MutationObserverManager.js +++ b/packages/react-native/src/private/webapis/mutationobserver/internals/MutationObserverManager.js @@ -24,7 +24,7 @@ import type MutationObserver, { } from '../MutationObserver'; import type MutationRecord from '../MutationRecord'; -import * as Systrace from '../../../../../Libraries/Performance/Systrace'; +import {trace} from '../../../../../Libraries/Performance/Systrace'; import {getPublicInstanceFromInternalInstanceHandle} from '../../../../../Libraries/ReactNative/RendererProxy'; import warnOnce from '../../../../../Libraries/Utilities/warnOnce'; import {getNativeNodeReference} from '../../dom/nodes/internals/NodeInternals'; @@ -147,12 +147,10 @@ export function unobserveAll(mutationObserverId: number): void { * entries to dispatch. */ function notifyMutationObservers(): void { - Systrace.beginEvent('MutationObserverManager.notifyMutationObservers'); - try { - doNotifyMutationObservers(); - } finally { - Systrace.endEvent(); - } + trace( + 'MutationObserverManager.notifyMutationObservers', + doNotifyMutationObservers, + ); } function doNotifyMutationObservers(): void {