diff --git a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap index 1e17e6249a11d2..54afb865b0c49c 100644 --- a/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap +++ b/packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap @@ -9715,5 +9715,206 @@ declare module.exports: { get RootTagContext(): RootTagContext, get unstable_enableLogBox(): () => void, }; +declare export class PerformanceEventTiming extends PerformanceEntry { + constructor(init: { + name: string, + startTime?: DOMHighResTimeStamp, + duration?: DOMHighResTimeStamp, + processingStart?: DOMHighResTimeStamp, + processingEnd?: DOMHighResTimeStamp, + interactionId?: number, + }): void; + get processingStart(): DOMHighResTimeStamp; + get processingEnd(): DOMHighResTimeStamp; + get interactionId(): number; + toJSON(): PerformanceEventTimingJSON; +} +type EventCountsForEachCallbackType = + | (() => void) + | ((value: number) => void) + | ((value: number, key: string) => void) + | ((value: number, key: string, map: Map) => void); +declare export class EventCounts { + get size(): number; + entries(): Iterator<[string, number]>; + forEach(callback: EventCountsForEachCallbackType): void; + get(key: string): ?number; + has(key: string): boolean; + keys(): Iterator; + values(): Iterator; +} +" +`; + +exports[`public API should not change unintentionally src/private/webapis/performance/LongTasks.js 1`] = ` +"export type PerformanceLongTaskTimingJSON = { + ...PerformanceEntryJSON, + attribution: $ReadOnlyArray, + ... +}; +declare export class TaskAttributionTiming extends PerformanceEntry {} +declare export class PerformanceLongTaskTiming extends PerformanceEntry { + get attribution(): $ReadOnlyArray; + toJSON(): PerformanceLongTaskTimingJSON; +} +" +`; + +exports[`public API should not change unintentionally src/private/webapis/performance/MemoryInfo.js 1`] = ` +"type MemoryInfoLike = { + jsHeapSizeLimit: ?number, + totalJSHeapSize: ?number, + usedJSHeapSize: ?number, +}; +declare export default class MemoryInfo { + constructor(memoryInfo: ?MemoryInfoLike): void; + get jsHeapSizeLimit(): ?number; + get totalJSHeapSize(): ?number; + get usedJSHeapSize(): ?number; +} +" +`; + +exports[`public API should not change unintentionally src/private/webapis/performance/Performance.js 1`] = ` +"export type PerformanceMeasureOptions = { + detail?: DetailType, + start?: DOMHighResTimeStamp, + duration?: DOMHighResTimeStamp, + end?: DOMHighResTimeStamp, +}; +declare export default class Performance { + eventCounts: EventCounts; + get memory(): MemoryInfo; + get rnStartupTiming(): ReactNativeStartupTiming; + mark(markName: string, markOptions?: PerformanceMarkOptions): PerformanceMark; + clearMarks(markName?: string): void; + measure( + measureName: string, + startMarkOrOptions?: string | PerformanceMeasureOptions, + endMark?: string + ): PerformanceMeasure; + clearMeasures(measureName?: string): void; + now: () => DOMHighResTimeStamp; + getEntries(): PerformanceEntryList; + getEntriesByType(entryType: PerformanceEntryType): PerformanceEntryList; + getEntriesByName( + entryName: string, + entryType?: PerformanceEntryType + ): PerformanceEntryList; +} +" +`; + +exports[`public API should not change unintentionally src/private/webapis/performance/PerformanceEntry.js 1`] = ` +"export type DOMHighResTimeStamp = number; +export type PerformanceEntryType = + | \\"mark\\" + | \\"measure\\" + | \\"event\\" + | \\"longtask\\" + | \\"resource\\"; +export type PerformanceEntryJSON = { + name: string, + entryType: PerformanceEntryType, + startTime: DOMHighResTimeStamp, + duration: DOMHighResTimeStamp, + ... +}; +declare export class PerformanceEntry { + constructor(init: { + name: string, + entryType: PerformanceEntryType, + startTime: DOMHighResTimeStamp, + duration: DOMHighResTimeStamp, + }): void; + get name(): string; + get entryType(): PerformanceEntryType; + get startTime(): DOMHighResTimeStamp; + get duration(): DOMHighResTimeStamp; + toJSON(): PerformanceEntryJSON; +} +export type PerformanceEntryList = $ReadOnlyArray; +" +`; + +exports[`public API should not change unintentionally src/private/webapis/performance/PerformanceObserver.js 1`] = ` +"export { PerformanceEntry } from \\"./PerformanceEntry\\"; +declare export class PerformanceObserverEntryList { + constructor(entries: PerformanceEntryList): void; + getEntries(): PerformanceEntryList; + getEntriesByType(type: PerformanceEntryType): PerformanceEntryList; + getEntriesByName( + name: string, + type?: PerformanceEntryType + ): PerformanceEntryList; +} +export type PerformanceObserverCallbackOptions = { + droppedEntriesCount: number, +}; +export type PerformanceObserverCallback = ( + list: PerformanceObserverEntryList, + observer: PerformanceObserver, + options?: PerformanceObserverCallbackOptions +) => void; +export interface PerformanceObserverInit { + +entryTypes?: Array; + +type?: PerformanceEntryType; + +buffered?: boolean; + +durationThreshold?: DOMHighResTimeStamp; +} +declare export class PerformanceObserver { + constructor(callback: PerformanceObserverCallback): void; + observe(options: PerformanceObserverInit): void; + disconnect(): void; + static supportedEntryTypes: $ReadOnlyArray; +} +export { PerformanceEventTiming }; +" +`; + +exports[`public API should not change unintentionally src/private/webapis/performance/ReactNativeStartupTiming.js 1`] = ` +"type ReactNativeStartupTimingLike = { + startTime: ?number, + endTime: ?number, + initializeRuntimeStart: ?number, + initializeRuntimeEnd: ?number, + executeJavaScriptBundleEntryPointStart: ?number, + executeJavaScriptBundleEntryPointEnd: ?number, +}; +declare export default class ReactNativeStartupTiming { + constructor(startUpTiming: ?ReactNativeStartupTimingLike): void; + get startTime(): ?number; + get endTime(): ?number; + get initializeRuntimeStart(): ?number; + get initializeRuntimeEnd(): ?number; + get executeJavaScriptBundleEntryPointStart(): ?number; + get executeJavaScriptBundleEntryPointEnd(): ?number; +} +" +`; + +exports[`public API should not change unintentionally src/private/webapis/performance/UserTiming.js 1`] = ` +"export type DetailType = mixed; +export type PerformanceMarkOptions = { + detail?: DetailType, + startTime?: DOMHighResTimeStamp, +}; +export type TimeStampOrName = DOMHighResTimeStamp | string; +export type PerformanceMeasureInit = { + detail?: DetailType, + startTime: DOMHighResTimeStamp, + duration: DOMHighResTimeStamp, +}; +declare export class PerformanceMark extends PerformanceEntry { + constructor(markName: string, markOptions?: PerformanceMarkOptions): void; + get detail(): DetailType; +} +declare export class PerformanceMeasure extends PerformanceEntry { + constructor( + measureName: string, + measureOptions: PerformanceMeasureInit + ): void; + get detail(): DetailType; +} " `; diff --git a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake index c29cfe392abb15..7c8af8eb9b0f68 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +++ b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake @@ -22,6 +22,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/folly-flags.cmake) # We configured the REACT_COMMON_DIR variable as it's commonly used to reference # shared C++ code in other targets. set(REACT_COMMON_DIR ${REACT_ANDROID_DIR}/../ReactCommon) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) # If you have ccache installed, we're going to honor it. find_program(CCACHE_FOUND ccache) @@ -60,21 +61,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_BUILD_DIR}/generated/autolinking/src/main/jni) -target_compile_options(${CMAKE_PROJECT_NAME} - PRIVATE - -Wall - -Werror - # We suppress cpp #error and #warning to don't fail the build - # due to use migrating away from - # #include - # This can be removed for React Native 0.73 - -Wno-error=cpp - -fexceptions - -frtti - -std=c++20 - -DLOG_TAG=\"ReactNative\" - -DFOLLY_NO_CONFIG=1 -) +target_compile_reactnative_options(${CMAKE_PROJECT_NAME} PRIVATE) # Prefab packages from React Native find_package(ReactAndroid REQUIRED CONFIG) diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 1c5049ba237bc5..1f8395f08f49b6 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -8,6 +8,8 @@ set(CMAKE_VERBOSE_MAKEFILE on) project(ReactAndroid) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) + # Convert input paths to CMake format (with forward slashes) file(TO_CMAKE_PATH "${REACT_ANDROID_DIR}" REACT_ANDROID_DIR) file(TO_CMAKE_PATH "${REACT_BUILD_DIR}" REACT_BUILD_DIR) @@ -22,7 +24,6 @@ endif(CCACHE_FOUND) # Make sure every shared lib includes a .note.gnu.build-id header add_link_options(-Wl,--build-id) -add_compile_options(-Wall -Werror) function(add_react_android_subdir relative_path) add_subdirectory(${REACT_ANDROID_DIR}/${relative_path} ReactAndroid/${relative_path}) @@ -237,6 +238,8 @@ target_link_libraries(reactnative yogacore ) +target_compile_reactnative_options(reactnative PRIVATE) + target_include_directories(reactnative PUBLIC $ @@ -355,14 +358,9 @@ add_executable(reactnative_unittest # ${REACT_COMMON_DIR}/react/renderer/core/tests/ConcreteShadowNodeTest.cpp # ${REACT_COMMON_DIR}/react/renderer/core/tests/ComponentDescriptorTest.cpp ) - target_compile_options(reactnative_unittest - PRIVATE - -Wall - -Werror - -fexceptions - -frtti - -std=c++20 - -DHERMES_ENABLE_DEBUGGER) + +target_compile_reactnative_options(reactnative_unittest PRIVATE) +target_compile_options(reactnative_unittest PRIVATE -DHERMES_ENABLE_DEBUGGER) target_link_libraries(reactnative_unittest fabricjni diff --git a/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/CMakeLists.txt index 75a3d344d12101..10d449e0f8262e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/first-party/fbgloginit/CMakeLists.txt @@ -8,6 +8,8 @@ set(CMAKE_VERBOSE_MAKEFILE on) add_compile_options(-fexceptions -fno-omit-frame-pointer) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) + add_library(glog_init OBJECT glog_init.cpp) target_include_directories(glog_init PUBLIC .) diff --git a/packages/react-native/ReactAndroid/src/main/jni/first-party/jni-lib-merge/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/first-party/jni-lib-merge/CMakeLists.txt index 7311d6b5574d00..249df90db5b5da 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/first-party/jni-lib-merge/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/first-party/jni-lib-merge/CMakeLists.txt @@ -6,6 +6,8 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) + # This is the 'glue' library responsible of allowing to do so-merging in React Native # OSS. This library contains just a .c file that is included in every merged library. # diff --git a/packages/react-native/ReactAndroid/src/main/jni/first-party/yogajni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/first-party/yogajni/CMakeLists.txt index 4c0b4c357c6fde..ef06342a3b71f5 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/first-party/yogajni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/first-party/yogajni/CMakeLists.txt @@ -7,13 +7,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) - -add_compile_options( - -fvisibility=hidden - -fexceptions - -frtti - -std=c++20 - -O3) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB yoga_SRC CONFIGURE_DEPENDS jni/*.cpp) add_library(yoga OBJECT ${yoga_SRC}) @@ -27,3 +21,6 @@ target_link_libraries(yoga log android ) + +target_compile_reactnative_options(yoga PRIVATE) +target_compile_options(yoga PRIVATE -fvisibility=hidden -O3) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt index b0476f1ff86d89..8364037e15bb31 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt @@ -6,9 +6,8 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options(-fexceptions -frtti -std=c++20 -Wall -DLOG_TAG=\"ReactNative\") - include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB react_devsupportjni_SRC CONFIGURE_DEPENDS *.cpp) @@ -21,3 +20,5 @@ target_include_directories(react_devsupportjni PUBLIC .) target_link_libraries(react_devsupportjni fbjni jsinspector) + +target_compile_reactnative_options(react_devsupportjni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt index be33411847efc9..f25aac1e50cc7e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/CMakeLists.txt @@ -6,6 +6,7 @@ cmake_minimum_required(VERSION 3.13) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB fabricjni_SRCS CONFIGURE_DEPENDS *.cpp) @@ -62,12 +63,4 @@ target_link_libraries( yoga ) -target_compile_options( - fabricjni - PRIVATE - -DLOG_TAG=\"Fabric\" - -fexceptions - -frtti - -std=c++20 - -Wall -) +target_compile_reactnative_options(fabricjni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/CMakeLists.txt index 1294f29ebbe111..5fd94dd94387bc 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/CMakeLists.txt @@ -6,6 +6,7 @@ cmake_minimum_required(VERSION 3.13) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB react_featureflagsjni_SRCS CONFIGURE_DEPENDS *.cpp) @@ -25,13 +26,4 @@ target_link_libraries( ) target_merge_so(react_featureflagsjni) - -target_compile_options( - react_featureflagsjni - PRIVATE - -DLOG_TAG=\"ReactNative\" - -fexceptions - -frtti - -std=c++20 - -Wall -) +target_compile_reactnative_options(react_featureflagsjni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/CMakeLists.txt index e9067594f4ca69..9a9c7386b32413 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/instrumentation/CMakeLists.txt @@ -9,17 +9,14 @@ set(CMAKE_VERBOSE_MAKEFILE on) file(GLOB_RECURSE jsijniprofiler_SRC CONFIGURE_DEPENDS *.cpp) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library( jsijniprofiler OBJECT ${jsijniprofiler_SRC} ) -target_compile_options( - jsijniprofiler - PRIVATE - -fexceptions -) +target_compile_reactnative_options(jsijniprofiler PRIVATE) target_merge_so(jsijniprofiler) target_include_directories(jsijniprofiler PRIVATE .) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt index ab70319d7f3416..d0d015ccc55d4c 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt @@ -9,18 +9,12 @@ set(CMAKE_VERBOSE_MAKEFILE on) file(GLOB_RECURSE hermes_executor_SRC CONFIGURE_DEPENDS *.cpp) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(hermes_executor OBJECT ${hermes_executor_SRC} ) -target_compile_options( - hermes_executor - PRIVATE - $<$:-DHERMES_ENABLE_DEBUGGER=1> - -std=c++20 - -fexceptions -) target_merge_so(hermes_executor) target_include_directories(hermes_executor PRIVATE .) target_link_libraries( @@ -30,3 +24,5 @@ target_link_libraries( jsi reactnative ) +target_compile_reactnative_options(hermes_executor PRIVATE) +target_compile_options(hermes_executor PRIVATE $<$:-DHERMES_ENABLE_DEBUGGER=1>) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/tooling/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/tooling/CMakeLists.txt index 5b54e346e62ce6..61e042472b2c51 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/tooling/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/tooling/CMakeLists.txt @@ -7,6 +7,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) # hermestooling is a shared library where we merge all the hermes* related libraries. # diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt index c119c96d93c8e2..deb2b6a8b5f1df 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt @@ -6,12 +6,9 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -file(GLOB reactnativejni_SRC CONFIGURE_DEPENDS *.cpp) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) -add_compile_options( - -fexceptions - -Wno-unused-lambda-capture - -std=c++20) +file(GLOB reactnativejni_SRC CONFIGURE_DEPENDS *.cpp) ###################### ### reactnativejni ### @@ -41,3 +38,5 @@ target_link_libraries(reactnativejni runtimeexecutor yoga ) +target_compile_reactnative_options(reactnativejni PRIVATE) +target_compile_options(reactnativejni PRIVATE -Wno-unused-lambda-capture) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jscexecutor/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/jscexecutor/CMakeLists.txt index cbcd19bb8ef06f..8e94fe9c4f3066 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jscexecutor/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jscexecutor/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_VERBOSE_MAKEFILE on) add_compile_options(-fvisibility=hidden -fexceptions -frtti) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB jscexecutor_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) add_library(jscexecutor OBJECT ${jscexecutor_SRC}) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jsctooling/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/jsctooling/CMakeLists.txt index 1b30b2aba81085..ebf360962b8c2c 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jsctooling/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jsctooling/CMakeLists.txt @@ -7,6 +7,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) # jsctooling is a shared library where we merge all the jsc* related libraries. # diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/CMakeLists.txt index f246ef3e1c8836..c3426c0d74de4e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/CMakeLists.txt @@ -7,8 +7,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) - -add_compile_options(-fexceptions -frtti -std=c++20 -Wall -DLOG_TAG=\"Fabric\") +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB mapbuffer_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/react/common/mapbuffer/*.cpp) @@ -34,3 +33,5 @@ target_link_libraries(mapbufferjni react_utils yoga ) + +target_compile_reactnative_options(mapbufferjni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt index 451b1237ce8466..cf81e75c2028b6 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/newarchdefaults/CMakeLists.txt @@ -7,12 +7,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) - -add_compile_options(-fexceptions - -frtti - -std=c++20 - -Wall - -DLOG_TAG=\"ReactNative\") +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB react_newarchdefaults_SRC CONFIGURE_DEPENDS *.cpp) @@ -35,3 +30,5 @@ target_link_libraries(react_newarchdefaults react_nativemodule_microtasks react_nativemodule_idlecallbacks jsi) + +target_compile_reactnative_options(react_newarchdefaults PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/CMakeLists.txt index a9fee872142d59..a317e62a42704b 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/reactnativeblob/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_VERBOSE_MAKEFILE on) add_compile_options(-fvisibility=hidden -fexceptions -frtti) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB reactnativeblob_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) add_library(reactnativeblob OBJECT ${reactnativeblob_SRC}) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/reactperflogger/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/reactperflogger/CMakeLists.txt index 2b2ca3424ba683..4c571a037f1acb 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/reactperflogger/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/reactperflogger/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options(-fexceptions -frtti -std=c++20 -Wall) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(reactperfloggerjni INTERFACE) @@ -20,3 +20,5 @@ target_link_libraries(reactperfloggerjni fbjni android reactperflogger) + +target_compile_reactnative_options(reactperfloggerjni INTERFACE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/cxxreactpackage/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/cxxreactpackage/CMakeLists.txt index 3f9ada77aa99d2..b8400abe311a38 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/cxxreactpackage/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/cxxreactpackage/CMakeLists.txt @@ -6,13 +6,6 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options( - -fexceptions - -frtti - -Wno-unused-lambda-capture - -std=c++20) - - ######################### ### cxxreactpackage ### ######################### @@ -28,3 +21,7 @@ target_link_libraries(react_cxxreactpackage INTERFACE fb fbjni) + +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) +target_compile_reactnative_options(react_cxxreactpackage INTERFACE) +target_compile_options(react_cxxreactpackage INTERFACE -Wno-unused-lambda-capture) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt index 1d8e0cf0a3ead2..01fa22e5ae05f1 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt @@ -7,6 +7,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB_RECURSE hermes_instance_jni_SRC CONFIGURE_DEPENDS *.cpp) @@ -14,14 +15,6 @@ add_library(hermesinstancejni OBJECT ${hermes_instance_jni_SRC} ) -target_compile_options( - hermesinstancejni - PRIVATE - $<$:-DHERMES_ENABLE_DEBUGGER=1> - -std=c++20 - -fexceptions -) - target_include_directories(hermesinstancejni PRIVATE .) target_merge_so(hermesinstancejni) @@ -31,3 +24,6 @@ target_link_libraries(hermesinstancejni bridgelesshermes reactnative ) + +target_compile_reactnative_options(hermesinstancejni PRIVATE) +target_compile_options(hermesinstancejni PRIVATE $<$:-DHERMES_ENABLE_DEBUGGER=1>) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt index 51c6615502adb0..f5fe782c8d621c 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt @@ -7,6 +7,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB_RECURSE bridgeless_jni_SRC CONFIGURE_DEPENDS *.cpp) @@ -14,13 +15,10 @@ add_library(rninstance OBJECT ${bridgeless_jni_SRC} ) -target_compile_options( - rninstance - PRIVATE - $<$:-DHERMES_ENABLE_DEBUGGER=1> - -std=c++20 - -fexceptions -) + +target_compile_reactnative_options(rninstance PRIVATE) +target_compile_options(rninstance PRIVATE $<$:-DHERMES_ENABLE_DEBUGGER=1>) + target_merge_so(rninstance) target_include_directories(rninstance PUBLIC .) target_link_libraries( diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jsc/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jsc/jni/CMakeLists.txt index 893b00a0d42826..ecdb6c6583ee92 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jsc/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jsc/jni/CMakeLists.txt @@ -9,6 +9,7 @@ set(CMAKE_VERBOSE_MAKEFILE on) add_compile_options(-fvisibility=hidden -fexceptions -frtti) include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB jscinstance_SRC CONFIGURE_DEPENDS "*.cpp") diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt index 6673e1b6668a57..68c9b8df548af7 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt @@ -6,11 +6,8 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options( - -fexceptions - -frtti - -Wno-unused-lambda-capture - -std=c++20) +include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) ######################### ### callinvokerholder ### @@ -34,13 +31,12 @@ target_link_libraries(callinvokerholder runtimeexecutor callinvoker reactperfloggerjni) +target_compile_reactnative_options(callinvokerholder PRIVATE) ################################## ### react_nativemodule_manager ### ################################## -include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) - # TODO: rename to react_nativemodule_manager add_library( turbomodulejsijni @@ -65,3 +61,4 @@ target_link_libraries(turbomodulejsijni react_nativemodule_core callinvokerholder reactperfloggerjni) +target_compile_reactnative_options(turbomodulejsijni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/uimanager/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/uimanager/CMakeLists.txt index 34de52fb13840a..455255b3501bfa 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/uimanager/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/uimanager/CMakeLists.txt @@ -6,9 +6,8 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options(-fexceptions -frtti -std=c++20 -Wall -DLOG_TAG=\"ReactNative\") - include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB uimanagerjni_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) add_library(uimanagerjni @@ -36,3 +35,5 @@ target_link_libraries(uimanagerjni rrc_native yoga ) + +target_compile_reactnative_options(uimanagerjni PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/third-party/double-conversion/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/third-party/double-conversion/CMakeLists.txt index d32b34c5bf6add..4ae17aafe08358 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/third-party/double-conversion/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/third-party/double-conversion/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options(-Wno-unused-variable -Wno-unused-local-typedefs) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(double-conversion STATIC diff --git a/packages/react-native/ReactAndroid/src/main/jni/third-party/fast_float/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/third-party/fast_float/CMakeLists.txt index 1b9d34f9ba6b7f..0e98719b813435 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/third-party/fast_float/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/third-party/fast_float/CMakeLists.txt @@ -6,8 +6,9 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options(-std=c++20 -fexceptions) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(fast_float INTERFACE) target_include_directories(fast_float INTERFACE include) +target_compile_reactnative_options(fast_float INTERFACE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/third-party/fmt/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/third-party/fmt/CMakeLists.txt index 65d6a74095ffdf..75c401d66b2c4e 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/third-party/fmt/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/third-party/fmt/CMakeLists.txt @@ -6,8 +6,9 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options(-std=c++20 -fexceptions) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(fmt STATIC src/format.cc) target_include_directories(fmt PUBLIC include) +target_compile_reactnative_options(fmt PRIVATE) diff --git a/packages/react-native/ReactAndroid/src/main/jni/third-party/glog/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/third-party/glog/CMakeLists.txt index 0a87206a7c21e6..91a692d4c1de31 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/third-party/glog/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/third-party/glog/CMakeLists.txt @@ -6,15 +6,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options( - -Wwrite-strings - -Woverloaded-virtual - -Wno-sign-compare - -DNDEBUG - -g - -O2 - -DHAVE_PREAD=1 -) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(glog STATIC diff --git a/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake b/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake new file mode 100644 index 00000000000000..bbbd534d9ad3ff --- /dev/null +++ b/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake @@ -0,0 +1,33 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +# This CMake file exposes the React Native Flags that all the libraries should use when +# compiling a module that will end up inside libreactnative.so + +SET(reactnative_FLAGS + -Wall + -Werror + -fexceptions + -frtti + -std=c++20 + -DFOLLY_NO_CONFIG=1 + -DLOG_TAG=\"ReactNative\" +) + +# This function can be used to configure the reactnative flags for a specific target in +# a conveniente way. The usage is: +# +# target_compile_reactnative_options(target_name scope) +# +# scope is either PUBLIC, PRIVATE or INTERFACE + +function(target_compile_reactnative_options target_name scope) + target_compile_options(${target_name} ${scope} ${reactnative_FLAGS}) + target_compile_definitions(${target_name} ${scope} RN_SERIALIZABLE_STATE) +endfunction() + diff --git a/packages/react-native/ReactCommon/cxxreact/CMakeLists.txt b/packages/react-native/ReactCommon/cxxreact/CMakeLists.txt index 535ec84f69c0b8..4f4e6f9e57f1ef 100644 --- a/packages/react-native/ReactCommon/cxxreact/CMakeLists.txt +++ b/packages/react-native/ReactCommon/cxxreact/CMakeLists.txt @@ -29,3 +29,6 @@ target_link_libraries(react_cxxreact reactperflogger runtimeexecutor react_debug) + +target_compile_reactnative_options(react_cxxreact PRIVATE) +target_compile_options(react_cxxreact PRIVATE -Wno-unused-lambda-capture) diff --git a/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp b/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp index 385bb95abe9979..3ec29737c7aca8 100644 --- a/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp +++ b/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp @@ -11,9 +11,8 @@ #include #include -#include -#include +#include namespace facebook::react { @@ -26,8 +25,8 @@ std::string JSExecutor::getSyntheticBundlePath( return folly::to("seg-", bundleId, ".js"); } -double JSExecutor::performanceNow() { - return chronoToDOMHighResTimeStamp(std::chrono::steady_clock::now()); +HighResTimeStamp JSExecutor::performanceNow() { + return HighResTimeStamp::now(); } jsinspector_modern::RuntimeTargetDelegate& diff --git a/packages/react-native/ReactCommon/cxxreact/JSExecutor.h b/packages/react-native/ReactCommon/cxxreact/JSExecutor.h index 469f4ca6fd73d5..775e3971f58284 100644 --- a/packages/react-native/ReactCommon/cxxreact/JSExecutor.h +++ b/packages/react-native/ReactCommon/cxxreact/JSExecutor.h @@ -14,6 +14,7 @@ #include #include #include +#include #ifndef RN_EXPORT #define RN_EXPORT __attribute__((visibility("default"))) @@ -138,7 +139,7 @@ class RN_EXPORT JSExecutor { uint32_t bundleId, const std::string& bundlePath); - static double performanceNow(); + static HighResTimeStamp performanceNow(); /** * Get a reference to the \c RuntimeTargetDelegate owned (or implemented) by diff --git a/packages/react-native/ReactCommon/jsc/CMakeLists.txt b/packages/react-native/ReactCommon/jsc/CMakeLists.txt index 32525e2a06107d..8794cd530361f4 100644 --- a/packages/react-native/ReactCommon/jsc/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jsc/CMakeLists.txt @@ -35,3 +35,6 @@ target_link_libraries(jscruntime if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug) target_compile_options(jscruntime PRIVATE -DNDEBUG) endif() + +target_compile_reactnative_options(jscruntime PRIVATE) +target_compile_options(jscruntime PRIVATE -O3 -Wno-unused-lambda-capture) diff --git a/packages/react-native/ReactCommon/jsi/CMakeLists.txt b/packages/react-native/ReactCommon/jsi/CMakeLists.txt index 0967a7d7ee69b1..560dfa82e5aa52 100644 --- a/packages/react-native/ReactCommon/jsi/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jsi/CMakeLists.txt @@ -25,3 +25,6 @@ target_include_directories(jsi PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(jsi folly_runtime glog) + +target_compile_reactnative_options(jsi PRIVATE) +target_compile_options(jsi PRIVATE -O3 -Wno-unused-lambda-capture) diff --git a/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp b/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp index 70a361017a0fb4..c486832b02261b 100644 --- a/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp +++ b/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp @@ -557,7 +557,9 @@ void bindNativePerformanceNow(Runtime& runtime) { [](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, - size_t count) { return Value(JSExecutor::performanceNow()); })); + size_t /*count*/) { + return JSExecutor::performanceNow().toDOMHighResTimeStamp(); + })); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp index 4302a793e6cd6e..8e64e84d90801f 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp @@ -159,4 +159,39 @@ void RuntimeTargetController::notifyDebuggerSessionDestroyed() { target_.emitDebuggerSessionDestroyed(); } +void RuntimeTargetController::registerForTracing() { + target_.registerForTracing(); +} + +void RuntimeTargetController::enableSamplingProfiler() { + target_.enableSamplingProfiler(); +} + +void RuntimeTargetController::disableSamplingProfiler() { + target_.disableSamplingProfiler(); +} + +tracing::RuntimeSamplingProfile +RuntimeTargetController::collectSamplingProfile() { + return target_.collectSamplingProfile(); +} + +void RuntimeTarget::registerForTracing() { + jsExecutor_([](auto& /*runtime*/) { + tracing::PerformanceTracer::getInstance().reportJavaScriptThread(); + }); +} + +void RuntimeTarget::enableSamplingProfiler() { + delegate_.enableSamplingProfiler(); +} + +void RuntimeTarget::disableSamplingProfiler() { + delegate_.disableSamplingProfiler(); +} + +tracing::RuntimeSamplingProfile RuntimeTarget::collectSamplingProfile() { + return delegate_.collectSamplingProfile(); +} + } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp index 1a3415fc6a1a11..5570ab660b6e4e 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp @@ -14,15 +14,29 @@ namespace facebook::react::jsinspector_modern { bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { if (req.method == "Tracing.start") { // @cdp Tracing.start support is experimental. - if (PerformanceTracer::getInstance().startTracing()) { - frontendChannel_(cdp::jsonResult(req.id)); - } else { + if (!instanceAgent_) { + frontendChannel_(cdp::jsonError( + req.id, + cdp::ErrorCode::InternalError, + "Couldn't find instance available for Tracing")); + + return true; + } + + bool correctlyStartedPerformanceTracer = + tracing::PerformanceTracer::getInstance().startTracing(); + + if (!correctlyStartedPerformanceTracer) { frontendChannel_(cdp::jsonError( req.id, cdp::ErrorCode::InternalError, "Tracing session already started")); } + instanceAgent_->startTracing(); + instanceTracingStartTimestamp_ = HighResTimeStamp::now(); + frontendChannel_(cdp::jsonResult(req.id)); + return true; } else if (req.method == "Tracing.end") { // @cdp Tracing.end support is experimental. @@ -39,7 +53,15 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { folly::dynamic::object("value", eventsChunk))); }); - if (!wasStopped) { + return true; + } + + instanceAgent_->stopTracing(); + + tracing::PerformanceTracer& performanceTracer = + tracing::PerformanceTracer::getInstance(); + bool correctlyStopped = performanceTracer.stopTracing(); + if (!correctlyStopped) { frontendChannel_(cdp::jsonError( req.id, cdp::ErrorCode::InternalError, diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h index 339b9c77137456..a70087c97ddb22 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h @@ -9,6 +9,11 @@ #include "CdpJson.h" #include "InspectorInterfaces.h" +#include "InstanceAgent.h" + +#include +#include +#include namespace facebook::react::jsinspector_modern { @@ -36,6 +41,19 @@ class TracingAgent { * A channel used to send responses and events to the frontend. */ FrontendChannel frontendChannel_; + + /** + * Current InstanceAgent. May be null to signify that there is + * currently no active instance. + */ + std::shared_ptr instanceAgent_; + + /** + * Timestamp of when we started tracing of an Instance, will be used as a + * a start of JavaScript samples recording and as a time origin for the events + * in this trace. + */ + HighResTimeStamp instanceTracingStartTimestamp_; }; } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt b/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt new file mode 100644 index 00000000000000..162debbcf27f50 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake) + +add_compile_options( + -fexceptions + -std=c++20 + -Wall + -Wpedantic) + +file(GLOB jsinspector_network_SRC CONFIGURE_DEPENDS *.cpp) + +add_library(jsinspector_network OBJECT ${jsinspector_network_SRC}) +target_merge_so(jsinspector_network) + +target_include_directories(jsinspector_network PUBLIC ${REACT_COMMON_DIR}) + +target_link_libraries(jsinspector_network + folly_runtime + jsinspector_cdp + react_performance_timeline + react_timing) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp new file mode 100644 index 00000000000000..9f207a0a23620b --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "NetworkReporter.h" + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED +#include "CdpNetwork.h" +#endif + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED +#include +#include +#endif +#include +#include + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED +#include +#endif +#include + +namespace facebook::react::jsinspector_modern { + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED +namespace { + +/** + * Get the current Unix timestamp in seconds (µs precision, CDP format). + */ +double getCurrentUnixTimestampSeconds() { + auto now = std::chrono::system_clock::now().time_since_epoch(); + auto seconds = std::chrono::duration_cast(now).count(); + auto micros = + std::chrono::duration_cast(now).count() % + 1000000; + + return static_cast(seconds) + + (static_cast(micros) / 1000000.0); +} + +} // namespace +#endif + +NetworkReporter& NetworkReporter::getInstance() { + static NetworkReporter instance; + return instance; +} + +void NetworkReporter::setFrontendChannel(FrontendChannel frontendChannel) { + frontendChannel_ = std::move(frontendChannel); +} + +bool NetworkReporter::enableDebugging() { + if (debuggingEnabled_.load(std::memory_order_acquire)) { + return false; + } + + debuggingEnabled_.store(true, std::memory_order_release); + return true; +} + +bool NetworkReporter::disableDebugging() { + if (!debuggingEnabled_.load(std::memory_order_acquire)) { + return false; + } + + debuggingEnabled_.store(false, std::memory_order_release); + return true; +} + +void NetworkReporter::reportRequestStart( + const std::string& requestId, + const RequestInfo& requestInfo, + int encodedDataLength, + const std::optional& redirectResponse) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + perfTimingsBuffer_.emplace( + requestId, + ResourceTimingData{ + .url = requestInfo.url, + .fetchStart = now, + .requestStart = now, + }); + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + if (!isDebuggingEnabledNoSync()) { + return; + } + + double timestamp = getCurrentUnixTimestampSeconds(); + auto request = cdp::network::Request::fromInputParams(requestInfo); + auto params = cdp::network::RequestWillBeSentParams{ + .requestId = requestId, + .loaderId = "", + .documentURL = "mobile", + .request = std::move(request), + // NOTE: Both timestamp and wallTime use the same unit, however wallTime + // is relative to an "arbitrary epoch". In our implementation, use the + // Unix epoch for both. + .timestamp = timestamp, + .wallTime = timestamp, + .initiator = folly::dynamic::object("type", "script"), + .redirectHasExtraInfo = redirectResponse.has_value(), + }; + + if (redirectResponse.has_value()) { + params.redirectResponse = cdp::network::Response::fromInputParams( + redirectResponse.value(), encodedDataLength); + } + + frontendChannel_( + cdp::jsonNotification("Network.requestWillBeSent", params.toDynamic())); +#endif +} + +void NetworkReporter::reportConnectionTiming(const std::string& requestId) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.connectStart = now; + } + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + if (!isDebuggingEnabledNoSync()) { + return; + } + + // TODO(T218236597) + throw std::runtime_error("Not implemented"); +#endif +} + +void NetworkReporter::reportRequestFailed( + const std::string& /*requestId*/) const { +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + if (!isDebuggingEnabledNoSync()) { + return; + } + + // TODO(T218236855) + throw std::runtime_error("Not implemented"); +#endif +} + +void NetworkReporter::reportResponseStart( + const std::string& requestId, + const ResponseInfo& responseInfo, + int encodedDataLength) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.responseStart = now; + it->second.responseStatus = responseInfo.statusCode; + } + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + if (!isDebuggingEnabledNoSync()) { + return; + } + + auto response = + cdp::network::Response::fromInputParams(responseInfo, encodedDataLength); + auto params = cdp::network::ResponseReceivedParams{ + .requestId = requestId, + .loaderId = "", + .timestamp = getCurrentUnixTimestampSeconds(), + .type = cdp::network::resourceTypeFromMimeType(response.mimeType), + .response = response, + .hasExtraInfo = false, + }; + + frontendChannel_( + cdp::jsonNotification("Network.responseReceived", params.toDynamic())); +#endif +} + +void NetworkReporter::reportDataReceived(const std::string& requestId) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.connectEnd = now; + it->second.responseStart = now; + } + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + if (!isDebuggingEnabledNoSync()) { + return; + } + + // TODO(T218236266) + throw std::runtime_error("Not implemented"); +#endif +} + +void NetworkReporter::reportResponseEnd( + const std::string& requestId, + int encodedDataLength) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Report PerformanceResourceTiming event + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + auto& eventData = it->second; + PerformanceEntryReporter::getInstance()->reportResourceTiming( + eventData.url, + eventData.fetchStart, + eventData.requestStart, + eventData.connectStart.value_or(now), + eventData.connectEnd.value_or(now), + eventData.responseStart.value_or(now), + now, + eventData.responseStatus); + perfTimingsBuffer_.erase(requestId); + } + } + } + +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: CDP event handling + if (!isDebuggingEnabledNoSync()) { + return; + } + + auto params = cdp::network::LoadingFinishedParams{ + .requestId = requestId, + .timestamp = getCurrentUnixTimestampSeconds(), + .encodedDataLength = encodedDataLength, + }; + + frontendChannel_( + cdp::jsonNotification("Network.loadingFinished", params.toDynamic())); +#endif +} + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h new file mode 100644 index 00000000000000..90701c412bfe47 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "NetworkTypes.h" + +#include + +#include +#include +#include +#include + +namespace facebook::react::jsinspector_modern { + +/** + * A callback that can be used to send debugger messages (method responses and + * events) to the frontend. The message must be a JSON-encoded string. + * The callback may be called from any thread. + */ +using FrontendChannel = std::function; + +/** + * Container for static network event metadata aligning with the + * `PerformanceResourceTiming` interface. + * + * This is a lightweight type stored in `perfTimingsBuffer_` and used for + * reporting complete events to the Web Performance subsystem. Not used for CDP + * reporting. + */ +struct ResourceTimingData { + std::string url; + HighResTimeStamp fetchStart; + HighResTimeStamp requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; + std::optional responseStatus; +}; + +/** + * [Experimental] An interface for reporting network events to the modern + * debugger server and Web Performance APIs. + * + * In a production (non dev or profiling) build, CDP reporting is disabled. + */ +class NetworkReporter { + public: + static NetworkReporter& getInstance(); + + /** + * Set the channel used to send CDP events to the frontend. This should be + * supplied before calling `enableDebugging`. + */ + void setFrontendChannel(FrontendChannel frontendChannel); + + /** + * Enable network tracking over CDP. Once enabled, network events will be + * sent to the debugger client. Returns `false` if already enabled. + * + * Corresponds to `Network.enable` in CDP. + */ + bool enableDebugging(); + + /** + * Disable network tracking over CDP, preventing network events from being + * sent to the debugger client. Returns `false` if not initially enabled. + * + * Corresponds to `Network.disable` in CDP. + */ + bool disableDebugging(); + + /** + * Report a network request that is about to be sent. + * + * - Corresponds to `Network.requestWillBeSent` in CDP. + * - Corresponds to `PerformanceResourceTiming.requestStart` (specifically, + * marking when the native request was initiated). + * + * https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-requeststart + */ + void reportRequestStart( + const std::string& requestId, + const RequestInfo& requestInfo, + int encodedDataLength, + const std::optional& redirectResponse); + + /** + * Report detailed timing info, such as DNS lookup, when a request has + * started. + * + * - Corresponds to `Network.requestWillBeSentExtraInfo` in CDP. + * - Corresponds to `PerformanceResourceTiming.domainLookupStart`, + * `PerformanceResourceTiming.connectStart`. + * + * https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-connectstart + */ + void reportConnectionTiming(const std::string& requestId); + + /** + * Report when a network request has failed. + * + * Corresponds to `Network.loadingFailed` in CDP. + */ + void reportRequestFailed(const std::string& requestId) const; + + /** + * Report when HTTP response headers have been received, corresponding to + * when the first byte of the response is available. + * + * - Corresponds to `Network.responseReceived` in CDP. + * - Corresponds to `PerformanceResourceTiming.responseStart`. + * + * https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responsestart + */ + void reportResponseStart( + const std::string& requestId, + const ResponseInfo& responseInfo, + int encodedDataLength); + + /** + * Report when additional chunks of the response body have been received. + * + * Corresponds to `Network.dataReceived` in CDP. + */ + void reportDataReceived(const std::string& requestId); + + /** + * Report when a network request is complete and we are no longer receiving + * response data. + * + * - Corresponds to `Network.loadingFinished` in CDP. + * - Corresponds to `PerformanceResourceTiming.responseEnd`. + * + * https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responseend + */ + void reportResponseEnd(const std::string& requestId, int encodedDataLength); + + private: + FrontendChannel frontendChannel_; + + NetworkReporter() = default; + NetworkReporter(const NetworkReporter&) = delete; + NetworkReporter& operator=(const NetworkReporter&) = delete; + ~NetworkReporter() = default; + + std::atomic debuggingEnabled_{false}; + + inline bool isDebuggingEnabledNoSync() const { + return debuggingEnabled_.load(std::memory_order_relaxed); + } + + std::unordered_map perfTimingsBuffer_{}; + std::mutex perfTimingsMutex_; +}; + +} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec new file mode 100644 index 00000000000000..b4c4a2b545f2b8 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec @@ -0,0 +1,55 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +header_search_paths = [] + +if ENV['USE_FRAMEWORKS'] + header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../..\"" +end + +header_dir = 'jsinspector-modern/network' +module_name = "jsinspector_modernnetwork" + +Pod::Spec.new do |s| + s.name = "React-jsinspectornetwork" + s.version = version + s.summary = "Network inspection for React Native DevTools" + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = min_supported_versions + s.source = source + s.source_files = "*.{cpp,h}" + s.header_dir = header_dir + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), + "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), + "DEFINES_MODULE" => "YES"} + + if ENV['USE_FRAMEWORKS'] + s.module_name = module_name + s.header_mappings_dir = "../.." + end + + add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp') + add_dependency(s, "React-featureflags") + s.dependency "React-performancetimeline" + s.dependency "React-timing" + + add_rn_third_party_dependencies(s) +end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt b/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt index 6be78a9675ca00..a8b7aed64c3f27 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt @@ -23,4 +23,6 @@ target_include_directories(jsinspector_tracing PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(jsinspector_tracing folly_runtime + oscompat + react_timing ) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/CdpTracing.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/CdpTracing.h deleted file mode 100644 index 58b91d630ba0c0..00000000000000 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/CdpTracing.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -#include - -namespace facebook::react::jsinspector_modern { - -/** - * https://developer.chrome.com/docs/devtools/performance/extension?utm_source=devtools#devtools_object - */ -struct DevToolsTrackEntryPayload { - std::string track; -}; - -} // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp new file mode 100644 index 00000000000000..535fe2af5f1444 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "EventLoopReporter.h" + +#if defined(REACT_NATIVE_DEBUGGER_ENABLED) +#include +#include "PerformanceTracer.h" +#endif + +namespace facebook::react::jsinspector_modern::tracing { + +#if defined(REACT_NATIVE_DEBUGGER_ENABLED) + +EventLoopReporter::EventLoopReporter(EventLoopPhase phase) + : startTimestamp_(HighResTimeStamp::now()), phase_(phase) {} + +EventLoopReporter::~EventLoopReporter() { + PerformanceTracer& performanceTracer = PerformanceTracer::getInstance(); + if (performanceTracer.isTracing()) { + auto end = HighResTimeStamp::now(); + switch (phase_) { + case EventLoopPhase::Task: + performanceTracer.reportEventLoopTask(startTimestamp_, end); + break; + + case EventLoopPhase::Microtasks: + performanceTracer.reportEventLoopMicrotasks(startTimestamp_, end); + break; + + default: + break; + } + } +} + +#else + +EventLoopReporter::EventLoopReporter(EventLoopPhase phase) {} + +EventLoopReporter::~EventLoopReporter() {} + +#endif + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h new file mode 100644 index 00000000000000..211fe139cba76d --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#if defined(REACT_NATIVE_DEBUGGER_ENABLED) +#include +#endif + +namespace facebook::react::jsinspector_modern::tracing { + +enum class EventLoopPhase { + Task, + Microtasks, +}; + +struct EventLoopReporter { + public: + explicit EventLoopReporter(EventLoopPhase phase); + + EventLoopReporter(const EventLoopReporter&) = delete; + EventLoopReporter(EventLoopReporter&&) = delete; + EventLoopReporter& operator=(const EventLoopReporter&) = delete; + EventLoopReporter& operator=(EventLoopReporter&&) = delete; + + ~EventLoopReporter(); + + private: +#if defined(REACT_NATIVE_DEBUGGER_ENABLED) + HighResTimeStamp startTimestamp_; + EventLoopPhase phase_; +#endif +}; + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index dad7b7cd5582c1..f58efda4071438 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -6,23 +6,17 @@ */ #include "PerformanceTracer.h" +#include "Timing.h" + +#include +#include #include #include #include -namespace facebook::react::jsinspector_modern { - -namespace { - -/** Process ID for all emitted events. */ -const uint64_t PID = 1000; - -/** Default/starting track ID for the "Timings" track. */ -const uint64_t USER_TIMINGS_DEFAULT_TRACK = 1000; - -} // namespace +namespace facebook::react::jsinspector_modern::tracing { PerformanceTracer& PerformanceTracer::getInstance() { static PerformanceTracer tracer; @@ -30,9 +24,30 @@ PerformanceTracer& PerformanceTracer::getInstance() { } bool PerformanceTracer::startTracing() { - std::lock_guard lock(mutex_); - if (tracing_) { - return false; + { + std::lock_guard lock(mutex_); + if (tracing_) { + return false; + } + + tracing_ = true; + } + + reportProcess(processId_, "React Native"); + + { + std::lock_guard lock(mutex_); + buffer_.push_back(TraceEvent{ + .name = "TracingStartedInPage", + .cat = "disabled-by-default-devtools.timeline", + .ph = 'I', + .ts = HighResTimeStamp::now(), + .pid = processId_, + .tid = oscompat::getCurrentThreadId(), + .args = folly::dynamic::object("data", folly::dynamic::object()), + }); + + return true; } tracing_ = true; return true; @@ -55,26 +70,12 @@ bool PerformanceTracer::stopTracingAndCollectEvents( // Register "Main" process buffer_.push_back(TraceEvent{ - .name = "process_name", - .cat = "__metadata", - .ph = 'M', - .ts = 0, - .pid = PID, - .tid = 0, - .args = folly::dynamic::object("name", "Main"), - }); - // Register "Timings" track - // NOTE: This is a hack to make the Trace Viewer show a "Timings" track - // adjacent to custom tracks in our current build of Chrome DevTools. - // In future, we should align events exactly. - buffer_.push_back(TraceEvent{ - .name = "thread_name", - .cat = "__metadata", - .ph = 'M', - .ts = 0, - .pid = PID, - .tid = USER_TIMINGS_DEFAULT_TRACK, - .args = folly::dynamic::object("name", "Timings"), + .name = "ReactNative-TracingStopped", + .cat = "disabled-by-default-devtools.timeline", + .ph = 'I', + .ts = HighResTimeStamp::now(), + .pid = processId_, + .tid = oscompat::getCurrentThreadId(), }); for (const auto& [trackName, trackId] : customTrackIdMap_) { @@ -113,28 +114,52 @@ bool PerformanceTracer::stopTracingAndCollectEvents( void PerformanceTracer::reportMark( const std::string_view& name, - uint64_t start) { + HighResTimeStamp start, + folly::dynamic&& detail) { + if (!tracingAtomic_) { + return; + } + std::lock_guard lock(mutex_); if (!tracing_) { return; } - buffer_.push_back(TraceEvent{ + folly::dynamic eventArgs = folly::dynamic::object(); + if (detail != nullptr) { + eventArgs = folly::dynamic::object( + "data", + folly::dynamic::object("detail", folly::toJson(std::move(detail)))); + } + + buffer_.emplace_back(TraceEvent{ .name = std::string(name), .cat = "blink.user_timing", .ph = 'I', .ts = start, - .pid = PID, // FIXME: This should be the real process ID. - .tid = USER_TIMINGS_DEFAULT_TRACK, // FIXME: This should be the real - // thread ID. + .pid = processId_, + .tid = oscompat::getCurrentThreadId(), + .args = eventArgs, }); } void PerformanceTracer::reportMeasure( const std::string_view& name, - uint64_t start, - uint64_t duration, - const std::optional& trackMetadata) { + HighResTimeStamp start, + HighResDuration duration, + folly::dynamic&& detail) { + if (!tracingAtomic_) { + return; + } + + folly::dynamic beginEventArgs = folly::dynamic::object(); + if (detail != nullptr) { + beginEventArgs = + folly::dynamic::object("detail", folly::toJson(std::move(detail))); + } + + auto currentThreadId = oscompat::getCurrentThreadId(); + std::lock_guard lock(mutex_); if (!tracing_) { return; @@ -161,27 +186,179 @@ void PerformanceTracer::reportMeasure( .cat = "blink.user_timing", .ph = 'X', .ts = start, - .pid = PID, // FIXME: This should be the real process ID. - .tid = threadId, // FIXME: This should be the real thread ID. - .dur = duration, + .pid = processId_, + .tid = currentThreadId, + .args = beginEventArgs, + }); + buffer_.push_back(TraceEvent{ + .id = performanceMeasureCount_, + .name = std::string(name), + .cat = "blink.user_timing", + .ph = 'e', + .ts = start + duration, + .pid = processId_, + .tid = currentThreadId, + }); +} + +void PerformanceTracer::reportProcess(uint64_t id, const std::string& name) { + if (!tracing_) { + return; + } + + std::lock_guard lock(mutex_); + if (!tracing_) { + return; + } + + buffer_.push_back(TraceEvent{ + .name = "process_name", + .cat = "__metadata", + .ph = 'M', + .ts = TRACING_TIME_ORIGIN, + .pid = id, + .tid = 0, + .args = folly::dynamic::object("name", name), + }); +} + +void PerformanceTracer::reportThread(uint64_t id, const std::string& name) { + if (!tracing_) { + return; + } + + std::lock_guard lock(mutex_); + if (!tracing_) { + return; + } + + buffer_.push_back(TraceEvent{ + .name = "thread_name", + .cat = "__metadata", + .ph = 'M', + .ts = TRACING_TIME_ORIGIN, + .pid = processId_, + .tid = id, + .args = folly::dynamic::object("name", name), + }); + + // This is synthetic Trace Event, which should not be represented on a + // timeline. CDT will filter out threads that only have JavaScript samples and + // no timeline events or user timings. We use this event to avoid that. + // This could happen for non-bridgeless apps, where Performance interface is + // not supported and no spec-compliant Event Loop implementation. + buffer_.push_back(TraceEvent{ + .name = "ReactNative-ThreadRegistered", + .cat = "disabled-by-default-devtools.timeline", + .ph = 'I', + .ts = TRACING_TIME_ORIGIN, + .pid = processId_, + .tid = id, + }); +} + +void PerformanceTracer::reportEventLoopTask( + HighResTimeStamp start, + HighResTimeStamp end) { + if (!tracing_) { + return; + } + + std::lock_guard lock(mutex_); + if (!tracing_) { + return; + } + + buffer_.push_back(TraceEvent{ + .name = "RunTask", + .cat = "disabled-by-default-devtools.timeline", + .ph = 'X', + .ts = start, + .pid = oscompat::getCurrentProcessId(), + .tid = oscompat::getCurrentThreadId(), + .dur = end - start, + }); +} + +void PerformanceTracer::reportEventLoopMicrotasks( + HighResTimeStamp start, + HighResTimeStamp end) { + if (!tracing_) { + return; + } + + std::lock_guard lock(mutex_); + if (!tracing_) { + return; + } + + buffer_.push_back(TraceEvent{ + .name = "RunMicrotasks", + .cat = "v8.execute", + .ph = 'X', + .ts = start, + .pid = oscompat::getCurrentProcessId(), + .tid = oscompat::getCurrentThreadId(), + .dur = end - start, + }); +} + +folly::dynamic PerformanceTracer::getSerializedRuntimeProfileTraceEvent( + uint64_t threadId, + uint16_t profileId, + HighResTimeStamp profileTimestamp) { + // CDT prioritizes event timestamp over startTime metadata field. + // https://fburl.com/lo764pf4 + return serializeTraceEvent(TraceEvent{ + .id = profileId, + .name = "Profile", + .cat = "disabled-by-default-v8.cpu_profiler", + .ph = 'P', + .ts = profileTimestamp, + .pid = processId_, + .tid = threadId, + .args = folly::dynamic::object( + "data", + folly ::dynamic::object( + "startTime", + highResTimeStampToTracingClockTimeStamp(profileTimestamp))), + }); +} + +folly::dynamic PerformanceTracer::getSerializedRuntimeProfileChunkTraceEvent( + uint16_t profileId, + uint64_t threadId, + HighResTimeStamp chunkTimestamp, + const tracing::TraceEventProfileChunk& traceEventProfileChunk) { + return serializeTraceEvent(TraceEvent{ + .id = profileId, + .name = "ProfileChunk", + .cat = "disabled-by-default-v8.cpu_profiler", + .ph = 'P', + .ts = chunkTimestamp, + .pid = processId_, + .tid = threadId, + .args = + folly::dynamic::object("data", traceEventProfileChunk.toDynamic()), }); } -folly::dynamic PerformanceTracer::serializeTraceEvent(TraceEvent event) const { +folly::dynamic PerformanceTracer::serializeTraceEvent( + const TraceEvent& event) const { folly::dynamic result = folly::dynamic::object; result["name"] = event.name; result["cat"] = event.cat; result["ph"] = std::string(1, event.ph); - result["ts"] = event.ts; + result["ts"] = highResTimeStampToTracingClockTimeStamp(event.ts); result["pid"] = event.pid; result["tid"] = event.tid; result["args"] = event.args; if (event.dur.has_value()) { - result["dur"] = event.dur.value(); + result["dur"] = highResDurationToTracingClockDuration(event.dur.value()); } return result; } -} // namespace facebook::react::jsinspector_modern +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index 2a8ccb31967e72..f1fe52aad6e0e9 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -7,16 +7,19 @@ #pragma once -#include "CdpTracing.h" +#include "ConsoleTimeStamp.h" +#include "TraceEvent.h" +#include "TraceEventProfile.h" -#include +#include +#include #include #include #include #include -namespace facebook::react::jsinspector_modern { +namespace facebook::react::jsinspector_modern::tracing { // TODO: Review how this API is integrated into jsinspector_modern (singleton // design is copied from earlier FuseboxTracer prototype). @@ -85,9 +88,26 @@ class PerformanceTracer { * * Returns `false` if tracing was not started. */ - bool stopTracingAndCollectEvents( + bool stopTracing(); + + /** + * Returns whether the tracer is currently tracing. This can be useful to + * avoid doing expensive work (like formatting strings) if tracing is not + * enabled. + */ + bool isTracing() const { + // This is not thread safe but it's only a performance optimization. The + // call to report marks and measures is already thread safe. + return tracing_; + } + + /** + * Flush out buffered CDP Trace Events using the given callback. + */ + void collectEvents( const std::function& - resultCallback); + resultCallback, + uint16_t chunkSize); /** * Record a `Performance.mark()` event - a labelled timestamp. If not @@ -95,7 +115,10 @@ class PerformanceTracer { * * See https://w3c.github.io/user-timing/#mark-method. */ - void reportMark(const std::string_view& name, uint64_t start); + void reportMark( + const std::string_view& name, + HighResTimeStamp start, + folly::dynamic&& detail = nullptr); /** * Record a `Performance.measure()` event - a labelled duration. If not @@ -105,9 +128,56 @@ class PerformanceTracer { */ void reportMeasure( const std::string_view& name, - uint64_t start, - uint64_t duration, - const std::optional& trackMetadata); + HighResTimeStamp start, + HighResDuration duration, + folly::dynamic&& detail = nullptr); + + /** + * Record a corresponding Trace Event for OS-level process. + */ + void reportProcess(uint64_t id, const std::string& name); + + /** + * Record a corresponding Trace Event for OS-level thread. + */ + void reportThread(uint64_t id, const std::string& name); + + /** + * Should only be called from the JavaScript thread, will buffer metadata + * Trace Event. + */ + void reportJavaScriptThread(); + + /** + * Record an Event Loop tick, which will be represented as an Event Loop task + * on a timeline view and grouped with JavaScript samples. + */ + void reportEventLoopTask(HighResTimeStamp start, HighResTimeStamp end); + + /** + * Record Microtasks phase of the Event Loop tick. Will be represented as a + * "Run Microtasks" block under a task. + */ + void reportEventLoopMicrotasks(HighResTimeStamp start, HighResTimeStamp end); + + /** + * Create and serialize Profile Trace Event. + * \return serialized Trace Event that represents a Profile for CDT. + */ + folly::dynamic getSerializedRuntimeProfileTraceEvent( + uint64_t threadId, + uint16_t profileId, + HighResTimeStamp profileTimestamp); + + /** + * Create and serialize ProfileChunk Trace Event. + * \return serialized Trace Event that represents a Profile Chunk for CDT. + */ + folly::dynamic getSerializedRuntimeProfileChunkTraceEvent( + uint16_t profileId, + uint64_t threadId, + HighResTimeStamp chunkTimestamp, + const TraceEventProfileChunk& traceEventProfileChunk); private: PerformanceTracer() = default; @@ -118,9 +188,11 @@ class PerformanceTracer { folly::dynamic serializeTraceEvent(TraceEvent event) const; bool tracing_{false}; - std::unordered_map customTrackIdMap_; + + uint64_t processId_; + uint32_t performanceMeasureCount_{0}; std::vector buffer_; std::mutex mutex_; }; -} // namespace facebook::react::jsinspector_modern +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec b/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec index 82757d82956143..7cca1e2ae4874f 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec @@ -52,5 +52,8 @@ Pod::Spec.new do |s| s.header_mappings_dir = "../.." end - s.dependency "RCT-Folly" + s.dependency "React-oscompat" + s.dependency "React-timing" + + add_rn_third_party_dependencies(s) end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp new file mode 100644 index 00000000000000..6e181b5e0b741f --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include "ProfileTreeNode.h" +#include "RuntimeSamplingProfileTraceEventSerializer.h" + +namespace facebook::react::jsinspector_modern::tracing { + +namespace { + +// To capture samples timestamps Hermes is using steady_clock and returns +// them in microseconds granularity since epoch. In the future we might want to +// update Hermes to return timestamps in chrono type. +HighResTimeStamp getHighResTimeStampForSample( + const RuntimeSamplingProfile::Sample& sample) { + auto microsecondsSinceSteadyClockEpoch = sample.getTimestamp(); + auto chronoTimePoint = std::chrono::steady_clock::time_point( + std::chrono::microseconds(microsecondsSinceSteadyClockEpoch)); + return HighResTimeStamp::fromChronoSteadyClockTimePoint(chronoTimePoint); +} + +// Right now we only emit single Profile. We might revisit this decision in the +// future, once we support multiple VMs being sampled at the same time. +constexpr uint16_t PROFILE_ID = 1; + +/// Fallback script ID for artificial call frames, such as (root), (idle) or +/// (program). Required for emulating the payload in a format that is expected +/// by Chrome DevTools. +constexpr uint32_t FALLBACK_SCRIPT_ID = 0; + +constexpr std::string_view GARBAGE_COLLECTOR_FRAME_NAME = "(garbage collector)"; +constexpr std::string_view ROOT_FRAME_NAME = "(root)"; +constexpr std::string_view IDLE_FRAME_NAME = "(idle)"; +constexpr std::string_view PROGRAM_FRAME_NAME = "(program)"; + +TraceEventProfileChunk::CPUProfile::Node convertToTraceEventProfileNode( + const ProfileTreeNode& node) { + const RuntimeSamplingProfile::SampleCallStackFrame& callFrame = + node.getCallFrame(); + auto traceEventCallFrame = + TraceEventProfileChunk::CPUProfile::Node::CallFrame{ + node.getCodeType() == ProfileTreeNode::CodeType::JavaScript ? "JS" + : "other", + callFrame.getScriptId(), + std::string(callFrame.getFunctionName()), + callFrame.hasUrl() + ? std::optional(std::string(callFrame.getUrl())) + : std::nullopt, + callFrame.hasLineNumber() + ? std::optional(callFrame.getLineNumber()) + : std::nullopt, + callFrame.hasColumnNumber() + ? std::optional(callFrame.getColumnNumber()) + : std::nullopt}; + + return TraceEventProfileChunk::CPUProfile::Node{ + node.getId(), + traceEventCallFrame, + node.hasParent() ? std::optional(node.getParentId()) + : std::nullopt}; +} + +RuntimeSamplingProfile::SampleCallStackFrame createArtificialCallFrame( + std::string_view callFrameName) { + return RuntimeSamplingProfile::SampleCallStackFrame{ + RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction, + FALLBACK_SCRIPT_ID, + callFrameName}; +}; + +RuntimeSamplingProfile::SampleCallStackFrame createGarbageCollectorCallFrame() { + return RuntimeSamplingProfile::SampleCallStackFrame{ + RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector, + FALLBACK_SCRIPT_ID, + GARBAGE_COLLECTOR_FRAME_NAME}; +}; + +class ProfileTreeRootNode : public ProfileTreeNode { + public: + explicit ProfileTreeRootNode(uint32_t id) + : ProfileTreeNode( + id, + CodeType::Other, + createArtificialCallFrame(ROOT_FRAME_NAME)) {} +}; + +} // namespace + +void RuntimeSamplingProfileTraceEventSerializer::sendProfileTraceEvent( + uint64_t threadId, + uint16_t profileId, + HighResTimeStamp profileStartTimestamp) const { + folly::dynamic serializedTraceEvent = + performanceTracer_.getSerializedRuntimeProfileTraceEvent( + threadId, profileId, profileStartTimestamp); + + notificationCallback_(folly::dynamic::array(serializedTraceEvent)); +} + +void RuntimeSamplingProfileTraceEventSerializer::chunkEmptySample( + ProfileChunk& chunk, + uint32_t idleNodeId, + HighResDuration samplesTimeDelta) { + chunk.samples.push_back(idleNodeId); + chunk.timeDeltas.push_back(samplesTimeDelta); +} + +void RuntimeSamplingProfileTraceEventSerializer::bufferProfileChunkTraceEvent( + ProfileChunk& chunk, + uint16_t profileId) { + if (chunk.isEmpty()) { + return; + } + + std::vector traceEventNodes; + traceEventNodes.reserve(chunk.nodes.size()); + for (const auto& node : chunk.nodes) { + traceEventNodes.push_back(convertToTraceEventProfileNode(node)); + } + + traceEventBuffer_.push_back( + performanceTracer_.getSerializedRuntimeProfileChunkTraceEvent( + profileId, + chunk.threadId, + chunk.timestamp, + TraceEventProfileChunk{ + .cpuProfile = + TraceEventProfileChunk::CPUProfile{ + traceEventNodes, chunk.samples}, + .timeDeltas = + TraceEventProfileChunk::TimeDeltas{chunk.timeDeltas}, + })); +} + +void RuntimeSamplingProfileTraceEventSerializer::processCallStack( + const std::vector& callStack, + ProfileChunk& chunk, + ProfileTreeNode& rootNode, + uint32_t idleNodeId, + HighResDuration samplesTimeDelta, + NodeIdGenerator& nodeIdGenerator) { + if (callStack.empty()) { + chunkEmptySample(chunk, idleNodeId, samplesTimeDelta); + return; + } + + ProfileTreeNode* previousNode = &rootNode; + for (auto it = callStack.rbegin(); it != callStack.rend(); ++it) { + const RuntimeSamplingProfile::SampleCallStackFrame& callFrame = *it; + bool isGarbageCollectorFrame = callFrame.getKind() == + RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector; + + ProfileTreeNode::CodeType childCodeType = isGarbageCollectorFrame + ? ProfileTreeNode::CodeType::Other + : ProfileTreeNode::CodeType::JavaScript; + // We don't need real garbage collector call frame, we change it to + // what Chrome DevTools expects. + RuntimeSamplingProfile::SampleCallStackFrame childCallFrame = + isGarbageCollectorFrame ? createGarbageCollectorCallFrame() : callFrame; + + ProfileTreeNode* maybeExistingChild = + previousNode->getIfAlreadyExists(childCodeType, childCallFrame); + if (maybeExistingChild != nullptr) { + previousNode = maybeExistingChild; + } else { + previousNode = previousNode->addChild( + nodeIdGenerator.getNext(), childCodeType, childCallFrame); + chunk.nodes.push_back(*previousNode); + } + } + + chunk.samples.push_back(previousNode->getId()); + chunk.timeDeltas.push_back(samplesTimeDelta); +} + +void RuntimeSamplingProfileTraceEventSerializer:: + sendBufferedTraceEventsAndClear() { + notificationCallback_(traceEventBuffer_); + traceEventBuffer_ = folly::dynamic::array(); +} + +void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( + const RuntimeSamplingProfile& profile, + HighResTimeStamp tracingStartTime) { + const std::vector& samples = + profile.getSamples(); + if (samples.empty()) { + return; + } + + uint64_t firstChunkThreadId = samples.front().getThreadId(); + HighResTimeStamp previousSampleTimestamp = tracingStartTime; + HighResTimeStamp currentChunkTimestamp = tracingStartTime; + + sendProfileTraceEvent(firstChunkThreadId, PROFILE_ID, tracingStartTime); + + // There could be any number of new nodes in this chunk. Empty if all nodes + // are already emitted in previous chunks. + ProfileChunk chunk{ + profileChunkSize_, firstChunkThreadId, currentChunkTimestamp}; + + NodeIdGenerator nodeIdGenerator{}; + + ProfileTreeRootNode rootNode(nodeIdGenerator.getNext()); + chunk.nodes.push_back(rootNode); + + ProfileTreeNode* programNode = rootNode.addChild( + nodeIdGenerator.getNext(), + ProfileTreeNode::CodeType::Other, + createArtificialCallFrame(PROGRAM_FRAME_NAME)); + chunk.nodes.push_back(*programNode); + + ProfileTreeNode* idleNode = rootNode.addChild( + nodeIdGenerator.getNext(), + ProfileTreeNode::CodeType::Other, + createArtificialCallFrame(IDLE_FRAME_NAME)); + chunk.nodes.push_back(*idleNode); + uint32_t idleNodeId = idleNode->getId(); + + for (const auto& sample : samples) { + uint64_t currentSampleThreadId = sample.getThreadId(); + auto currentSampleTimestamp = getHighResTimeStampForSample(sample); + + // We should not attempt to merge samples from different threads. + // From past observations, this only happens for GC nodes. + // We should group samples by thread id once we support executing JavaScript + // on different threads. + if (currentSampleThreadId != chunk.threadId || chunk.isFull()) { + bufferProfileChunkTraceEvent(chunk, PROFILE_ID); + chunk = ProfileChunk{ + profileChunkSize_, currentSampleThreadId, currentChunkTimestamp}; + } + + if (traceEventBuffer_.size() == traceEventChunkSize_) { + sendBufferedTraceEventsAndClear(); + } + + processCallStack( + sample.getCallStack(), + chunk, + rootNode, + idleNodeId, + currentSampleTimestamp - previousSampleTimestamp, + nodeIdGenerator); + + previousSampleTimestamp = currentSampleTimestamp; + } + + if (!chunk.isEmpty()) { + bufferProfileChunkTraceEvent(chunk, PROFILE_ID); + } + + if (!traceEventBuffer_.empty()) { + sendBufferedTraceEventsAndClear(); + } +} + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h new file mode 100644 index 00000000000000..620d42df119024 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h @@ -0,0 +1,165 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "PerformanceTracer.h" +#include "ProfileTreeNode.h" +#include "RuntimeSamplingProfile.h" + +#include + +namespace facebook::react::jsinspector_modern::tracing { + +namespace { + +struct NodeIdGenerator { + public: + uint32_t getNext() { + return ++counter_; + } + + private: + uint32_t counter_ = 0; +}; + +} // namespace + +/** + * Serializes RuntimeSamplingProfile into collection of specific Trace Events, + * which represent Profile information on a timeline. + */ +class RuntimeSamplingProfileTraceEventSerializer { + struct ProfileChunk { + ProfileChunk( + uint16_t chunkSize, + uint64_t chunkThreadId, + HighResTimeStamp chunkTimestamp) + : size(chunkSize), threadId(chunkThreadId), timestamp(chunkTimestamp) { + samples.reserve(size); + timeDeltas.reserve(size); + } + + bool isFull() const { + return samples.size() == size; + } + + bool isEmpty() const { + return samples.empty(); + } + + std::vector nodes; + std::vector samples; + std::vector timeDeltas; + uint16_t size; + uint64_t threadId; + HighResTimeStamp timestamp; + }; + + public: + /** + * \param performanceTracer A reference to PerformanceTracer instance. + * \param notificationCallback A reference to a callback, which is called + * when a chunk of trace events is ready to be sent. + * \param traceEventChunkSize The maximum number of ProfileChunk trace + * events that can be sent in a single CDP Tracing.dataCollected message. + * \param profileChunkSize The maximum number of ProfileChunk trace events + * that can be sent in a single ProfileChunk trace event. + */ + RuntimeSamplingProfileTraceEventSerializer( + PerformanceTracer& performanceTracer, + std::function + notificationCallback, + uint16_t traceEventChunkSize, + uint16_t profileChunkSize = 10) + : performanceTracer_(performanceTracer), + notificationCallback_(std::move(notificationCallback)), + traceEventChunkSize_(traceEventChunkSize), + profileChunkSize_(profileChunkSize) { + traceEventBuffer_ = folly::dynamic::array(); + traceEventBuffer_.reserve(traceEventChunkSize); + } + + /** + * \param profile What we will be serializing. + * \param tracingStartTime A timestamp of when tracing of an Instance started, + * will be used as a starting reference point of JavaScript samples recording. + */ + void serializeAndNotify( + const RuntimeSamplingProfile& profile, + HighResTimeStamp tracingStartTime); + + private: + /** + * Sends a single "Profile" Trace Event via notificationCallback_. + * \param threadId The id of the thread, where the Profile was collected. + * \param profileId The id of the Profile. + * \param profileStartUnixTimestamp The Unix timestamp of the start of the + * profile. + */ + void sendProfileTraceEvent( + uint64_t threadId, + uint16_t profileId, + HighResTimeStamp profileStartTimestamp) const; + + /** + * Encapsulates logic for processing the empty sample, when the VM was idling. + * \param chunk The profile chunk, which will record this sample. + * \param idleNodeId The id of the (idle) node. + * \param samplesTimeDelta Delta between the current sample and the + * previous one. + */ + void chunkEmptySample( + ProfileChunk& chunk, + uint32_t idleNodeId, + HighResDuration samplesTimeDelta); + + /** + * Records ProfileChunk as a "ProfileChunk" Trace Event in traceEventBuffer_. + * \param chunk The chunk that will be buffered. + * \param profileId The id of the Profile. + */ + void bufferProfileChunkTraceEvent(ProfileChunk& chunk, uint16_t profileId); + + /** + * Encapsulates logic for processing the call stack of the sample. + * \param callStack The call stack that will be processed. + * \param chunk The profile chunk, which will buffer the sample with the + * provided call stack. + * \param rootNode The (root) node. Will be the parent node of the + * corresponding profile tree branch. + * \param idleNodeId Id of the (idle) node. Will be the only node that is used + * for the corresponding profile tree branch, in case of an empty call stack. + * \param samplesTimeDelta Delta between the current sample and the previous + * one. + * \param nodeIdGenerator NodeIdGenerator instance that will be used for + * generating unique node ids. + */ + void processCallStack( + const std::vector& + callStack, + ProfileChunk& chunk, + ProfileTreeNode& rootNode, + uint32_t idleNodeId, + HighResDuration samplesTimeDelta, + NodeIdGenerator& nodeIdGenerator); + + /** + * Sends buffered Trace Events via notificationCallback_ and then clears it. + */ + void sendBufferedTraceEventsAndClear(); + + PerformanceTracer& performanceTracer_; + const std::function + notificationCallback_; + uint16_t traceEventChunkSize_; + uint16_t profileChunkSize_; + + folly::dynamic traceEventBuffer_; +}; + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/Timing.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/Timing.h new file mode 100644 index 00000000000000..bda8f65df22d14 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/Timing.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include + +namespace facebook::react::jsinspector_modern::tracing { + +// The Tracing Clock time origin is the steady_clock epoch. This is mostly done +// to replicate Chromium's behavior, but also saves us from aligning custom +// DOMHighResTimeStamps that can be specified in performance.mark / +// performance.measure calls: these should not extend the timeline window, this +// is the current approach in Chromium. +constexpr HighResTimeStamp TRACING_TIME_ORIGIN = + HighResTimeStamp::fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point()); + +// Tracing timestamps are represented a time value in microseconds since +// arbitrary time origin (epoch) with no fractional part. +inline uint64_t highResTimeStampToTracingClockTimeStamp( + HighResTimeStamp timestamp) { + assert( + timestamp >= TRACING_TIME_ORIGIN && + "Provided timestamp is before time origin"); + auto duration = timestamp - TRACING_TIME_ORIGIN; + return static_cast( + static_cast(duration.toNanoseconds()) / 1e3); +} + +inline int64_t highResDurationToTracingClockDuration(HighResDuration duration) { + return static_cast( + static_cast(duration.toNanoseconds()) / 1e3); +} + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h new file mode 100644 index 00000000000000..fd961d90d27a0f --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include + +namespace facebook::react::jsinspector_modern::tracing { + +/** + * A trace event to send to the debugger frontend, as defined by the Trace Event + * Format. + * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?pli=1&tab=t.0#heading=h.yr4qxyxotyw + */ +struct TraceEvent { + /** + * Optional. Serialized as a string, usually is hexadecimal number. + * https://github.com/ChromeDevTools/devtools-frontend/blob/99a9104ae974f8caa63927e356800f6762cdbf25/front_end/models/trace/helpers/Trace.ts#L198-L201 + */ + std::optional id; + + /** The name of the event, as displayed in the Trace Viewer. */ + std::string name; + + /** + * A comma separated list of categories for the event, configuring how + * events are shown in the Trace Viewer UI. + */ + std::string cat; + + /** + * The event type. This is a single character which changes depending on the + * type of event being output. See + * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?pli=1&tab=t.0#heading=h.puwqg050lyuy + */ + char ph; + + /** The tracing clock timestamp of the event, in microseconds (µs). */ + HighResTimeStamp ts; + + /** The process ID for the process that output this event. */ + uint64_t pid; + + /** The thread ID for the process that output this event. */ + uint64_t tid; + + /** Any arguments provided for the event. */ + folly::dynamic args = folly::dynamic::object(); + + /** + * The duration of the event, in microseconds (µs). Only applicable to + * complete events ("ph": "X"). + */ + std::optional dur; +}; + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h new file mode 100644 index 00000000000000..cbafe64d527bd1 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +#include + +namespace facebook::react::jsinspector_modern::tracing { + +/// Arbitrary data structure, which represents payload of the "ProfileChunk" +/// Trace Event. +struct TraceEventProfileChunk { + /// Deltas between timestamps of chronolocigally sorted samples. + /// Will be sent as part of the "ProfileChunk" trace event. + struct TimeDeltas { + folly::dynamic toDynamic() const { + auto value = folly::dynamic::array(); + value.reserve(deltas.size()); + for (const auto& delta : deltas) { + value.push_back(highResDurationToTracingClockDuration(delta)); + } + return value; + } + + std::vector deltas; + }; + + /// Contains Profile information that will be emitted in this chunk: nodes and + /// sample root node ids. + struct CPUProfile { + /// Unique node in the profile tree, has unique id, call frame and + /// optionally + /// id of its parent node. Only root node has no parent. + struct Node { + /// Unique call frame in the call stack. + struct CallFrame { + folly::dynamic toDynamic() const { + folly::dynamic dynamicCallFrame = folly::dynamic::object(); + dynamicCallFrame["codeType"] = codeType; + dynamicCallFrame["scriptId"] = scriptId; + dynamicCallFrame["functionName"] = functionName; + if (url.has_value()) { + dynamicCallFrame["url"] = url.value(); + } + if (lineNumber.has_value()) { + dynamicCallFrame["lineNumber"] = lineNumber.value(); + } + if (columnNumber.has_value()) { + dynamicCallFrame["columnNumber"] = columnNumber.value(); + } + + return dynamicCallFrame; + } + + std::string codeType; + uint32_t scriptId; + std::string functionName; + std::optional url; + std::optional lineNumber; + std::optional columnNumber; + }; + + folly::dynamic toDynamic() const { + folly::dynamic dynamicNode = folly::dynamic::object(); + + dynamicNode["callFrame"] = callFrame.toDynamic(); + dynamicNode["id"] = id; + if (parentId.has_value()) { + dynamicNode["parent"] = parentId.value(); + } + + return dynamicNode; + } + + uint32_t id; + CallFrame callFrame; + std::optional parentId; + }; + + folly::dynamic toDynamic() const { + folly::dynamic dynamicNodes = folly::dynamic::array(); + dynamicNodes.reserve(nodes.size()); + for (const auto& node : nodes) { + dynamicNodes.push_back(node.toDynamic()); + } + folly::dynamic dynamicSamples = + folly::dynamic::array(samples.begin(), samples.end()); + + return folly::dynamic::object("nodes", dynamicNodes)( + "samples", dynamicSamples); + } + + std::vector nodes; + std::vector samples; + }; + + folly::dynamic toDynamic() const { + return folly::dynamic::object("cpuProfile", cpuProfile.toDynamic())( + "timeDeltas", timeDeltas.toDynamic()); + } + + CPUProfile cpuProfile; + TimeDeltas timeDeltas; +}; + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp new file mode 100644 index 00000000000000..1303f560fba192 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +#include +#include +#include + +namespace facebook::react::jsinspector_modern::tracing { + +class RuntimeSamplingProfileTraceEventSerializerTest : public ::testing::Test { + protected: + std::vector notificationEvents_; + + std::function + createNotificationCallback() { + return [this](const folly::dynamic& traceEventsChunk) { + notificationEvents_.push_back(traceEventsChunk); + }; + } + + RuntimeSamplingProfile::SampleCallStackFrame createJSCallFrame( + std::string_view functionName, + uint32_t scriptId = 1, + std::optional url = std::nullopt, + std::optional lineNumber = std::nullopt, + std::optional columnNumber = std::nullopt) { + return RuntimeSamplingProfile::SampleCallStackFrame( + RuntimeSamplingProfile::SampleCallStackFrame::Kind::JSFunction, + scriptId, + functionName, + url, + lineNumber, + columnNumber); + } + + RuntimeSamplingProfile::SampleCallStackFrame createGCCallFrame() { + return RuntimeSamplingProfile::SampleCallStackFrame( + RuntimeSamplingProfile::SampleCallStackFrame::Kind::GarbageCollector, + 0, + "(garbage collector)"); + } + + RuntimeSamplingProfile::Sample createSample( + uint64_t timestamp, + uint64_t threadId, + std::vector callStack) { + return {timestamp, threadId, std::move(callStack)}; + } + + RuntimeSamplingProfile createEmptyProfile() { + return {"TestRuntime", {}, {}}; + } + + RuntimeSamplingProfile createProfileWithSamples( + std::vector samples) { + return {"TestRuntime", std::move(samples), {}}; + } +}; + +TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptyProfile) { + // Setup + auto notificationCallback = createNotificationCallback(); + RuntimeSamplingProfileTraceEventSerializer serializer( + PerformanceTracer::getInstance(), notificationCallback, 10); + + auto profile = createEmptyProfile(); + auto tracingStartTime = HighResTimeStamp::now(); + + // Execute + serializer.serializeAndNotify(profile, tracingStartTime); + + // Nothing should be reported if the profile is empty. + EXPECT_TRUE(notificationEvents_.empty()); +} + +TEST_F( + RuntimeSamplingProfileTraceEventSerializerTest, + SameCallFramesAreMerged) { + // Setup + auto notificationCallback = createNotificationCallback(); + RuntimeSamplingProfileTraceEventSerializer serializer( + PerformanceTracer::getInstance(), notificationCallback, 10); + + // [ foo ] + // [ bar ] + // [baz][(gc)] + std::vector callStack1 = { + createJSCallFrame("bar", 1, "test.js", 20, 10), + createJSCallFrame("foo", 1, "test.js", 10, 5), + }; + + std::vector callStack2 = { + createJSCallFrame("baz", 1, "other.js", 5, 1), + createJSCallFrame("bar", 1, "test.js", 20, 10), + createJSCallFrame("foo", 1, "test.js", 10, 5), + }; + + std::vector callStack3 = { + createGCCallFrame(), + createJSCallFrame("bar", 1, "test.js", 20, 10), + createJSCallFrame("foo", 1, "test.js", 10, 5), + }; + + uint64_t threadId = 1; + uint64_t timestamp1 = 1000000; + uint64_t timestamp2 = 2000000; + uint64_t timestamp3 = 3000000; + + auto samples = std::vector{}; + samples.emplace_back(createSample(timestamp1, threadId, callStack1)); + samples.emplace_back(createSample(timestamp2, threadId, callStack2)); + samples.emplace_back(createSample(timestamp3, threadId, callStack3)); + + auto profile = createProfileWithSamples(std::move(samples)); + auto tracingStartTime = HighResTimeStamp::now(); + + // Execute + serializer.serializeAndNotify(profile, tracingStartTime); + + // Verify + ASSERT_EQ(notificationEvents_.size(), 2); + // (root), (program), (idle), foo, bar, baz, (garbage collector) + ASSERT_EQ( + notificationEvents_[1][0]["args"]["data"]["cpuProfile"]["nodes"].size(), + 7); +} + +TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptySample) { + // Setup + auto notificationCallback = createNotificationCallback(); + RuntimeSamplingProfileTraceEventSerializer serializer( + PerformanceTracer::getInstance(), notificationCallback, 10); + + // Create an empty sample (no call stack) + std::vector emptyCallStack; + + uint64_t threadId = 1; + uint64_t timestamp = 1000000; + + auto samples = std::vector{}; + samples.emplace_back(createSample(timestamp, threadId, emptyCallStack)); + auto profile = createProfileWithSamples(std::move(samples)); + + auto tracingStartTime = HighResTimeStamp::now(); + + // Mock the performance tracer methods + folly::dynamic profileEvent = folly::dynamic::object; + folly::dynamic chunkEvent = folly::dynamic::object; + + // Execute + serializer.serializeAndNotify(profile, tracingStartTime); + + // Verify + // [["Profile"], ["ProfileChunk"]] + ASSERT_EQ(notificationEvents_.size(), 2); + // (root), (program), (idle) + ASSERT_EQ( + notificationEvents_[1][0]["args"]["data"]["cpuProfile"]["nodes"].size(), + 3); +} + +TEST_F( + RuntimeSamplingProfileTraceEventSerializerTest, + SamplesFromDifferentThreads) { + // Setup + auto notificationCallback = createNotificationCallback(); + RuntimeSamplingProfileTraceEventSerializer serializer( + PerformanceTracer::getInstance(), notificationCallback, 10); + + // Create samples with different thread IDs + std::vector callStack = { + createJSCallFrame("foo", 1, "test.js", 10, 5)}; + + uint64_t timestamp = 1000000; + uint64_t threadId1 = 1; + uint64_t threadId2 = 2; + + auto samples = std::vector{}; + samples.emplace_back(createSample(timestamp, threadId1, callStack)); + samples.emplace_back(createSample(timestamp + 1000, threadId2, callStack)); + samples.emplace_back(createSample(timestamp + 2000, threadId1, callStack)); + + auto profile = createProfileWithSamples(std::move(samples)); + + auto tracingStartTime = HighResTimeStamp::now(); + + // Execute + serializer.serializeAndNotify(profile, tracingStartTime); + + // [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk]] + // Samples from different thread should never be grouped together in the same + // chunk. + ASSERT_EQ(notificationEvents_.size(), 2); + ASSERT_EQ(notificationEvents_[1].size(), 3); +} + +TEST_F( + RuntimeSamplingProfileTraceEventSerializerTest, + TraceEventChunkSizeLimit) { + // Setup + auto notificationCallback = createNotificationCallback(); + uint16_t traceEventChunkSize = 2; + uint16_t profileChunkSize = 2; + RuntimeSamplingProfileTraceEventSerializer serializer( + PerformanceTracer::getInstance(), + notificationCallback, + traceEventChunkSize, + profileChunkSize); + + // Create multiple samples + std::vector callStack = { + createJSCallFrame("foo", 1, "test.js", 10, 5)}; + + uint64_t timestamp = 1000000; + uint64_t threadId = 1; + + std::vector samples; + samples.reserve(5); + for (int i = 0; i < 5; i++) { + samples.push_back(createSample(timestamp + i * 1000, threadId, callStack)); + } + + auto profile = createProfileWithSamples(std::move(samples)); + auto tracingStartTime = HighResTimeStamp::now(); + + // Execute + serializer.serializeAndNotify(profile, tracingStartTime); + + // [["Profile"], ["ProfileChunk", "ProfileChunk"], ["ProfileChunk"]] + ASSERT_EQ(notificationEvents_.size(), 3); + + // Check that each chunk has at most traceEventChunkSize events + for (size_t i = 1; i < notificationEvents_.size(); i++) { + EXPECT_LE(notificationEvents_[i].size(), traceEventChunkSize); + } +} + +TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) { + // Setup + auto notificationCallback = createNotificationCallback(); + // Set a small profile chunk size to test profile chunking + uint16_t traceEventChunkSize = 10; + uint16_t profileChunkSize = 2; + double samplesCount = 5; + RuntimeSamplingProfileTraceEventSerializer serializer( + PerformanceTracer::getInstance(), + notificationCallback, + traceEventChunkSize, + profileChunkSize); + + // Create multiple samples + std::vector callStack = { + createJSCallFrame("foo", 1, "test.js", 10, 5)}; + + uint64_t timestamp = 1000000; + uint64_t threadId = 1; + + std::vector samples; + samples.reserve(samplesCount); + for (int i = 0; i < samplesCount; i++) { + samples.push_back(createSample(timestamp + i * 1000, threadId, callStack)); + } + + auto profile = createProfileWithSamples(std::move(samples)); + auto tracingStartTime = HighResTimeStamp::now(); + + // Execute + serializer.serializeAndNotify(profile, tracingStartTime); + + // [["Profile"], ["ProfileChunk", "ProfileChunk", "ProfileChunk"]] + ASSERT_EQ(notificationEvents_.size(), 2); + ASSERT_EQ( + notificationEvents_[1].size(), + std::ceil(samplesCount / profileChunkSize)); + + for (auto& profileChunk : notificationEvents_[1]) { + EXPECT_LE( + profileChunk["args"]["data"]["cpuProfile"]["samples"].size(), + profileChunkSize); + } +} + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsitooling/CMakeLists.txt b/packages/react-native/ReactCommon/jsitooling/CMakeLists.txt new file mode 100644 index 00000000000000..565f832b39df6e --- /dev/null +++ b/packages/react-native/ReactCommon/jsitooling/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) +file(GLOB jsitooling_SRC CONFIGURE_DEPENDS react/runtime/*.cpp) +add_library(jsitooling OBJECT ${jsitooling_SRC}) + +target_include_directories(jsitooling + PUBLIC + ${REACT_COMMON_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(jsitooling + react_cxxreact + folly_runtime + glog + jsi) + +target_compile_reactnative_options(jsitooling PRIVATE) +target_compile_options(jsitooling PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/bridging/CMakeLists.txt b/packages/react-native/ReactCommon/react/bridging/CMakeLists.txt index 09dad427d20dd6..64bde6826fccfd 100644 --- a/packages/react-native/ReactCommon/react/bridging/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/bridging/CMakeLists.txt @@ -20,4 +20,6 @@ add_library(react_bridging OBJECT ${react_bridging_SRC}) target_include_directories(react_bridging PUBLIC ${REACT_COMMON_DIR}) -target_link_libraries(react_bridging jsi callinvoker) +target_link_libraries(react_bridging jsi callinvoker react_timing) +target_compile_reactnative_options(react_bridging PRIVATE) +target_compile_options(react_bridging PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp index 01263a3d6b9beb..b859295862ead2 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -766,4 +766,22 @@ TEST_F(BridgingTest, dynamicTest) { EXPECT_TRUE(undefinedFromJsResult.isNull()); } +TEST_F(BridgingTest, highResTimeStampTest) { + HighResTimeStamp timestamp = HighResTimeStamp::now(); + EXPECT_EQ( + timestamp, + bridging::fromJs( + rt, bridging::toJs(rt, timestamp), invoker)); + + auto duration = HighResDuration::fromNanoseconds(1); + EXPECT_EQ( + duration, + bridging::fromJs( + rt, bridging::toJs(rt, duration), invoker)); + + EXPECT_EQ(1.0, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6))); + EXPECT_EQ( + 1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1))); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/debug/CMakeLists.txt b/packages/react-native/ReactCommon/react/debug/CMakeLists.txt index bfe6ab5e49b4df..25779886bdca87 100644 --- a/packages/react-native/ReactCommon/react/debug/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/debug/CMakeLists.txt @@ -22,6 +22,8 @@ target_include_directories(react_debug PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(react_debug log folly_runtime) +target_compile_reactnative_options(react_debug PRIVATE) +target_compile_options(react_debug PRIVATE -Wpedantic) if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug) target_compile_options(react_debug PUBLIC -DNDEBUG) endif() diff --git a/packages/react-native/ReactCommon/react/featureflags/CMakeLists.txt b/packages/react-native/ReactCommon/react/featureflags/CMakeLists.txt index 4613a85e11ce25..61a8f16f173a55 100644 --- a/packages/react-native/ReactCommon/react/featureflags/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/featureflags/CMakeLists.txt @@ -19,5 +19,6 @@ add_library(react_featureflags OBJECT ${react_featureflags_SRC}) target_include_directories(react_featureflags PUBLIC ${REACT_COMMON_DIR}) -target_link_libraries(react_featureflags - folly_runtime) +target_link_libraries(react_featureflags folly_runtime) +target_compile_reactnative_options(react_featureflags PRIVATE) +target_compile_options(react_featureflags PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/core/CMakeLists.txt index 93289d66cdfd47..64a7491af4ff4b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/core/CMakeLists.txt @@ -39,3 +39,5 @@ target_link_libraries(react_nativemodule_core react_featureflags reactperflogger reactnativejni) +target_compile_reactnative_options(react_nativemodule_core PRIVATE) +target_compile_options(react_nativemodule_core PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt index 7984050cbdec1d..d2e0464c29ea2e 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt @@ -26,3 +26,5 @@ target_link_libraries(react_nativemodule_defaults react_nativemodule_microtasks react_nativemodule_idlecallbacks ) +target_compile_reactnative_options(react_nativemodule_defaults PRIVATE) +target_compile_options(react_nativemodule_defaults PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/devtoolsruntimesettings/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/devtoolsruntimesettings/CMakeLists.txt index 318e0f5d6b1075..4c6bb388496b54 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/devtoolsruntimesettings/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/devtoolsruntimesettings/CMakeLists.txt @@ -22,3 +22,5 @@ target_include_directories(react_nativemodule_devtoolsruntimesettings PUBLIC ${R target_link_libraries(react_nativemodule_devtoolsruntimesettings react_devtoolsruntimesettingscxx ) +target_compile_reactnative_options(react_nativemodule_devtoolsruntimesettings PRIVATE) +target_compile_options(react_nativemodule_devtoolsruntimesettings PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/dom/CMakeLists.txt index 6d9e0d1d1e8080..33e123b34effac 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/CMakeLists.txt @@ -30,3 +30,5 @@ target_link_libraries(react_nativemodule_dom react_render_dom react_render_uimanager ) +target_compile_reactnative_options(react_nativemodule_dom PRIVATE) +target_compile_options(react_nativemodule_dom PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/featureflags/CMakeLists.txt index be6d99e45e3cf2..ecd30ec3e439f4 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/CMakeLists.txt @@ -24,3 +24,5 @@ target_link_libraries(react_nativemodule_featureflags react_cxxreact react_featureflags ) +target_compile_reactnative_options(react_nativemodule_featureflags PRIVATE) +target_compile_options(react_nativemodule_featureflags PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/CMakeLists.txt index fda5f514e8adf9..0e1be9400bc1c7 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/CMakeLists.txt @@ -24,3 +24,5 @@ target_link_libraries(react_nativemodule_idlecallbacks react_cxxreact react_render_runtimescheduler ) +target_compile_reactnative_options(react_nativemodule_idlecallbacks PRIVATE) +target_compile_options(react_nativemodule_idlecallbacks PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h index dcc2a6c3fd60f5..9eb4b039beb829 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h @@ -49,7 +49,7 @@ using NativeIntersectionObserverEntry = // isIntersectingAboveThresholds bool, // time - double>; + HighResTimeStamp>; template <> struct Bridging diff --git a/packages/react-native/ReactCommon/react/nativemodule/microtasks/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/microtasks/CMakeLists.txt index fe451ed9b3fc9c..c322da34cf79b3 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/microtasks/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/microtasks/CMakeLists.txt @@ -23,3 +23,5 @@ target_link_libraries(react_nativemodule_microtasks react_codegen_rncore react_cxxreact ) +target_compile_reactnative_options(react_nativemodule_microtasks PRIVATE) +target_compile_options(react_nativemodule_microtasks PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt index d4c686051d9f4e..b532866aefb619 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/CMakeLists.txt @@ -25,3 +25,6 @@ target_link_libraries(sampleturbomodule jsi reactnative ) + +target_compile_reactnative_options(sampleturbomodule PRIVATE) +target_compile_options(sampleturbomodule PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index cff8e8678a77d5..9bf3f276f95849 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -8,16 +8,15 @@ #include "NativePerformance.h" #include -#include #include +#include #include #include +#include #include -#include #include #include -#include #include "NativePerformance.h" @@ -25,10 +24,6 @@ #include "Plugins.h" #endif -#ifdef WITH_PERFETTO -#include -#endif - std::shared_ptr NativePerformanceModuleProvider( std::shared_ptr jsInvoker) { return std::make_shared( @@ -39,33 +34,6 @@ namespace facebook::react { namespace { -#if defined(__clang__) -#define NO_DESTROY [[clang::no_destroy]] -#else -#define NO_DESTROY -#endif - -NO_DESTROY const std::string TRACK_PREFIX = "Track:"; - -std::tuple, std::string_view> parseTrackName( - const std::string& name) { - // Until there's a standard way to pass through track information, parse it - // manually, e.g., "Track:Foo:Event name" - // https://github.com/w3c/user-timing/issues/109 - std::optional trackName; - std::string_view eventName(name); - if (name.starts_with(TRACK_PREFIX)) { - const auto trackNameDelimiter = name.find(':', TRACK_PREFIX.length()); - if (trackNameDelimiter != std::string::npos) { - trackName = name.substr( - TRACK_PREFIX.length(), trackNameDelimiter - TRACK_PREFIX.length()); - eventName = std::string_view(name).substr(trackNameDelimiter + 1); - } - } - - return std::make_tuple(trackName, eventName); -} - class PerformanceObserverWrapper : public jsi::NativeState { public: explicit PerformanceObserverWrapper( @@ -80,6 +48,50 @@ void sortEntries(std::vector& entries) { entries.begin(), entries.end(), PerformanceEntrySorter{}); } +NativePerformanceEntry toNativePerformanceEntry(const PerformanceEntry& entry) { + auto nativeEntry = std::visit( + [](const auto& entryData) -> NativePerformanceEntry { + return { + .name = entryData.name, + .entryType = entryData.entryType, + .startTime = entryData.startTime, + .duration = entryData.duration, + }; + }, + entry); + + if (std::holds_alternative(entry)) { + auto eventEntry = std::get(entry); + nativeEntry.processingStart = eventEntry.processingStart; + nativeEntry.processingEnd = eventEntry.processingEnd; + nativeEntry.interactionId = eventEntry.interactionId; + } + if (std::holds_alternative(entry)) { + auto resourceEntry = std::get(entry); + nativeEntry.fetchStart = resourceEntry.fetchStart; + nativeEntry.requestStart = resourceEntry.requestStart; + nativeEntry.connectStart = resourceEntry.connectStart; + nativeEntry.connectEnd = resourceEntry.connectEnd; + nativeEntry.responseStart = resourceEntry.responseStart; + nativeEntry.responseEnd = resourceEntry.responseEnd; + nativeEntry.responseStatus = resourceEntry.responseStatus; + } + + return nativeEntry; +} + +std::vector toNativePerformanceEntries( + std::vector& entries) { + std::vector result; + result.reserve(entries.size()); + + for (auto& entry : entries) { + result.emplace_back(toNativePerformanceEntry(entry)); + } + + return result; +} + const std::array ENTRY_TYPES_AVAILABLE_FROM_TIMELINE{ {PerformanceEntryType::MARK, PerformanceEntryType::MEASURE}}; @@ -100,61 +112,53 @@ std::shared_ptr tryGetObserver( return observerWrapper ? observerWrapper->observer : nullptr; } +PerformanceEntryReporter::UserTimingDetailProvider getDetailProviderFromEntry( + jsi::Runtime& rt, + jsi::Value& entry) { + return [&rt, &entry]() -> folly::dynamic { + try { + auto detail = entry.asObject(rt).getProperty(rt, "detail"); + return jsi::dynamicFromValue(rt, detail); + } catch (jsi::JSIException& ex) { + return nullptr; + } + }; +} + } // namespace NativePerformance::NativePerformance(std::shared_ptr jsInvoker) - : NativePerformanceCxxSpec(std::move(jsInvoker)) { -#ifdef WITH_PERFETTO - initializePerfetto(); -#endif -} + : NativePerformanceCxxSpec(std::move(jsInvoker)) {} -double NativePerformance::now(jsi::Runtime& /*rt*/) { +HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) { return JSExecutor::performanceNow(); } -double NativePerformance::markWithResult( +void NativePerformance::reportMark( jsi::Runtime& rt, std::string name, - std::optional startTime) { - auto [trackName, eventName] = parseTrackName(name); - auto entry = - PerformanceEntryReporter::getInstance()->reportMark(name, startTime); - - ReactPerfettoLogger::mark(eventName, entry.startTime, trackName); - - return entry.startTime; + HighResTimeStamp startTime, + jsi::Value entry) { + PerformanceEntryReporter::getInstance()->reportMark( + name, startTime, getDetailProviderFromEntry(rt, entry)); } -std::tuple NativePerformance::measureWithResult( +void NativePerformance::reportMeasure( jsi::Runtime& rt, std::string name, - double startTime, - double endTime, - std::optional duration, - std::optional startMark, - std::optional endMark) { - auto [trackName, eventName] = parseTrackName(name); - - std::optional trackMetadata; - - if (trackName.has_value()) { - trackMetadata = {.track = trackName.value()}; - } - - auto entry = PerformanceEntryReporter::getInstance()->reportMeasure( - eventName, - startTime, - endTime, - duration, - startMark, - endMark, - trackMetadata); - - ReactPerfettoLogger::measure( - eventName, entry.startTime, entry.startTime + entry.duration, trackName); + HighResTimeStamp startTime, + HighResDuration duration, + jsi::Value entry) { + PerformanceEntryReporter::getInstance()->reportMeasure( + name, startTime, duration, getDetailProviderFromEntry(rt, entry)); +} - return std::tuple{entry.startTime, entry.duration}; +std::optional NativePerformance::getMarkTime( + jsi::Runtime& rt, + std::string name) { + auto markTime = PerformanceEntryReporter::getInstance()->getMarkTime(name); + return markTime ? std::optional{(*markTime).toDOMHighResTimeStamp()} + : std::nullopt; } void NativePerformance::clearMarks( @@ -181,7 +185,7 @@ void NativePerformance::clearMeasures( } } -std::vector NativePerformance::getEntries( +std::vector NativePerformance::getEntries( jsi::Runtime& /*rt*/) { std::vector entries; @@ -191,10 +195,10 @@ std::vector NativePerformance::getEntries( sortEntries(entries); - return entries; + return toNativePerformanceEntries(entries); } -std::vector NativePerformance::getEntriesByName( +std::vector NativePerformance::getEntriesByName( jsi::Runtime& /*rt*/, std::string entryName, std::optional entryType) { @@ -214,10 +218,10 @@ std::vector NativePerformance::getEntriesByName( sortEntries(entries); - return entries; + return toNativePerformanceEntries(entries); } -std::vector NativePerformance::getEntriesByType( +std::vector NativePerformance::getEntriesByType( jsi::Runtime& /*rt*/, PerformanceEntryType entryType) { std::vector entries; @@ -228,7 +232,7 @@ std::vector NativePerformance::getEntriesByType( sortEntries(entries); - return entries; + return toNativePerformanceEntries(entries); } std::vector> NativePerformance::getEventCounts( @@ -329,7 +333,8 @@ void NativePerformance::observe( return; } - auto durationThreshold = options.durationThreshold.value_or(0.0); + auto durationThreshold = + options.durationThreshold.value_or(HighResDuration::zero()); // observer of type multiple if (options.entryTypes.has_value()) { @@ -363,7 +368,7 @@ void NativePerformance::disconnect(jsi::Runtime& rt, jsi::Object observerObj) { observer->disconnect(); } -std::vector NativePerformance::takeRecords( +std::vector NativePerformance::takeRecords( jsi::Runtime& rt, jsi::Object observerObj, bool sort) { @@ -379,7 +384,7 @@ std::vector NativePerformance::takeRecords( if (sort) { sortEntries(records); } - return records; + return toNativePerformanceEntries(records); } std::vector @@ -388,4 +393,16 @@ NativePerformance::getSupportedPerformanceEntryTypes(jsi::Runtime& /*rt*/) { return {supportedEntryTypes.begin(), supportedEntryTypes.end()}; } +#pragma mark - Testing + +void NativePerformance::setCurrentTimeStampForTesting( + jsi::Runtime& /*rt*/, + HighResTimeStamp ts) { + forcedCurrentTimeStamp_ = ts; +} + +void NativePerformance::clearEventCountsForTesting(jsi::Runtime& /*rt*/) { + PerformanceEntryReporter::getInstance()->clearEventCounts(); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index b1954e975d9d17..883e2c245c8a9c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -32,7 +32,7 @@ using NativePerformancePerformanceObserverObserveOptions = // buffered std::optional, // durationThreshold - std::optional>; + std::optional>; template <> struct Bridging { @@ -49,9 +49,32 @@ struct Bridging { } }; +// Our Native Module codegen does not support JS type unions, so we use a +// flattened object here as an intermediate format. +struct NativePerformanceEntry { + std::string name; + PerformanceEntryType entryType; + HighResTimeStamp startTime; + HighResDuration duration; + + // For PerformanceEventTiming only + std::optional processingStart; + std::optional processingEnd; + std::optional interactionId; + + // For PerformanceResourceTiming only + std::optional fetchStart; + std::optional requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; + std::optional responseEnd; + std::optional responseStatus; +}; + template <> -struct Bridging - : NativePerformanceRawPerformanceEntryBridging {}; +struct Bridging + : NativePerformanceRawPerformanceEntryBridging {}; template <> struct Bridging @@ -65,25 +88,24 @@ class NativePerformance : public NativePerformanceCxxSpec { #pragma mark - DOM Performance (High Resolution Time) (https://www.w3.org/TR/hr-time-3/#dom-performance) // https://www.w3.org/TR/hr-time-3/#now-method - double now(jsi::Runtime& rt); + HighResTimeStamp now(jsi::Runtime& rt); #pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/) - // https://w3c.github.io/user-timing/#mark-method - double markWithResult( + void reportMark( jsi::Runtime& rt, std::string name, - std::optional startTime); + HighResTimeStamp time, + jsi::Value entry); - // https://w3c.github.io/user-timing/#measure-method - std::tuple measureWithResult( + void reportMeasure( jsi::Runtime& rt, std::string name, - double startTime, - double endTime, - std::optional duration, - std::optional startMark, - std::optional endMark); + HighResTimeStamp startTime, + HighResDuration duration, + jsi::Value entry); + + std::optional getMarkTime(jsi::Runtime& rt, std::string name); // https://w3c.github.io/user-timing/#clearmarks-method void clearMarks( @@ -98,15 +120,15 @@ class NativePerformance : public NativePerformanceCxxSpec { #pragma mark - Performance Timeline (https://w3c.github.io/performance-timeline/#performance-timeline) // https://www.w3.org/TR/performance-timeline/#getentries-method - std::vector getEntries(jsi::Runtime& rt); + std::vector getEntries(jsi::Runtime& rt); // https://www.w3.org/TR/performance-timeline/#getentriesbytype-method - std::vector getEntriesByType( + std::vector getEntriesByType( jsi::Runtime& rt, PerformanceEntryType entryType); // https://www.w3.org/TR/performance-timeline/#getentriesbyname-method - std::vector getEntriesByName( + std::vector getEntriesByName( jsi::Runtime& rt, std::string entryName, std::optional entryType = std::nullopt); @@ -125,7 +147,7 @@ class NativePerformance : public NativePerformanceCxxSpec { jsi::Object observer, NativePerformancePerformanceObserverObserveOptions options); void disconnect(jsi::Runtime& rt, jsi::Object observer); - std::vector takeRecords( + std::vector takeRecords( jsi::Runtime& rt, jsi::Object observerObj, // When called via `observer.takeRecords` it should be in insertion order. @@ -163,6 +185,14 @@ class NativePerformance : public NativePerformanceCxxSpec { // tracking. std::unordered_map getReactNativeStartupTiming( jsi::Runtime& rt); + +#pragma mark - Testing + + void setCurrentTimeStampForTesting(jsi::Runtime& rt, HighResTimeStamp ts); + void clearEventCountsForTesting(jsi::Runtime& rt); + + private: + std::optional forcedCurrentTimeStamp_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index 335539e390a082..742f6f9fefe3e4 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -6,19 +6,18 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options( - -fexceptions - -frtti - -std=c++20 - -Wall - -Wpedantic - -DLOG_TAG=\"ReactNative\") +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB react_performance_timeline_SRC CONFIGURE_DEPENDS *.cpp) add_library(react_performance_timeline OBJECT ${react_performance_timeline_SRC}) +target_compile_reactnative_options(react_performance_timeline PRIVATE) +target_compile_options(react_performance_timeline PRIVATE -Wpedantic) + target_include_directories(react_performance_timeline PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(react_performance_timeline jsinspector_tracing + reactperflogger + react_featureflags react_timing - react_cxxreact) + folly_runtime) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h index 34e18be4d144a4..aa24bb30221de6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h @@ -10,7 +10,6 @@ #include #include #include -#include "PerformanceEntry.h" namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index 191970e4bc5bfb..887af0d1a0753c 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -8,9 +8,10 @@ #pragma once #include + #include #include -#include +#include namespace facebook::react { @@ -22,31 +23,69 @@ enum class PerformanceEntryType { MEASURE = 2, EVENT = 3, LONGTASK = 4, - _NEXT = 5, + RESOURCE = 5, + _NEXT = 6, }; -struct PerformanceEntry { +struct AbstractPerformanceEntry { std::string name; - PerformanceEntryType entryType; - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp duration = 0; + HighResTimeStamp startTime; + HighResDuration duration = HighResDuration::zero(); +}; + +struct PerformanceMark : AbstractPerformanceEntry { + static constexpr PerformanceEntryType entryType = PerformanceEntryType::MARK; +}; + +struct PerformanceMeasure : AbstractPerformanceEntry { + static constexpr PerformanceEntryType entryType = + PerformanceEntryType::MEASURE; +}; + +struct PerformanceEventTiming : AbstractPerformanceEntry { + static constexpr PerformanceEntryType entryType = PerformanceEntryType::EVENT; + HighResTimeStamp processingStart; + HighResTimeStamp processingEnd; + PerformanceEntryInteractionId interactionId; +}; + +struct PerformanceLongTaskTiming : AbstractPerformanceEntry { + static constexpr PerformanceEntryType entryType = + PerformanceEntryType::LONGTASK; +}; - // For "event" entries only: - std::optional processingStart; - std::optional processingEnd; - std::optional interactionId; +struct PerformanceResourceTiming : AbstractPerformanceEntry { + static constexpr PerformanceEntryType entryType = + PerformanceEntryType::RESOURCE; + /** Aligns with `startTime`. */ + HighResTimeStamp fetchStart; + HighResTimeStamp requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; + /** Aligns with `duration`. */ + std::optional responseEnd; + std::optional responseStatus; }; -constexpr size_t NUM_PERFORMANCE_ENTRY_TYPES = - (size_t)PerformanceEntryType::_NEXT - 1; // Valid types start from 1. +using PerformanceEntry = std::variant< + PerformanceMark, + PerformanceMeasure, + PerformanceEventTiming, + PerformanceLongTaskTiming, + PerformanceResourceTiming>; struct PerformanceEntrySorter { bool operator()(const PerformanceEntry& lhs, const PerformanceEntry& rhs) { - if (lhs.startTime != rhs.startTime) { - return lhs.startTime < rhs.startTime; - } else { - return lhs.duration < rhs.duration; - } + return std::visit( + [](const auto& left, const auto& right) { + if (left.startTime != right.startTime) { + return left.startTime < right.startTime; + } + return left.duration < right.duration; + }, + lhs, + rhs); } }; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 96a137ace1463f..c3ce79d34127eb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -14,7 +14,7 @@ namespace facebook::react { // Default duration threshold for reporting performance entries (0 means "report // all") -constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; +constexpr HighResDuration DEFAULT_DURATION_THRESHOLD = HighResDuration::zero(); /** * Abstract performance entry buffer with reporting flags. @@ -22,7 +22,7 @@ constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; */ class PerformanceEntryBuffer { public: - double durationThreshold{DEFAULT_DURATION_THRESHOLD}; + HighResDuration durationThreshold = DEFAULT_DURATION_THRESHOLD; size_t droppedEntriesCount{0}; explicit PerformanceEntryBuffer() = default; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index 4a38e847d7e88a..c64ad46a3f9cc4 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -7,6 +7,8 @@ #include "PerformanceEntryCircularBuffer.h" +#include + namespace facebook::react { void PerformanceEntryCircularBuffer::add(const PerformanceEntry& entry) { @@ -23,8 +25,11 @@ void PerformanceEntryCircularBuffer::getEntries( void PerformanceEntryCircularBuffer::getEntries( std::vector& target, const std::string& name) const { - buffer_.getEntries( - target, [&](const PerformanceEntry& e) { return e.name == name; }); + buffer_.getEntries(target, [&](const PerformanceEntry& entry) { + return std::visit( + [&name](const auto& entryData) { return entryData.name == name; }, + entry); + }); } void PerformanceEntryCircularBuffer::clear() { @@ -32,7 +37,11 @@ void PerformanceEntryCircularBuffer::clear() { } void PerformanceEntryCircularBuffer::clear(const std::string& name) { - buffer_.clear([&](const PerformanceEntry& e) { return e.name == name; }); + buffer_.clear([&](const PerformanceEntry& entry) { + return std::visit( + [&name](const auto& entryData) { return entryData.name == name; }, + entry); + }); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index 89e3a3c19060c3..97914ad54d4df7 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -11,12 +11,15 @@ namespace facebook::react { void PerformanceEntryKeyedBuffer::add(const PerformanceEntry& entry) { - auto node = entryMap_.find(entry.name); + auto name = + std::visit([](const auto& entryData) { return entryData.name; }, entry); + + auto node = entryMap_.find(name); if (node != entryMap_.end()) { node->second.push_back(entry); } else { - entryMap_.emplace(entry.name, std::vector{entry}); + entryMap_.emplace(name, std::vector{entry}); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index 85deed9f418786..b65c6f949f52dd 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -8,7 +8,6 @@ #pragma once #include -#include #include #include #include "PerformanceEntryBuffer.h" diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 36c947cda77a2f..c46a1a4faf7b65 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -7,9 +7,16 @@ #include "PerformanceEntryReporter.h" -#include #include #include +#include +#include + +#ifdef WITH_PERFETTO +#include +#endif + +#include namespace facebook::react { @@ -22,15 +29,47 @@ std::vector getSupportedEntryTypesInternal() { PerformanceEntryType::EVENT, }; - if (ReactNativeFeatureFlags::enableLongTaskAPI()) { - supportedEntryTypes.emplace_back(PerformanceEntryType::LONGTASK); + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + supportedEntryTypes.emplace_back(PerformanceEntryType::RESOURCE); } return supportedEntryTypes; } -uint64_t timestampToMicroseconds(DOMHighResTimeStamp timestamp) { - return static_cast(timestamp * 1000); +std::optional getTrackFromDetail(folly::dynamic& detail) { + if (!detail.isObject()) { + return std::nullopt; + } + + auto maybeDevtools = detail["devtools"]; + if (!maybeDevtools.isObject()) { + return std::nullopt; + } + + auto maybeTrack = maybeDevtools["track"]; + if (!maybeTrack.isString()) { + return std::nullopt; + } + + return maybeTrack.asString(); +} + +std::optional getTrackGroupFromDetail(folly::dynamic& detail) { + if (!detail.isObject()) { + return std::nullopt; + } + + auto maybeDevtools = detail["devtools"]; + if (!maybeDevtools.isObject()) { + return std::nullopt; + } + + auto maybeTrackGroup = maybeDevtools["trackGroup"]; + if (!maybeTrackGroup.isString()) { + return std::nullopt; + } + + return maybeTrackGroup.asString(); } } // namespace @@ -42,11 +81,15 @@ PerformanceEntryReporter::getInstance() { } PerformanceEntryReporter::PerformanceEntryReporter() - : observerRegistry_(std::make_unique()) {} + : observerRegistry_(std::make_unique()) { +#ifdef WITH_PERFETTO + initializePerfetto(); +#endif +} -DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { +HighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() - : JSExecutor::performanceNow(); + : HighResTimeStamp::now(); } std::vector @@ -132,87 +175,66 @@ void PerformanceEntryReporter::clearEntries( getBufferRef(entryType).clear(entryName); } -PerformanceEntry PerformanceEntryReporter::reportMark( +void PerformanceEntryReporter::reportMark( const std::string& name, - const std::optional& startTime) { - auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp(); - const auto entry = PerformanceEntry{ - .name = name, - .entryType = PerformanceEntryType::MARK, - .startTime = startTimeVal}; + const HighResTimeStamp startTime, + UserTimingDetailProvider&& detailProvider) { + const auto entry = PerformanceMark{{.name = name, .startTime = startTime}}; + traceMark(entry, std::move(detailProvider)); + + // Add to buffers & notify observers { std::unique_lock lock(buffersMutex_); markBuffer_.add(entry); } - jsinspector_modern::PerformanceTracer::getInstance().reportMark( - name, timestampToMicroseconds(startTimeVal)); - observerRegistry_->queuePerformanceEntry(entry); - return entry; } -PerformanceEntry PerformanceEntryReporter::reportMeasure( - const std::string_view& name, - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp endTime, - const std::optional& duration, - const std::optional& startMark, - const std::optional& endMark, - const std::optional& - trackMetadata) { - DOMHighResTimeStamp startTimeVal = - startMark ? getMarkTime(*startMark) : startTime; - DOMHighResTimeStamp endTimeVal = endMark ? getMarkTime(*endMark) : endTime; - - if (!endMark && endTime < startTimeVal) { - // The end time is not specified, take the current time, according to the - // standard - endTimeVal = getCurrentTimeStamp(); - } - - DOMHighResTimeStamp durationVal = - duration ? *duration : endTimeVal - startTimeVal; +void PerformanceEntryReporter::reportMeasure( + const std::string& name, + HighResTimeStamp startTime, + HighResDuration duration, + UserTimingDetailProvider&& detailProvider) { + const auto entry = PerformanceMeasure{ + {.name = std::string(name), + .startTime = startTime, + .duration = duration}}; - const auto entry = PerformanceEntry{ - .name = std::string(name), - .entryType = PerformanceEntryType::MEASURE, - .startTime = startTimeVal, - .duration = durationVal}; + traceMeasure(entry, std::move(detailProvider)); + // Add to buffers & notify observers { std::unique_lock lock(buffersMutex_); measureBuffer_.add(entry); } - jsinspector_modern::PerformanceTracer::getInstance().reportMeasure( - name, - timestampToMicroseconds(startTimeVal), - timestampToMicroseconds(durationVal), - trackMetadata); - observerRegistry_->queuePerformanceEntry(entry); - return entry; } -DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( +void PerformanceEntryReporter::clearEventCounts() { + eventCounts_.clear(); +} + +std::optional PerformanceEntryReporter::getMarkTime( const std::string& markName) const { std::shared_lock lock(buffersMutex_); if (auto it = markBuffer_.find(markName); it) { - return it->startTime; - } else { - return 0.0; + return std::visit( + [](const auto& entryData) { return entryData.startTime; }, *it); } + + return std::nullopt; } void PerformanceEntryReporter::reportEvent( std::string name, - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp duration, - DOMHighResTimeStamp processingStart, - DOMHighResTimeStamp processingEnd, + HighResTimeStamp startTime, + HighResDuration duration, + HighResTimeStamp processingStart, + HighResTimeStamp processingEnd, uint32_t interactionId) { eventCounts_[name]++; @@ -222,14 +244,11 @@ void PerformanceEntryReporter::reportEvent( return; } - const auto entry = PerformanceEntry{ - .name = std::move(name), - .entryType = PerformanceEntryType::EVENT, - .startTime = startTime, - .duration = duration, - .processingStart = processingStart, - .processingEnd = processingEnd, - .interactionId = interactionId}; + const auto entry = PerformanceEventTiming{ + {.name = std::move(name), .startTime = startTime, .duration = duration}, + processingStart, + processingEnd, + interactionId}; { std::unique_lock lock(buffersMutex_); @@ -241,13 +260,12 @@ void PerformanceEntryReporter::reportEvent( } void PerformanceEntryReporter::reportLongTask( - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp duration) { - const auto entry = PerformanceEntry{ - .name = std::string{"self"}, - .entryType = PerformanceEntryType::LONGTASK, - .startTime = startTime, - .duration = duration}; + HighResTimeStamp startTime, + HighResDuration duration) { + const auto entry = PerformanceLongTaskTiming{ + {.name = std::string{"self"}, + .startTime = startTime, + .duration = duration}}; { std::unique_lock lock(buffersMutex_); @@ -257,4 +275,75 @@ void PerformanceEntryReporter::reportLongTask( observerRegistry_->queuePerformanceEntry(entry); } +void PerformanceEntryReporter::reportResourceTiming( + const std::string& url, + HighResTimeStamp fetchStart, + HighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + HighResTimeStamp responseStart, + HighResTimeStamp responseEnd, + const std::optional& responseStatus) { + const auto entry = PerformanceResourceTiming{ + {.name = url, .startTime = fetchStart}, + fetchStart, + requestStart, + connectStart, + connectEnd, + responseStart, + responseEnd, + responseStatus, + }; + + // Add to buffers & notify observers + { + std::unique_lock lock(buffersMutex_); + resourceTimingBuffer_.add(entry); + } + + observerRegistry_->queuePerformanceEntry(entry); +} + +void PerformanceEntryReporter::traceMark( + const PerformanceMark& entry, + UserTimingDetailProvider&& detailProvider) const { + auto& performanceTracer = + jsinspector_modern::tracing::PerformanceTracer::getInstance(); + + if (ReactPerfettoLogger::isTracing()) { + ReactPerfettoLogger::mark(entry.name, entry.startTime); + } + + if (performanceTracer.isTracing()) { + performanceTracer.reportMark( + entry.name, + entry.startTime, + detailProvider != nullptr ? detailProvider() : nullptr); + } +} + +void PerformanceEntryReporter::traceMeasure( + const PerformanceMeasure& entry, + UserTimingDetailProvider&& detailProvider) const { + auto& performanceTracer = + jsinspector_modern::tracing::PerformanceTracer::getInstance(); + if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) { + auto detail = detailProvider != nullptr ? detailProvider() : nullptr; + + if (ReactPerfettoLogger::isTracing()) { + ReactPerfettoLogger::measure( + entry.name, + entry.startTime, + entry.startTime + entry.duration, + detail != nullptr ? getTrackFromDetail(detail) : std::nullopt, + detail != nullptr ? getTrackGroupFromDetail(detail) : std::nullopt); + } + + if (performanceTracer.isTracing()) { + performanceTracer.reportMeasure( + entry.name, entry.startTime, entry.duration, std::move(detail)); + } + } +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 96730861a9ea9b..d66427b230fc7e 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -11,7 +11,7 @@ #include "PerformanceEntryKeyedBuffer.h" #include "PerformanceObserverRegistry.h" -#include +#include #include #include @@ -21,10 +21,14 @@ namespace facebook::react { +// Aligned with maxBufferSize implemented by browsers +// https://w3c.github.io/timing-entrytypes-registry/#registry constexpr size_t EVENT_BUFFER_SIZE = 150; constexpr size_t LONG_TASK_BUFFER_SIZE = 200; +constexpr size_t RESOURCE_TIMING_BUFFER_SIZE = 250; -constexpr DOMHighResTimeStamp LONG_TASK_DURATION_THRESHOLD_MS = 50.0; +constexpr HighResDuration LONG_TASK_DURATION_THRESHOLD = + HighResDuration::fromMilliseconds(50); class PerformanceEntryReporter { public: @@ -63,9 +67,9 @@ class PerformanceEntryReporter { PerformanceEntryType entryType, const std::string& entryName); - DOMHighResTimeStamp getCurrentTimeStamp() const; + HighResTimeStamp getCurrentTimeStamp() const; - void setTimeStampProvider(std::function provider) { + void setTimeStampProvider(std::function provider) { timeStampProvider_ = std::move(provider); } @@ -77,29 +81,43 @@ class PerformanceEntryReporter { return eventCounts_; } - PerformanceEntry reportMark( + void clearEventCounts(); + + std::optional getMarkTime( + const std::string& markName) const; + + using UserTimingDetailProvider = std::function; + + void reportMark( + const std::string& name, + HighResTimeStamp startTime, + UserTimingDetailProvider&& detailProvider = nullptr); + + void reportMeasure( const std::string& name, - const std::optional& startTime = std::nullopt); - - PerformanceEntry reportMeasure( - const std::string_view& name, - double startTime, - double endTime, - const std::optional& duration = std::nullopt, - const std::optional& startMark = std::nullopt, - const std::optional& endMark = std::nullopt, - const std::optional& - trackMetadata = std::nullopt); + HighResTimeStamp startTime, + HighResDuration duration, + UserTimingDetailProvider&& detailProvider = nullptr); void reportEvent( std::string name, - double startTime, - double duration, - double processingStart, - double processingEnd, + HighResTimeStamp startTime, + HighResDuration duration, + HighResTimeStamp processingStart, + HighResTimeStamp processingEnd, uint32_t interactionId); - void reportLongTask(double startTime, double duration); + void reportLongTask(HighResTimeStamp startTime, HighResDuration duration); + + void reportResourceTiming( + const std::string& url, + HighResTimeStamp fetchStart, + HighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + HighResTimeStamp responseStart, + HighResTimeStamp responseEnd, + const std::optional& responseStatus); private: std::unique_ptr observerRegistry_; @@ -107,14 +125,14 @@ class PerformanceEntryReporter { mutable std::shared_mutex buffersMutex_; PerformanceEntryCircularBuffer eventBuffer_{EVENT_BUFFER_SIZE}; PerformanceEntryCircularBuffer longTaskBuffer_{LONG_TASK_BUFFER_SIZE}; + PerformanceEntryCircularBuffer resourceTimingBuffer_{ + RESOURCE_TIMING_BUFFER_SIZE}; PerformanceEntryKeyedBuffer markBuffer_; PerformanceEntryKeyedBuffer measureBuffer_; std::unordered_map eventCounts_; - std::function timeStampProvider_ = nullptr; - - double getMarkTime(const std::string& markName) const; + std::function timeStampProvider_ = nullptr; const inline PerformanceEntryBuffer& getBuffer( PerformanceEntryType entryType) const { @@ -127,6 +145,8 @@ class PerformanceEntryReporter { return measureBuffer_; case PerformanceEntryType::LONGTASK: return longTaskBuffer_; + case PerformanceEntryType::RESOURCE: + return resourceTimingBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } @@ -143,11 +163,20 @@ class PerformanceEntryReporter { return measureBuffer_; case PerformanceEntryType::LONGTASK: return longTaskBuffer_; + case PerformanceEntryType::RESOURCE: + return resourceTimingBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } throw std::logic_error("Unhandled PerformanceEntryType"); } + + void traceMark( + const PerformanceMark& entry, + UserTimingDetailProvider&& detailProvider) const; + void traceMeasure( + const PerformanceMeasure& entry, + UserTimingDetailProvider&& detailProvider) const; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index f36f87016f3716..fbdf02a31ffc62 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -8,13 +8,18 @@ #include "PerformanceObserver.h" #include "PerformanceEntryReporter.h" +#include + namespace facebook::react { void PerformanceObserver::handleEntry(const PerformanceEntry& entry) { - if (observedTypes_.contains(entry.entryType)) { + auto entryType = std::visit( + [](const auto& entryData) { return entryData.entryType; }, entry); + + if (observedTypes_.contains(entryType)) { // https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming - if (entry.entryType == PerformanceEntryType::EVENT && - entry.duration < durationThreshold_) { + if (std::holds_alternative(entry) && + std::get(entry).duration < durationThreshold_) { // The entries duration is lower than the desired reporting threshold, // skip return; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index a79d8d0a99feb4..8b387ec753c54d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -7,12 +7,15 @@ #pragma once +#include "PerformanceEntryBuffer.h" +#include "PerformanceObserverRegistry.h" + +#include + #include #include #include #include -#include "PerformanceEntryBuffer.h" -#include "PerformanceObserverRegistry.h" namespace facebook::react { @@ -27,7 +30,7 @@ using PerformanceObserverCallback = std::function; * https://w3c.github.io/performance-timeline/#performanceobserverinit-dictionary */ struct PerformanceObserverObserveMultipleOptions { - double durationThreshold = 0.0; + HighResDuration durationThreshold = DEFAULT_DURATION_THRESHOLD; }; /** @@ -38,7 +41,7 @@ struct PerformanceObserverObserveMultipleOptions { */ struct PerformanceObserverObserveSingleOptions { bool buffered = false; - double durationThreshold = 0.0; + HighResDuration durationThreshold = DEFAULT_DURATION_THRESHOLD; }; /** @@ -124,7 +127,7 @@ class PerformanceObserver PerformanceObserverEntryTypeFilter observedTypes_; /// https://www.w3.org/TR/event-timing/#sec-modifications-perf-timeline - double durationThreshold_{DEFAULT_DURATION_THRESHOLD}; + HighResDuration durationThreshold_ = DEFAULT_DURATION_THRESHOLD; std::vector buffer_; bool didScheduleFlushBuffer_ = false; bool requiresDroppedEntries_ = false; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec index fd0bcd29a0a00f..470b72f235e75d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec +++ b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec @@ -54,6 +54,6 @@ Pod::Spec.new do |s| s.dependency "React-featureflags" add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing') s.dependency "React-timing" - s.dependency "React-cxxreact" + s.dependency "React-perflogger" s.dependency "RCT-Folly", folly_version end diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp index d11642a68765ed..824816d8168472 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -11,6 +11,9 @@ #include "../PerformanceEntryReporter.h" +#include +#include + using namespace facebook::react; namespace facebook::react { @@ -18,27 +21,53 @@ namespace facebook::react { [[maybe_unused]] static bool operator==( const PerformanceEntry& lhs, const PerformanceEntry& rhs) { - return lhs.name == rhs.name && lhs.entryType == rhs.entryType && - lhs.startTime == rhs.startTime && lhs.duration == rhs.duration && - lhs.processingStart == rhs.processingStart && - lhs.processingEnd == rhs.processingEnd && - lhs.interactionId == rhs.interactionId; + return std::visit( + [&](const auto& left, const auto& right) { + bool baseMatch = left.name == right.name && + left.entryType == right.entryType && + left.startTime == right.startTime && + left.duration == right.duration; + + if (baseMatch && left.entryType == PerformanceEntryType::EVENT) { + auto leftEventTiming = std::get(lhs); + auto rightEventTiming = std::get(rhs); + + return leftEventTiming.processingStart == + rightEventTiming.processingStart && + leftEventTiming.processingEnd == rightEventTiming.processingEnd && + leftEventTiming.interactionId == rightEventTiming.interactionId; + } + + return baseMatch; + }, + lhs, + rhs); } [[maybe_unused]] static std::ostream& operator<<( std::ostream& os, const PerformanceEntry& entry) { - static constexpr const char* entryTypeNames[] = { + static constexpr auto entryTypeNames = std::to_array({ "PerformanceEntryType::UNDEFINED", "PerformanceEntryType::MARK", "PerformanceEntryType::MEASURE", "PerformanceEntryType::EVENT", - }; - return os << "{ .name = \"" << entry.name << "\"" << ", .entryType = " - << entryTypeNames[static_cast(entry.entryType)] - << ", .startTime = " << entry.startTime - << ", .duration = " << entry.duration << " }"; + "PerformanceEntryType::RESOURCE", + }); + + return std::visit( + [&](const auto& entryDetails) -> std::ostream& { + os << "{ .name = \"" << entryDetails.name << "\"" << ", .entryType = " + << entryTypeNames[static_cast(entryDetails.entryType) - 1] + << ", .startTime = " + << entryDetails.startTime.toDOMHighResTimeStamp() + << ", .duration = " << entryDetails.duration.toDOMHighResTimeStamp() + << " }"; + return os; + }, + entry); } + } // namespace facebook::react namespace { @@ -51,130 +80,112 @@ std::vector toSorted( TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); - reporter->reportMark("mark0", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark2", 2); + reporter->reportMark("mark0", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); // Report mark0 again - reporter->reportMark("mark0", 3); + reporter->reportMark( + "mark0", timeOrigin + HighResDuration::fromMilliseconds(3)); const auto entries = toSorted(reporter->getEntries()); ASSERT_EQ(4, entries.size()); const std::vector expected = { - {.name = "mark0", - .entryType = PerformanceEntryType::MARK, - .startTime = 0, - .duration = 0}, - {.name = "mark1", - .entryType = PerformanceEntryType::MARK, - .startTime = 1, - .duration = 0}, - {.name = "mark2", - .entryType = PerformanceEntryType::MARK, - .startTime = 2, - .duration = 0}, - {.name = "mark0", - .entryType = PerformanceEntryType::MARK, - .startTime = 3, - .duration = 0}}; + PerformanceMark{ + {.name = "mark0", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark0", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(3), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, entries); } TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); - reporter->reportMark("mark0", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark2", 2); + reporter->reportMark("mark0", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); - reporter->reportMeasure("measure0", 0, 2); - reporter->reportMeasure("measure1", 0, 2, 4); - reporter->reportMeasure("measure2", 0, 0, std::nullopt, "mark1", "mark2"); - reporter->reportMeasure("measure3", 0, 0, 5, "mark1"); reporter->reportMeasure( - "measure4", 1.5, 0, std::nullopt, std::nullopt, "mark2"); - - reporter->setTimeStampProvider([]() { return 3.5; }); - reporter->reportMeasure("measure5", 0, 0, std::nullopt, "mark2"); + "measure0", timeOrigin, HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", timeOrigin, HighResDuration::fromMilliseconds(3)); - reporter->reportMark("mark3", 2.5); - reporter->reportMeasure("measure6", 2.0, 2.0); - reporter->reportMark("mark4", 2.1); - reporter->reportMark("mark4", 3.0); - // Uses the last reported time for mark4 - reporter->reportMeasure("measure7", 0, 0, std::nullopt, "mark1", "mark4"); + reporter->reportMark( + "mark3", timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(2), + HighResDuration::fromMilliseconds(2)); + reporter->reportMark( + "mark4", timeOrigin + HighResDuration::fromMilliseconds(3)); const auto entries = toSorted(reporter->getEntries()); const std::vector expected = { - {.name = "mark0", - .entryType = PerformanceEntryType::MARK, - .startTime = 0, - .duration = 0}, - {.name = "measure0", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 2}, - {.name = "measure1", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 4}, - {.name = "mark1", - .entryType = PerformanceEntryType::MARK, - .startTime = 1, - .duration = 0}, - {.name = "measure2", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 1}, - {.name = "measure7", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 2}, - {.name = "measure3", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 5}, - {.name = "measure4", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1.5, - .duration = 0.5}, - {.name = "mark2", - .entryType = PerformanceEntryType::MARK, - .startTime = 2, - .duration = 0}, - {.name = "measure6", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 2, - .duration = 0}, - {.name = "measure5", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 2, - .duration = 1.5}, - {.name = "mark4", - .entryType = PerformanceEntryType::MARK, - .startTime = 2.1, - .duration = 0}, - {.name = "mark3", - .entryType = PerformanceEntryType::MARK, - .startTime = 2.5, - .duration = 0}, - {.name = "mark4", - .entryType = PerformanceEntryType::MARK, - .startTime = 3, - .duration = 0}}; + PerformanceMark{ + {.name = "mark0", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "measure0", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMeasure{ + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMark{ + {.name = "mark3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark4", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(3), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, entries); } TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); { @@ -182,52 +193,57 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { ASSERT_EQ(0, entries.size()); } - reporter->reportMark("common_name", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark2", 2); + reporter->reportMark("common_name", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); - reporter->reportMeasure("common_name", 0, 2); - reporter->reportMeasure("measure1", 0, 2, 4); - reporter->reportMeasure("measure2", 0, 0, std::nullopt, "mark1", "mark2"); - reporter->reportMeasure("measure3", 0, 0, 5, "mark1"); reporter->reportMeasure( - "measure4", 1.5, 0, std::nullopt, std::nullopt, "mark2"); + "common_name", timeOrigin, HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", timeOrigin, HighResDuration::fromMilliseconds(3)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(1), + HighResDuration::fromMilliseconds(6)); + reporter->reportMeasure( + "measure3", + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + HighResDuration::fromMilliseconds(2)); { const auto allEntries = toSorted(reporter->getEntries()); const std::vector expected = { - {.name = "common_name", - .entryType = PerformanceEntryType::MARK, - .startTime = 0, - .duration = 0}, - {.name = "common_name", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 2}, - {.name = "measure1", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 4}, - {.name = "mark1", - .entryType = PerformanceEntryType::MARK, - .startTime = 1, - .duration = 0}, - {.name = "measure2", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 1}, - {.name = "measure3", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 5}, - {.name = "measure4", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1.5, - .duration = 0.5}, - {.name = "mark2", - .entryType = PerformanceEntryType::MARK, - .startTime = 2, - .duration = 0}}; + PerformanceMark{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMeasure{ + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::fromMilliseconds(6)}}, + PerformanceMeasure{ + {.name = "measure3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, allEntries); } @@ -235,18 +251,18 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { const auto marks = toSorted(reporter->getEntries(PerformanceEntryType::MARK)); const std::vector expected = { - {.name = "common_name", - .entryType = PerformanceEntryType::MARK, - .startTime = 0, - .duration = 0}, - {.name = "mark1", - .entryType = PerformanceEntryType::MARK, - .startTime = 1, - .duration = 0}, - {.name = "mark2", - .entryType = PerformanceEntryType::MARK, - .startTime = 2, - .duration = 0}}; + PerformanceMark{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, marks); } @@ -254,45 +270,39 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { const auto measures = toSorted(reporter->getEntries(PerformanceEntryType::MEASURE)); const std::vector expected = { - {.name = "common_name", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 2}, - {.name = "measure1", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 4}, - {.name = "measure2", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 1}, - {.name = "measure3", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 5}, - {.name = "measure4", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1.5, - .duration = 0.5}}; + PerformanceMeasure{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMeasure{ + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, + PerformanceMeasure{ + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::fromMilliseconds(6)}}, + PerformanceMeasure{ + {.name = "measure3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + .duration = HighResDuration::fromMilliseconds(2)}}}; ASSERT_EQ(expected, measures); } { const std::vector expected = { - {.name = "common_name", - .entryType = PerformanceEntryType::MARK, - .startTime = 0}}; + PerformanceMark{{.name = "common_name", .startTime = timeOrigin}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MARK, "common_name"); ASSERT_EQ(expected, commonName); } { - const std::vector expected = { + const std::vector expected = {PerformanceMeasure{ {.name = "common_name", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 2}}; + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MEASURE, "common_name"); ASSERT_EQ(expected, commonName); @@ -301,37 +311,48 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); - reporter->reportMark("common_name", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark1", 2.1); - reporter->reportMark("mark2", 2); + reporter->reportMark("common_name", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromNanoseconds(2.1 * 1e6)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); - reporter->reportMeasure("common_name", 0, 2); - reporter->reportMeasure("measure1", 0, 2, 4); - reporter->reportMeasure("measure2", 0, 0, std::nullopt, "mark1", "mark2"); - reporter->reportMeasure("measure3", 0, 0, 5, "mark1"); reporter->reportMeasure( - "measure4", 1.5, 0, std::nullopt, std::nullopt, "mark2"); + "common_name", timeOrigin, HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", timeOrigin, HighResDuration::fromMilliseconds(3)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(1), + HighResDuration::fromMilliseconds(6)); + reporter->reportMeasure( + "measure3", + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + HighResDuration::fromMilliseconds(2)); reporter->clearEntries(PerformanceEntryType::MARK, "common_name"); { auto entries = toSorted(reporter->getEntries(PerformanceEntryType::MARK)); std::vector expected = { - {.name = "mark1", - .entryType = PerformanceEntryType::MARK, - .startTime = 1, - .duration = 0}, - {.name = "mark2", - .entryType = PerformanceEntryType::MARK, - .startTime = 2, - .duration = 0}, - {.name = "mark1", - .entryType = PerformanceEntryType::MARK, - .startTime = 2.1, - .duration = 0}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark1", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(2.1 * 1e6), + .duration = HighResDuration::zero()}}, }; ASSERT_EQ(expected, entries); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp index 09e72be298803e..8bb6a9163afcf3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp @@ -16,11 +16,27 @@ namespace facebook::react { [[maybe_unused]] static bool operator==( const PerformanceEntry& lhs, const PerformanceEntry& rhs) { - return lhs.name == rhs.name && lhs.entryType == rhs.entryType && - lhs.startTime == rhs.startTime && lhs.duration == rhs.duration && - lhs.processingStart == rhs.processingStart && - lhs.processingEnd == rhs.processingEnd && - lhs.interactionId == rhs.interactionId; + return std::visit( + [&](const auto& left, const auto& right) { + bool baseMatch = left.name == right.name && + left.entryType == right.entryType && + left.startTime == right.startTime && + left.duration == right.duration; + + if (baseMatch && left.entryType == PerformanceEntryType::EVENT) { + auto leftEventTiming = std::get(lhs); + auto rightEventTiming = std::get(rhs); + + return leftEventTiming.processingStart == + rightEventTiming.processingStart && + leftEventTiming.processingEnd == rightEventTiming.processingEnd && + leftEventTiming.interactionId == rightEventTiming.interactionId; + } + + return baseMatch; + }, + lhs, + rhs); } } // namespace facebook::react @@ -33,12 +49,14 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveFlushes) { bool callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); + auto timeOrigin = HighResTimeStamp::now(); observer->observe(PerformanceEntryType::MARK); // buffer is empty ASSERT_FALSE(callbackCalled); - reporter->reportMark("test", 10); + reporter->reportMark( + "test", timeOrigin + HighResDuration::fromMilliseconds(10)); ASSERT_TRUE(callbackCalled); observer->disconnect(); @@ -48,10 +66,12 @@ TEST(PerformanceObserver, PerformanceObserverTestFilteredSingle) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); observer->observe(PerformanceEntryType::MEASURE); - reporter->reportMark("test", 10); + reporter->reportMark( + "test", timeOrigin + HighResDuration::fromMilliseconds(10)); // wrong type ASSERT_EQ(observer->takeRecords().size(), 0); @@ -63,15 +83,34 @@ TEST(PerformanceObserver, PerformanceObserverTestFilterMulti) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); observer->observe( {PerformanceEntryType::MEASURE, PerformanceEntryType::MARK}); - reporter->reportEvent("test1", 10, 10, 0, 0, 0); - reporter->reportEvent("test2", 10, 10, 0, 0, 0); - reporter->reportEvent("test3", 10, 10, 0, 0, 0); + reporter->reportEvent( + "test1", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test3", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); ASSERT_EQ(observer->takeRecords().size(), 0); ASSERT_FALSE(callbackCalled); @@ -85,11 +124,14 @@ TEST( auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); observer->observe(PerformanceEntryType::MEASURE); - reporter->reportMark("test", 10); + + reporter->reportMark( + "test", timeOrigin + HighResDuration::fromMilliseconds(10)); ASSERT_FALSE(callbackCalled); @@ -100,14 +142,34 @@ TEST(PerformanceObserver, PerformanceObserverTestFilterMultiCallbackNotCalled) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); observer->observe( {PerformanceEntryType::MEASURE, PerformanceEntryType::MARK}); - reporter->reportEvent("test1", 10, 10, 0, 0, 0); - reporter->reportEvent("test2", 10, 10, 0, 0, 0); - reporter->reportEvent("off3", 10, 10, 0, 0, 0); + + reporter->reportEvent( + "test1", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "off3", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); ASSERT_FALSE(callbackCalled); @@ -118,24 +180,32 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); observer->observe(PerformanceEntryType::MARK); - reporter->reportMark("test1", 10); - reporter->reportMeasure("off", 10, 20); - reporter->reportMark("test2", 20); - reporter->reportMark("test3", 30); + + reporter->reportMark( + "test1", timeOrigin + HighResDuration::fromMilliseconds(10)); + reporter->reportMeasure( + "off", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(20)); + reporter->reportMark( + "test2", timeOrigin + HighResDuration::fromMilliseconds(20)); + reporter->reportMark( + "test3", timeOrigin + HighResDuration::fromMilliseconds(30)); const std::vector expected = { - {.name = "test1", - .entryType = PerformanceEntryType::MARK, - .startTime = 10}, - {.name = "test2", - .entryType = PerformanceEntryType::MARK, - .startTime = 20}, - {.name = "test3", - .entryType = PerformanceEntryType::MARK, - .startTime = 30}, + PerformanceMark{ + {.name = "test1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(10)}}, + PerformanceMark{ + {.name = "test2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(20)}}, + PerformanceMark{ + {.name = "test3", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(30)}}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -147,34 +217,66 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveDurationThreshold) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); - observer->observe(PerformanceEntryType::EVENT, {.durationThreshold = 50}); - reporter->reportEvent("test1", 0, 50, 0, 0, 0); - reporter->reportEvent("test2", 0, 100, 0, 0, 0); - reporter->reportEvent("off1", 0, 40, 0, 0, 0); - reporter->reportMark("off2", 100); - reporter->reportEvent("test3", 0, 60, 0, 0, 0); + observer->observe( + PerformanceEntryType::EVENT, + {.durationThreshold = HighResDuration::fromMilliseconds(50)}); + + reporter->reportEvent( + "test1", + timeOrigin, + HighResDuration::fromMilliseconds(50), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "off1", + timeOrigin, + HighResDuration::fromMilliseconds(40), + timeOrigin, + timeOrigin, + 0); + reporter->reportMark( + "off2", timeOrigin + HighResDuration::fromMilliseconds(100)); + reporter->reportEvent( + "test3", + timeOrigin, + HighResDuration::fromMilliseconds(60), + timeOrigin, + timeOrigin, + 0); const std::vector expected = { - {.name = "test1", - .entryType = PerformanceEntryType::EVENT, - .duration = 50, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}, - {.name = "test2", - .entryType = PerformanceEntryType::EVENT, - .duration = 100, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}, - {.name = "test3", - .entryType = PerformanceEntryType::EVENT, - .duration = 60, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}, + PerformanceEventTiming{ + {.name = "test1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(50)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "test2", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "test3", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(60)}, + timeOrigin, + timeOrigin, + 0}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -186,38 +288,66 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveBuffered) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); - reporter->reportEvent("test1", 0, 50, 0, 0, 0); - reporter->reportEvent("test2", 0, 100, 0, 0, 0); - reporter->reportEvent("test3", 0, 40, 0, 0, 0); - reporter->reportEvent("test4", 0, 100, 0, 0, 0); + auto timeOrigin = HighResTimeStamp::now(); + reporter->reportEvent( + "test1", + timeOrigin, + HighResDuration::fromMilliseconds(50), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test3", + timeOrigin, + HighResDuration::fromMilliseconds(40), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test4", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); observer->observe( - PerformanceEntryType::EVENT, {.buffered = true, .durationThreshold = 50}); + PerformanceEntryType::EVENT, + {.buffered = true, + .durationThreshold = HighResDuration::fromMilliseconds(50)}); const std::vector expected = { - {.name = "test1", - .entryType = PerformanceEntryType::EVENT, - .startTime = 0, - .duration = 50, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}, - {.name = "test2", - .entryType = PerformanceEntryType::EVENT, - .startTime = 0, - .duration = 100, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}, - {.name = "test4", - .entryType = PerformanceEntryType::EVENT, - .startTime = 0, - .duration = 100, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}}; + PerformanceEventTiming{ + {.name = "test1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(50)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "test2", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "test4", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, + }; ASSERT_EQ(expected, observer->takeRecords()); @@ -228,41 +358,70 @@ TEST(PerformanceObserver, PerformanceObserverTestMultiple) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer1 = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); auto observer2 = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); - observer1->observe(PerformanceEntryType::EVENT, {.durationThreshold = 50}); - observer2->observe(PerformanceEntryType::EVENT, {.durationThreshold = 80}); - - reporter->reportMeasure("measure", 0, 50); - reporter->reportEvent("event1", 0, 100, 0, 0, 0); - reporter->reportEvent("event2", 0, 40, 0, 0, 0); - reporter->reportMark("mark1", 100); - reporter->reportEvent("event3", 0, 60, 0, 0, 0); + observer1->observe( + PerformanceEntryType::EVENT, + {.durationThreshold = HighResDuration::fromMilliseconds(50)}); + observer2->observe( + PerformanceEntryType::EVENT, + {.durationThreshold = HighResDuration::fromMilliseconds(80)}); + + reporter->reportMeasure( + "measure", timeOrigin, HighResDuration::fromMilliseconds(50)); + reporter->reportEvent( + "event1", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "event2", + timeOrigin, + HighResDuration::fromMilliseconds(40), + timeOrigin, + timeOrigin, + 0); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(100)); + reporter->reportEvent( + "event3", + timeOrigin, + HighResDuration::fromMilliseconds(60), + timeOrigin, + timeOrigin, + 0); const std::vector expected1 = { - {.name = "event1", - .entryType = PerformanceEntryType::EVENT, - .duration = 100, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}, - {.name = "event3", - .entryType = PerformanceEntryType::EVENT, - .duration = 60, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}, + PerformanceEventTiming{ + {.name = "event1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "event3", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(60)}, + timeOrigin, + timeOrigin, + 0}, }; const std::vector expected2 = { - {.name = "event1", - .entryType = PerformanceEntryType::EVENT, - .duration = 100, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}}; + PerformanceEventTiming{ + {.name = "event1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, + }; ASSERT_EQ(expected1, observer1->takeRecords()); ASSERT_EQ(expected2, observer2->takeRecords()); diff --git a/packages/react-native/ReactCommon/react/renderer/animations/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/animations/CMakeLists.txt index d6f770ba21d2e6..a8ca0558579cda 100644 --- a/packages/react-native/ReactCommon/react/renderer/animations/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/animations/CMakeLists.txt @@ -35,3 +35,5 @@ target_link_libraries(react_render_animations runtimeexecutor yoga ) +target_compile_reactnative_options(react_renderer_animations PRIVATE) +target_compile_options(react_renderer_animations PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt index abf62846262ba2..f9b75d12898ac6 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/CMakeLists.txt @@ -33,3 +33,5 @@ target_link_libraries(react_render_attributedstring rrc_view yoga ) +target_compile_reactnative_options(react_renderer_attributedstring PRIVATE) +target_compile_options(react_renderer_attributedstring PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/componentregistry/CMakeLists.txt index ae69da2fe8ee8e..f02f6c582e8b10 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/CMakeLists.txt @@ -29,3 +29,5 @@ target_link_libraries(react_render_componentregistry react_utils rrc_legacyviewmanagerinterop ) +target_compile_reactnative_options(react_renderer_componentregistry PRIVATE) +target_compile_options(react_renderer_componentregistry PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/native/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/componentregistry/native/CMakeLists.txt index 54a70c545c07db..d315157ae21fe9 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/native/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/native/CMakeLists.txt @@ -29,3 +29,5 @@ target_link_libraries(rrc_native react_utils callinvoker ) +target_compile_reactnative_options(rrc_native PRIVATE) +target_compile_options(rrc_native PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/image/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/image/CMakeLists.txt index fcd235890e605d..ff2232f8b5e941 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/image/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/image/CMakeLists.txt @@ -34,3 +34,5 @@ target_link_libraries(rrc_image rrc_view yoga ) +target_compile_reactnative_options(rrc_image PRIVATE) +target_compile_options(rrc_image PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/CMakeLists.txt index c73062be9aea8e..a8cde70e88b5ae 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/legacyviewmanagerinterop/CMakeLists.txt @@ -28,3 +28,5 @@ target_link_libraries(rrc_legacyviewmanagerinterop rrc_view yoga ) +target_compile_reactnative_options(rrc_legacyviewmanagerinterop PRIVATE) +target_compile_options(rrc_legacyviewmanagerinterop PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt index a84728b6b2811a..561c1097a1e80d 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/modal/CMakeLists.txt @@ -37,3 +37,5 @@ target_link_libraries(rrc_modal rrc_view yoga ) +target_compile_reactnative_options(rrc_modal PRIVATE) +target_compile_options(rrc_modal PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/progressbar/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/progressbar/CMakeLists.txt index 37956a293fb1d6..cac8669857f0bb 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/progressbar/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/progressbar/CMakeLists.txt @@ -38,3 +38,5 @@ target_link_libraries(rrc_progressbar rrc_view yoga ) +target_compile_reactnative_options(rrc_progressbar PRIVATE) +target_compile_options(rrc_progressbar PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/root/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/root/CMakeLists.txt index 9b3d2e8daec978..b421946550c9fc 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/root/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/root/CMakeLists.txt @@ -30,3 +30,5 @@ target_link_libraries(rrc_root rrc_view yoga ) +target_compile_reactnative_options(rrc_root PRIVATE) +target_compile_options(rrc_root PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/safeareaview/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/CMakeLists.txt index 956fc3d5722dad..cc91423e9aee11 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/safeareaview/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/safeareaview/CMakeLists.txt @@ -34,13 +34,5 @@ target_link_libraries( yoga ) -target_compile_options( - rrc_safeareaview - PRIVATE - -DLOG_TAG=\"Fabric\" - -fexceptions - -frtti - -std=c++20 - -Wall - -Wpedantic -) +target_compile_reactnative_options(rrc_safeareaview PRIVATE) +target_compile_options(rrc_safeareaview PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/scrollview/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/scrollview/CMakeLists.txt index f15f1df8ebdbe5..13f0cc8716c94a 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/scrollview/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/scrollview/CMakeLists.txt @@ -32,3 +32,5 @@ target_link_libraries(rrc_scrollview rrc_view yoga ) +target_compile_reactnative_options(rrc_scrollview PRIVATE) +target_compile_options(rrc_scrollview PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/switch/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/switch/CMakeLists.txt index 422d3afe4954c0..fbebc3cf3b14ee 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/switch/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/switch/CMakeLists.txt @@ -33,13 +33,5 @@ target_link_libraries( yoga ) -target_compile_options( - rrc_switch - PRIVATE - -DLOG_TAG=\"Fabric\" - -fexceptions - -frtti - -std=c++20 - -Wall - -Wpedantic -) +target_compile_reactnative_options(rrc_switch PRIVATE) +target_compile_options(rrc_switch PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/text/CMakeLists.txt index 63f9905228c92f..d6a9c9f934d161 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/text/CMakeLists.txt @@ -36,3 +36,5 @@ target_link_libraries(rrc_text rrc_view yoga ) +target_compile_reactnative_options(rrc_text PRIVATE) +target_compile_options(rrc_text PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/textinput/CMakeLists.txt index 844239fe5cbbe1..09a945144198d4 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/CMakeLists.txt @@ -41,3 +41,5 @@ target_link_libraries(rrc_textinput rrc_view yoga ) +target_compile_reactnative_options(rrc_textinput PRIVATE) +target_compile_options(rrc_textinput PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/unimplementedview/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/unimplementedview/CMakeLists.txt index 1f6996eebfb482..a759f735dc4e33 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/unimplementedview/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/unimplementedview/CMakeLists.txt @@ -31,3 +31,5 @@ target_link_libraries(rrc_unimplementedview rrc_view yoga ) +target_compile_reactnative_options(rrc_unimplementedview PRIVATE) +target_compile_options(rrc_unimplementedview PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/components/view/CMakeLists.txt index 6198066ac449c8..13841e714e0d16 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/components/view/CMakeLists.txt @@ -37,3 +37,5 @@ target_link_libraries(rrc_view react_render_debug react_render_graphics yoga) +target_compile_reactnative_options(rrc_view PRIVATE) +target_compile_options(rrc_view PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/consistency/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/consistency/CMakeLists.txt index 4849958d1d1de7..373df16e407003 100644 --- a/packages/react-native/ReactCommon/react/renderer/consistency/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/consistency/CMakeLists.txt @@ -17,4 +17,6 @@ add_compile_options( file(GLOB react_render_consistency_SRC CONFIGURE_DEPENDS *.cpp) add_library(react_render_consistency OBJECT ${react_render_consistency_SRC}) -target_include_directories(react_render_consistency PUBLIC ${REACT_COMMON_DIR}) +target_include_directories(react_renderer_consistency PUBLIC ${REACT_COMMON_DIR}) +target_compile_reactnative_options(react_renderer_consistency PRIVATE) +target_compile_options(react_renderer_consistency PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/core/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/core/CMakeLists.txt index 8760857f68f0b6..0af669b0bfaa5c 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/core/CMakeLists.txt @@ -31,3 +31,5 @@ target_link_libraries(react_render_core react_render_runtimescheduler react_utils runtimeexecutor) +target_compile_reactnative_options(react_renderer_core PRIVATE) +target_compile_options(react_renderer_core PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h b/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h index 86fdc36297a756..14d4461be36351 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h @@ -32,8 +32,7 @@ class EventLogger { virtual EventTag onEventStart( std::string_view name, SharedEventTarget target, - DOMHighResTimeStamp eventStartTimeStamp = - DOM_HIGH_RES_TIME_STAMP_UNSET) = 0; + std::optional eventStartTimeStamp = std::nullopt) = 0; /* * Called when event starts getting dispatched (processed by the handlers, if diff --git a/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h b/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h index c79aca59aa9920..38282620f6933b 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h +++ b/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h @@ -74,7 +74,7 @@ struct RawEvent { // The client may specify a platform-specific timestamp for the event start // time, for example when MotionEvent was triggered on the Android native // side. - DOMHighResTimeStamp eventStartTimeStamp{DOM_HIGH_RES_TIME_STAMP_UNSET}; + std::optional eventStartTimeStamp = std::nullopt; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp index e841ab161e9a14..1e5ff3a5d5f72c 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp @@ -24,7 +24,7 @@ class MockEventLogger : public EventLogger { EventTag onEventStart( std::string_view /*name*/, SharedEventTarget /*target*/, - DOMHighResTimeStamp /*eventStartTimeStamp*/) override { + std::optional /*eventStartTimeStamp*/) override { return EMPTY_EVENT_TAG; } void onEventProcessingStart(EventTag /*tag*/) override {} diff --git a/packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt new file mode 100644 index 00000000000000..37f588e6bff93a --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) + +file(GLOB react_renderer_css_SRC CONFIGURE_DEPENDS *.cpp) + +# We need to create library as INTERFACE if it is header only +if("${react_renderer_css_SRC}" STREQUAL "") + add_library(react_renderer_css INTERFACE) + + target_include_directories(react_renderer_css INTERFACE ${REACT_COMMON_DIR}) + target_link_libraries(react_renderer_css INTERFACE + fast_float + glog + react_debug + react_utils) + target_compile_reactnative_options(react_renderer_css INTERFACE) + target_compile_options(react_renderer_css INTERFACE -Wpedantic) +else() + add_library(react_renderer_css OBJECT ${react_renderer_css_SRC}) + + target_include_directories(react_renderer_css PUBLIC ${REACT_COMMON_DIR}) + target_link_libraries(react_renderer_css + fast_float + glog + react_debug + react_utils) + target_compile_reactnative_options(react_renderer_css PRIVATE) + target_compile_options(react_renderer_css PRIVATE -Wpedantic) +endif() diff --git a/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt index d9c4adecd08108..bc2c2c08e5fc31 100644 --- a/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt @@ -17,5 +17,7 @@ add_compile_options( file(GLOB react_render_debug_SRC CONFIGURE_DEPENDS *.cpp) add_library(react_render_debug OBJECT ${react_render_debug_SRC}) -target_include_directories(react_render_debug PUBLIC ${REACT_COMMON_DIR}) -target_link_libraries(react_render_debug folly_runtime react_debug) +target_include_directories(react_renderer_debug PUBLIC ${REACT_COMMON_DIR}) +target_link_libraries(react_renderer_debug folly_runtime react_debug) +target_compile_reactnative_options(react_renderer_debug PRIVATE) +target_compile_options(react_renderer_debug PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/dom/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/dom/CMakeLists.txt index ea54cf6a02b21a..f1cef92cf1e4e4 100644 --- a/packages/react-native/ReactCommon/react/renderer/dom/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/dom/CMakeLists.txt @@ -24,3 +24,5 @@ target_link_libraries(react_render_dom react_render_graphics rrc_root rrc_text) +target_compile_reactnative_options(react_renderer_dom PRIVATE) +target_compile_options(react_renderer_dom PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/element/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/element/CMakeLists.txt index 5a81468210536d..2e3063d9744e00 100644 --- a/packages/react-native/ReactCommon/react/renderer/element/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/element/CMakeLists.txt @@ -25,3 +25,5 @@ target_link_libraries(react_render_element react_render_core react_render_componentregistry ) +target_compile_reactnative_options(react_renderer_element PRIVATE) +target_compile_options(react_renderer_element PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt index a1b924706877cc..a9f99635d7b9c8 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/graphics/CMakeLists.txt @@ -30,3 +30,5 @@ target_link_libraries(react_render_graphics react_debug react_utils ) +target_compile_reactnative_options(react_renderer_graphics PRIVATE) +target_compile_options(react_renderer_graphics PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/imagemanager/CMakeLists.txt index f7aa941094de95..73cea4cd5c5a02 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/CMakeLists.txt @@ -40,3 +40,5 @@ target_link_libraries(react_render_imagemanager react_render_mounting reactnativejni yoga) +target_compile_reactnative_options(react_renderer_imagemanager PRIVATE) +target_compile_options(react_renderer_imagemanager PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/leakchecker/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/leakchecker/CMakeLists.txt index 4f9dde26baaa6e..c855d1a1793285 100644 --- a/packages/react-native/ReactCommon/react/renderer/leakchecker/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/leakchecker/CMakeLists.txt @@ -22,3 +22,5 @@ target_link_libraries(react_render_leakchecker glog react_render_core runtimeexecutor) +target_compile_reactnative_options(react_renderer_leakchecker PRIVATE) +target_compile_options(react_renderer_leakchecker PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/mapbuffer/CMakeLists.txt index 14a98e2783463a..f96c0a173782c9 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/CMakeLists.txt @@ -14,8 +14,7 @@ add_compile_options( -Wpedantic -DLOG_TAG=\"Fabric\") -file(GLOB react_render_mapbuffer_SRC CONFIGURE_DEPENDS *.cpp) -add_library(react_render_mapbuffer OBJECT ${react_render_mapbuffer_SRC}) - -target_include_directories(react_render_mapbuffer PUBLIC ${REACT_COMMON_DIR}) -target_link_libraries(react_render_mapbuffer glog glog_init react_debug) +target_include_directories(react_renderer_mapbuffer PUBLIC ${REACT_COMMON_DIR}) +target_link_libraries(react_renderer_mapbuffer glog glog_init react_debug) +target_compile_reactnative_options(react_renderer_mapbuffer PRIVATE) +target_compile_options(react_renderer_mapbuffer PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt index 6bbc525ae18508..7cd01de16b6148 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt @@ -36,3 +36,5 @@ target_link_libraries(react_render_mounting rrc_root rrc_view yoga) +target_compile_reactnative_options(react_renderer_mounting PRIVATE) +target_compile_options(react_renderer_mounting PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/observers/events/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/observers/events/CMakeLists.txt index f089774799922c..b151f8ab8c2f8a 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/events/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/observers/events/CMakeLists.txt @@ -26,3 +26,5 @@ target_link_libraries(react_render_observers_events react_featureflags react_render_uimanager react_utils) +target_compile_reactnative_options(react_renderer_observers_events PRIVATE) +target_compile_options(react_renderer_observers_events PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp index 73184777507880..8ed6bc1f6d7a2e 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp @@ -103,7 +103,7 @@ EventPerformanceLogger::EventPerformanceLogger( EventTag EventPerformanceLogger::onEventStart( std::string_view name, SharedEventTarget target, - DOMHighResTimeStamp eventStartTimeStamp) { + std::optional eventStartTimeStamp) { auto performanceEntryReporter = performanceEntryReporter_.lock(); if (performanceEntryReporter == nullptr) { return EMPTY_EVENT_TAG; @@ -121,9 +121,9 @@ EventTag EventPerformanceLogger::onEventStart( // The event start timestamp may be provided by the caller in order to // specify the platform specific event start time. - auto timeStamp = eventStartTimeStamp == DOM_HIGH_RES_TIME_STAMP_UNSET - ? performanceEntryReporter->getCurrentTimeStamp() - : eventStartTimeStamp; + HighResTimeStamp timeStamp = eventStartTimeStamp + ? *eventStartTimeStamp + : performanceEntryReporter->getCurrentTimeStamp(); { std::lock_guard lock(eventsInFlightMutex_); eventsInFlight_.emplace( @@ -223,11 +223,7 @@ void EventPerformanceLogger::dispatchPendingEventTimingEntries( void EventPerformanceLogger::shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept { - if (!ReactNativeFeatureFlags::enableReportEventPaintTime()) { - return; - } - + HighResTimeStamp mountTime) noexcept { auto performanceEntryReporter = performanceEntryReporter_.lock(); if (performanceEntryReporter == nullptr) { return; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h index b878459f270a50..6e8c468b90c577 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h @@ -30,8 +30,8 @@ class EventPerformanceLogger : public EventLogger, EventTag onEventStart( std::string_view name, SharedEventTarget target, - DOMHighResTimeStamp eventStartTimeStamp = - DOM_HIGH_RES_TIME_STAMP_UNSET) override; + std::optional eventStartTimeStamp = + std::nullopt) override; void onEventProcessingStart(EventTag tag) override; void onEventProcessingEnd(EventTag tag) override; @@ -45,15 +45,15 @@ class EventPerformanceLogger : public EventLogger, void shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept override; + HighResTimeStamp mountTime) noexcept override; private: struct EventEntry { std::string_view name; SharedEventTarget target{nullptr}; - DOMHighResTimeStamp startTime{0.0}; - DOMHighResTimeStamp processingStartTime{0.0}; - DOMHighResTimeStamp processingEndTime{0.0}; + HighResTimeStamp startTime; + std::optional processingStartTime; + std::optional processingEndTime; bool isWaitingForMount{false}; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp index 2589ee749abb1b..4b63aab7ad70a6 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp @@ -104,7 +104,7 @@ static Float getHighestThresholdCrossed( std::optional IntersectionObserver::updateIntersectionObservation( const RootShadowNode& rootShadowNode, - double time) { + HighResTimeStamp time) { const auto layoutableRootShadowNode = dynamic_cast(&rootShadowNode); @@ -170,7 +170,7 @@ IntersectionObserver::updateIntersectionObservation( std::optional IntersectionObserver::updateIntersectionObservationForSurfaceUnmount( - double time) { + HighResTimeStamp time) { return setNotIntersectingState(Rect{}, Rect{}, Rect{}, time); } @@ -181,7 +181,7 @@ IntersectionObserver::setIntersectingState( const Rect& intersectionRect, Float threshold, Float rootThreshold, - double time) { + HighResTimeStamp time) { auto newState = IntersectionObserverState::Intersecting(threshold, rootThreshold); @@ -207,7 +207,7 @@ IntersectionObserver::setNotIntersectingState( const Rect& rootBoundingRect, const Rect& targetBoundingRect, const Rect& intersectionRect, - double time) { + HighResTimeStamp time) { if (state_ != IntersectionObserverState::NotIntersecting()) { state_ = IntersectionObserverState::NotIntersecting(); IntersectionObserverEntry entry{ diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h index ba6a4449b366a0..2aa7963aff7592 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h @@ -25,9 +25,13 @@ struct IntersectionObserverEntry { Rect rootRect; Rect intersectionRect; bool isIntersectingAboveThresholds; - // TODO(T156529385) Define `DOMHighResTimeStamp` as an alias for `double` and - // use it here. - double time; + HighResTimeStamp time; + + bool sameShadowNodeFamily( + const ShadowNodeFamily& otherShadowNodeFamily) const { + return std::addressof(*shadowNodeFamily) == + std::addressof(otherShadowNodeFamily); + } }; class IntersectionObserver { @@ -42,10 +46,10 @@ class IntersectionObserver { // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo std::optional updateIntersectionObservation( const RootShadowNode& rootShadowNode, - double time); + HighResTimeStamp time); std::optional - updateIntersectionObservationForSurfaceUnmount(double time); + updateIntersectionObservationForSurfaceUnmount(HighResTimeStamp time); IntersectionObserverObserverId getIntersectionObserverId() const { return intersectionObserverId_; @@ -66,13 +70,13 @@ class IntersectionObserver { const Rect& intersectionRect, Float threshold, Float rootThreshold, - double time); + HighResTimeStamp time); std::optional setNotIntersectingState( const Rect& rootBoundingRect, const Rect& targetBoundingRect, const Rect& intersectionRect, - double time); + HighResTimeStamp time); IntersectionObserverObserverId intersectionObserverId_; ShadowNode::Shared targetShadowNode_; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp index 7fa55a5a470f18..861a1459fa11b1 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp @@ -168,14 +168,14 @@ IntersectionObserverManager::takeRecords() { void IntersectionObserverManager::shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double time) noexcept { + HighResTimeStamp time) noexcept { updateIntersectionObservations( rootShadowNode->getSurfaceId(), rootShadowNode.get(), time); } void IntersectionObserverManager::shadowTreeDidUnmount( SurfaceId surfaceId, - double time) noexcept { + HighResTimeStamp time) noexcept { updateIntersectionObservations(surfaceId, nullptr, time); } @@ -184,7 +184,7 @@ void IntersectionObserverManager::shadowTreeDidUnmount( void IntersectionObserverManager::updateIntersectionObservations( SurfaceId surfaceId, const RootShadowNode* rootShadowNode, - double time) { + HighResTimeStamp time) { TraceSection s("IntersectionObserverManager::updateIntersectionObservations"); std::vector entries; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h index 93ae10dd1b2ab0..bc4a0751a75470 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h @@ -43,9 +43,10 @@ class IntersectionObserverManager final : public UIManagerMountHook { void shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double time) noexcept override; + HighResTimeStamp time) noexcept override; - void shadowTreeDidUnmount(SurfaceId surfaceId, double time) noexcept override; + void shadowTreeDidUnmount(SurfaceId surfaceId, HighResTimeStamp time) noexcept + override; private: mutable std::unordered_map> @@ -68,7 +69,7 @@ class IntersectionObserverManager final : public UIManagerMountHook { void updateIntersectionObservations( SurfaceId surfaceId, const RootShadowNode* rootShadowNode, - double time); + HighResTimeStamp time); const IntersectionObserver& getRegisteredIntersectionObserver( SurfaceId surfaceId, diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt index ec62d4df3caaa9..550acb8cb2f1ed 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt @@ -29,4 +29,7 @@ target_link_libraries(react_render_runtimescheduler react_timing react_utils react_featureflags - runtimeexecutor) + runtimeexecutor + jsinspector_tracing) +target_compile_reactnative_options(react_renderer_runtimescheduler PRIVATE) +target_compile_options(react_renderer_runtimescheduler PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp index 8da2c4d1a87713..8c2a1c7a20fd3f 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp @@ -444,12 +444,10 @@ void RuntimeScheduler_Modern::reportLongTasks( return; } - auto checkedDurationMs = - chronoToDOMHighResTimeStamp(longestPeriodWithoutYieldingOpportunity_); - if (checkedDurationMs >= LONG_TASK_DURATION_THRESHOLD_MS) { - auto durationMs = chronoToDOMHighResTimeStamp(endTime - startTime); - auto startTimeMs = chronoToDOMHighResTimeStamp(startTime); - reporter->reportLongTask(startTimeMs, durationMs); + if (longestPeriodWithoutYieldingOpportunity_ >= + LONG_TASK_DURATION_THRESHOLD) { + auto duration = endTime - startTime; + reporter->reportLongTask(startTime, duration); } } diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp index bc8be4d44e30ee..def12cd70fd1ea 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp @@ -12,8 +12,10 @@ #include #include #include +#include #include #include +#include #include "StubClock.h" #include "StubErrorUtils.h" @@ -1246,9 +1248,16 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasks) { EXPECT_EQ(stubQueue_->size(), 0); pendingEntries = performanceEntryReporter_->getEntries(); EXPECT_EQ(pendingEntries.size(), 1); - EXPECT_EQ(pendingEntries[0].entryType, PerformanceEntryType::LONGTASK); - EXPECT_EQ(pendingEntries[0].startTime, 100); - EXPECT_EQ(pendingEntries[0].duration, 50); + auto entry = pendingEntries[0]; + std::visit( + [](const auto& entryDetails) { + EXPECT_EQ(entryDetails.entryType, PerformanceEntryType::LONGTASK); + EXPECT_EQ( + entryDetails.startTime.toDOMHighResTimeStamp(), + startTime.toDOMHighResTimeStamp() + 100); + EXPECT_EQ(entryDetails.duration, HighResDuration::fromMilliseconds(50)); + }, + entry); } TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { @@ -1326,9 +1335,17 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { EXPECT_EQ(stubQueue_->size(), 0); pendingEntries = performanceEntryReporter_->getEntries(); EXPECT_EQ(pendingEntries.size(), 1); - EXPECT_EQ(pendingEntries[0].entryType, PerformanceEntryType::LONGTASK); - EXPECT_EQ(pendingEntries[0].startTime, 100); - EXPECT_EQ(pendingEntries[0].duration, 120); + auto entry = pendingEntries[0]; + std::visit( + [](const auto& entryDetails) { + EXPECT_EQ(entryDetails.entryType, PerformanceEntryType::LONGTASK); + EXPECT_EQ( + entryDetails.startTime.toDOMHighResTimeStamp(), + startTime.toDOMHighResTimeStamp() + 100); + EXPECT_EQ( + entryDetails.duration, HighResDuration::fromMilliseconds(120)); + }, + entry); } INSTANTIATE_TEST_SUITE_P( diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt index 0f68c342b22cd4..24241fb4a88a19 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt @@ -39,3 +39,5 @@ target_link_libraries(react_render_scheduler rrc_view yoga ) +target_compile_reactnative_options(react_renderer_scheduler PRIVATE) +target_compile_options(react_renderer_scheduler PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/telemetry/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/telemetry/CMakeLists.txt index 4335e99fd6715b..e0c60adf850653 100644 --- a/packages/react-native/ReactCommon/react/renderer/telemetry/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/telemetry/CMakeLists.txt @@ -30,3 +30,5 @@ target_link_libraries(react_render_telemetry rrc_root rrc_view yoga) +target_compile_reactnative_options(react_renderer_telemetry PRIVATE) +target_compile_options(react_renderer_telemetry PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/CMakeLists.txt index 8edcc165b48b8f..0a3c9249fdaade 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/CMakeLists.txt @@ -47,3 +47,5 @@ target_link_libraries(react_render_textlayoutmanager reactnativejni yoga ) +target_compile_reactnative_options(react_renderer_textlayoutmanager PRIVATE) +target_compile_options(react_renderer_textlayoutmanager PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt index 28198bb9af870b..bf2a3900bc47be 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt @@ -41,3 +41,6 @@ target_link_libraries(react_render_uimanager rrc_view runtimeexecutor ) +target_compile_reactnative_options(react_renderer_uimanager PRIVATE) +target_compile_options(react_renderer_uimanager PRIVATE -Wno-unused-local-typedef) +target_compile_options(react_renderer_uimanager PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h index 3e5627b53eb15f..1a014411d85973 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h @@ -26,11 +26,11 @@ class UIManagerMountHook { */ virtual void shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept = 0; + HighResTimeStamp mountTime) noexcept = 0; virtual void shadowTreeDidUnmount( SurfaceId /*surfaceId*/, - double /*unmountTime*/) noexcept { + HighResTimeStamp /*unmountTime*/) noexcept { // Default no-op implementation for backwards compatibility. } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/CMakeLists.txt index c57115ec42b9ab..42bfe24caf1ba3 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/CMakeLists.txt @@ -22,5 +22,7 @@ target_include_directories(react_render_uimanager_consistency PUBLIC ${REACT_COM target_link_libraries(react_render_uimanager_consistency glog rrc_root - react_render_consistency - react_render_mounting) + react_renderer_consistency + react_renderer_mounting) +target_compile_reactnative_options(react_renderer_uimanager_consistency PRIVATE) +target_compile_options(react_renderer_uimanager_consistency PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt index 91ef2b5bc13df4..dd9a9f7135d707 100644 --- a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt @@ -25,6 +25,7 @@ target_link_libraries(bridgelesshermes reactnative ) +target_compile_reactnative_options(bridgelesshermes PRIVATE) if(${CMAKE_BUILD_TYPE} MATCHES Debug) target_compile_options( bridgelesshermes diff --git a/packages/react-native/ReactCommon/react/timing/primitives.h b/packages/react-native/ReactCommon/react/timing/primitives.h index c6b3c304635cdc..9c3be8990b8af5 100644 --- a/packages/react-native/ReactCommon/react/timing/primitives.h +++ b/packages/react-native/ReactCommon/react/timing/primitives.h @@ -11,23 +11,288 @@ namespace facebook::react { -// `DOMHighResTimeStamp` represents a time value in milliseconds (time point or -// duration), with sub-millisecond precision. -// On the Web, the precision can be reduced for security purposes, but that is -// not necessary in React Native. -using DOMHighResTimeStamp = double; - -constexpr DOMHighResTimeStamp DOM_HIGH_RES_TIME_STAMP_UNSET = -1.0; - -inline DOMHighResTimeStamp chronoToDOMHighResTimeStamp( - std::chrono::steady_clock::duration duration) { - return static_cast>(duration) - .count(); +class HighResDuration; +class HighResTimeStamp; + +/* + * A class representing a duration of time with high precision. + * + * @see __docs__/README.md for more information. + */ +class HighResDuration { + friend class HighResTimeStamp; + friend constexpr HighResDuration operator-( + const HighResTimeStamp& lhs, + const HighResTimeStamp& rhs); + friend constexpr HighResTimeStamp operator+( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + friend constexpr HighResTimeStamp operator-( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + + public: + constexpr HighResDuration() + : chronoDuration_(std::chrono::steady_clock::duration()) {} + + static constexpr HighResDuration zero() { + return HighResDuration(std::chrono::steady_clock::duration::zero()); + } + + static constexpr HighResDuration fromChrono( + std::chrono::steady_clock::duration chronoDuration) { + return HighResDuration(chronoDuration); + } + + static constexpr HighResDuration fromNanoseconds(int64_t units) { + return HighResDuration(std::chrono::nanoseconds(units)); + } + + static constexpr HighResDuration fromMilliseconds(int64_t units) { + return HighResDuration(std::chrono::milliseconds(units)); + } + + constexpr int64_t toNanoseconds() const { + return std::chrono::duration_cast(chronoDuration_) + .count(); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + static constexpr HighResDuration fromDOMHighResTimeStamp(double units) { + auto nanoseconds = static_cast(units * 1e6); + return fromNanoseconds(nanoseconds); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + constexpr double toDOMHighResTimeStamp() const { + return static_cast>( + chronoDuration_) + .count(); + } + + constexpr HighResDuration operator+(const HighResDuration& rhs) const { + return HighResDuration(chronoDuration_ + rhs.chronoDuration_); + } + + constexpr HighResDuration operator+( + const std::chrono::steady_clock::duration& rhs) const { + return HighResDuration(chronoDuration_ + rhs); + } + + constexpr HighResDuration operator-(const HighResDuration& rhs) const { + return HighResDuration(chronoDuration_ - rhs.chronoDuration_); + } + + constexpr HighResDuration operator-( + const std::chrono::steady_clock::duration& rhs) const { + return HighResDuration(chronoDuration_ - rhs); + } + + constexpr HighResDuration& operator+=(const HighResDuration& rhs) { + chronoDuration_ += rhs.chronoDuration_; + return *this; + } + + constexpr HighResDuration& operator+=( + const std::chrono::steady_clock::duration& rhs) { + chronoDuration_ += rhs; + return *this; + } + + constexpr HighResDuration& operator-=(const HighResDuration& rhs) { + chronoDuration_ -= rhs.chronoDuration_; + return *this; + } + + constexpr HighResDuration& operator-=( + const std::chrono::steady_clock::duration& rhs) { + chronoDuration_ -= rhs; + return *this; + } + + constexpr bool operator==(const HighResDuration& rhs) const { + return chronoDuration_ == rhs.chronoDuration_; + } + + constexpr bool operator==( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ == rhs; + } + + constexpr bool operator!=(const HighResDuration& rhs) const { + return chronoDuration_ != rhs.chronoDuration_; + } + + constexpr bool operator!=( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ != rhs; + } + + constexpr bool operator<(const HighResDuration& rhs) const { + return chronoDuration_ < rhs.chronoDuration_; + } + + constexpr bool operator<( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ < rhs; + } + + constexpr bool operator<=(const HighResDuration& rhs) const { + return chronoDuration_ <= rhs.chronoDuration_; + } + + constexpr bool operator<=( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ <= rhs; + } + + constexpr bool operator>(const HighResDuration& rhs) const { + return chronoDuration_ > rhs.chronoDuration_; + } + + constexpr bool operator>( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ > rhs; + } + + constexpr bool operator>=(const HighResDuration& rhs) const { + return chronoDuration_ >= rhs.chronoDuration_; + } + + constexpr bool operator>=( + const std::chrono::steady_clock::duration& rhs) const { + return chronoDuration_ >= rhs; + } + + constexpr operator std::chrono::steady_clock::duration() const { + return chronoDuration_; + } + + private: + explicit constexpr HighResDuration( + std::chrono::steady_clock::duration chronoDuration) + : chronoDuration_(chronoDuration) {} + + std::chrono::steady_clock::duration chronoDuration_; +}; + +/* + * A class representing a specific point in time with high precision. + * + * @see __docs__/README.md for more information. + */ +class HighResTimeStamp { + friend constexpr HighResDuration operator-( + const HighResTimeStamp& lhs, + const HighResTimeStamp& rhs); + friend constexpr HighResTimeStamp operator+( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + friend constexpr HighResTimeStamp operator-( + const HighResTimeStamp& lhs, + const HighResDuration& rhs); + + public: + HighResTimeStamp() noexcept + : chronoTimePoint_(std::chrono::steady_clock::now()) {} + + static constexpr HighResTimeStamp now() noexcept { + return HighResTimeStamp(std::chrono::steady_clock::now()); + } + + static constexpr HighResTimeStamp min() noexcept { + return HighResTimeStamp(std::chrono::steady_clock::time_point::min()); + } + + static constexpr HighResTimeStamp max() noexcept { + return HighResTimeStamp(std::chrono::steady_clock::time_point::max()); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + static constexpr HighResTimeStamp fromDOMHighResTimeStamp(double units) { + auto nanoseconds = static_cast(units * 1e6); + return HighResTimeStamp(std::chrono::steady_clock::time_point( + std::chrono::nanoseconds(nanoseconds))); + } + + // @see https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp + constexpr double toDOMHighResTimeStamp() const { + return HighResDuration(chronoTimePoint_.time_since_epoch()) + .toDOMHighResTimeStamp(); + } + + // This method is expected to be used only when converting time stamps from + // external systems. + static constexpr HighResTimeStamp fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point chronoTimePoint) { + return HighResTimeStamp(chronoTimePoint); + } + + // This method is provided for convenience, if you need to convert + // HighResTimeStamp to some common epoch with time stamps from other sources. + constexpr std::chrono::steady_clock::time_point toChronoSteadyClockTimePoint() + const { + return chronoTimePoint_; + } + + constexpr bool operator==(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ == rhs.chronoTimePoint_; + } + + constexpr bool operator!=(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ != rhs.chronoTimePoint_; + } + + constexpr bool operator<(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ < rhs.chronoTimePoint_; + } + + constexpr bool operator<=(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ <= rhs.chronoTimePoint_; + } + + constexpr bool operator>(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ > rhs.chronoTimePoint_; + } + + constexpr bool operator>=(const HighResTimeStamp& rhs) const { + return chronoTimePoint_ >= rhs.chronoTimePoint_; + } + + constexpr HighResTimeStamp& operator+=(const HighResDuration& rhs) { + chronoTimePoint_ += rhs.chronoDuration_; + return *this; + } + + constexpr HighResTimeStamp& operator-=(const HighResDuration& rhs) { + chronoTimePoint_ -= rhs.chronoDuration_; + return *this; + } + + private: + explicit constexpr HighResTimeStamp( + std::chrono::steady_clock::time_point chronoTimePoint) + : chronoTimePoint_(chronoTimePoint) {} + + std::chrono::steady_clock::time_point chronoTimePoint_; +}; + +inline constexpr HighResDuration operator-( + const HighResTimeStamp& lhs, + const HighResTimeStamp& rhs) { + return HighResDuration(lhs.chronoTimePoint_ - rhs.chronoTimePoint_); +} + +inline constexpr HighResTimeStamp operator+( + const HighResTimeStamp& lhs, + const HighResDuration& rhs) { + return HighResTimeStamp(lhs.chronoTimePoint_ + rhs.chronoDuration_); } -inline DOMHighResTimeStamp chronoToDOMHighResTimeStamp( - std::chrono::steady_clock::time_point timePoint) { - return chronoToDOMHighResTimeStamp(timePoint.time_since_epoch()); +inline constexpr HighResTimeStamp operator-( + const HighResTimeStamp& lhs, + const HighResDuration& rhs) { + return HighResTimeStamp(lhs.chronoTimePoint_ - rhs.chronoDuration_); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp b/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp index 80452a2f713d37..f94a379c036d84 100644 --- a/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp +++ b/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp @@ -11,37 +11,95 @@ namespace facebook::react { -using Clock = std::chrono::steady_clock; -using TimePoint = std::chrono::time_point; - -TEST(chronoToDOMHighResTimeStamp, withDurations) { - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::nanoseconds(10)), 0.00001); - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::microseconds(10)), 0.01); - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::milliseconds(10)), 10.0); - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::seconds(10)), 10000.0); +TEST(HighResDuration, CorrectlyConvertsToDOMHighResTimeStamp) { EXPECT_EQ( - chronoToDOMHighResTimeStamp( - std::chrono::seconds(1) + std::chrono::nanoseconds(20)), - 1000.000020); -} - -TEST(chronoToDOMHighResTimeStamp, withTimePoints) { - EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::nanoseconds(10))), - 0.00001); + HighResDuration::fromNanoseconds(10).toDOMHighResTimeStamp(), 0.00001); EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::microseconds(10))), - 0.01); + HighResDuration::fromNanoseconds(10 * 1e3).toDOMHighResTimeStamp(), 0.01); EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::milliseconds(10))), - 10.0); + HighResDuration::fromNanoseconds(10 * 1e6).toDOMHighResTimeStamp(), 10.0); EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::seconds(10))), + HighResDuration::fromNanoseconds(10 * 1e9).toDOMHighResTimeStamp(), 10000.0); EXPECT_EQ( - chronoToDOMHighResTimeStamp( - TimePoint(std::chrono::seconds(1) + std::chrono::nanoseconds(20))), + HighResDuration::fromNanoseconds(1e9 + 20).toDOMHighResTimeStamp(), 1000.000020); + + EXPECT_EQ(HighResDuration::fromMilliseconds(0).toDOMHighResTimeStamp(), 0); + EXPECT_EQ( + HighResDuration::fromMilliseconds(10).toDOMHighResTimeStamp(), 10.0); +} + +TEST(HighResDuration, ComparisonOperators) { + auto duration1 = HighResDuration::fromNanoseconds(10); + auto duration2 = HighResDuration::fromNanoseconds(20); + auto duration3 = HighResDuration::fromNanoseconds(10); + + EXPECT_TRUE(duration1 == duration3); + EXPECT_FALSE(duration1 == duration2); + + EXPECT_TRUE(duration1 != duration2); + EXPECT_FALSE(duration1 != duration3); + + EXPECT_TRUE(duration1 < duration2); + EXPECT_FALSE(duration2 < duration1); + EXPECT_FALSE(duration1 < duration3); + + EXPECT_TRUE(duration1 <= duration2); + EXPECT_TRUE(duration1 <= duration3); + EXPECT_FALSE(duration2 <= duration1); + + EXPECT_TRUE(duration2 > duration1); + EXPECT_FALSE(duration1 > duration2); + EXPECT_FALSE(duration1 > duration3); + + EXPECT_TRUE(duration2 >= duration1); + EXPECT_TRUE(duration1 >= duration3); + EXPECT_FALSE(duration1 >= duration2); +} + +TEST(HighResDuration, ArithmeticOperators) { + auto duration1 = HighResDuration::fromChrono(std::chrono::nanoseconds(100)); + auto duration2 = HighResDuration::fromChrono(std::chrono::nanoseconds(50)); + + EXPECT_EQ(duration1 + duration2, std::chrono::nanoseconds(150)); + EXPECT_EQ(duration1 - duration2, std::chrono::nanoseconds(50)); + EXPECT_EQ(duration2 - duration1, std::chrono::nanoseconds(-50)); +} + +TEST(HighResTimeStamp, ComparisonOperators) { + auto now = HighResTimeStamp::now(); + auto later = now + HighResDuration::fromNanoseconds(1); + auto nowCopy = now; + + EXPECT_TRUE(now == nowCopy); + EXPECT_FALSE(now == later); + + EXPECT_TRUE(now != later); + EXPECT_FALSE(now != nowCopy); + + EXPECT_TRUE(now < later); + EXPECT_FALSE(later < now); + EXPECT_FALSE(now < nowCopy); + + EXPECT_TRUE(now <= later); + EXPECT_TRUE(now <= nowCopy); + EXPECT_FALSE(later <= now); + + EXPECT_TRUE(later > now); + EXPECT_FALSE(now > later); + EXPECT_FALSE(now > nowCopy); + + EXPECT_TRUE(later >= now); + EXPECT_TRUE(now >= nowCopy); + EXPECT_FALSE(now >= later); +} + +TEST(HighResTimeStamp, SteadyClockTimePointConversion) { + [[maybe_unused]] auto timestamp = + HighResTimeStamp::now().toChronoSteadyClockTimePoint(); + + EXPECT_TRUE(decltype(timestamp)::clock::is_steady); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/utils/CMakeLists.txt b/packages/react-native/ReactCommon/react/utils/CMakeLists.txt index 5ac2f3c51bc878..fd4c0efaa620d9 100644 --- a/packages/react-native/ReactCommon/react/utils/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/utils/CMakeLists.txt @@ -24,3 +24,5 @@ target_link_libraries(react_utils glog_init jsireact react_debug) +target_compile_reactnative_options(react_utils PRIVATE) +target_compile_options(react_utils PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp index a1b245772fc5ac..6cce4ddd9adab5 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp @@ -111,7 +111,7 @@ void HermesPerfettoDataSource::OnStart(const StartArgs&) { "react-native", perfetto::DynamicString{"Profiling Started"}, getPerfettoWebPerfTrackSync("JS Sampling"), - performanceNowToPerfettoTraceTime(0)); + perfetto::TrackEvent::GetTraceTimeNs()); } void HermesPerfettoDataSource::OnFlush(const FlushArgs&) { diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp index a0786d4c649df4..c280ad4ca08a2d 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp @@ -80,13 +80,17 @@ perfetto::Track getPerfettoWebPerfTrackAsync(const std::string& trackName) { } // Perfetto's monotonic clock seems to match the std::chrono::steady_clock we -// use in JSExecutor::performanceNow on Android platforms, but if that +// use in HighResTimeStamp on Android platforms, but if that // assumption is incorrect we may need to manually offset perfetto timestamps. -uint64_t performanceNowToPerfettoTraceTime(double perfNowTime) { - if (perfNowTime == 0) { - return perfetto::TrackEvent::GetTraceTimeNs(); - } - return static_cast(perfNowTime * 1.e6); +uint64_t highResTimeStampToPerfettoTraceTime(HighResTimeStamp timestamp) { + auto chronoDurationSinceSteadyClockEpoch = + timestamp.toChronoSteadyClockTimePoint().time_since_epoch(); + auto nanoseconds = std::chrono::duration_cast( + chronoDurationSinceSteadyClockEpoch); + + return std::chrono::duration_cast>( + nanoseconds) + .count(); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h index c01ac604c89fc1..c46ff051c53654 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h @@ -10,6 +10,7 @@ #ifdef WITH_PERFETTO #include +#include #include #include @@ -20,7 +21,7 @@ void initializePerfetto(); perfetto::Track getPerfettoWebPerfTrackSync(const std::string& trackName); perfetto::Track getPerfettoWebPerfTrackAsync(const std::string& trackName); -uint64_t performanceNowToPerfettoTraceTime(double perfNowTime); +uint64_t highResTimeStampToPerfettoTraceTime(HighResTimeStamp timestamp); } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp index 3e8e50b320bad1..df907c0e20507b 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp @@ -25,14 +25,28 @@ std::string toPerfettoTrackName( ? PERFETTO_TRACK_NAME_PREFIX + std::string(trackName.value()) : PERFETTO_DEFAULT_TRACK_NAME; } +#elif defined(WITH_FBSYSTRACE) +int64_t getDeltaNanos(HighResTimeStamp jsTime) { + auto now = HighResTimeStamp::now(); + return (jsTime - now).toNanoseconds(); +} +#endif } // namespace #endif +/* static */ bool ReactPerfettoLogger::isTracing() { +#ifdef WITH_PERFETTO + return TRACE_EVENT_CATEGORY_ENABLED("react-native"); +#else + return false; +#endif +} + /* static */ void ReactPerfettoLogger::measure( const std::string_view& eventName, - double startTime, - double endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackName) { #ifdef WITH_PERFETTO if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) { @@ -41,16 +55,16 @@ std::string toPerfettoTrackName( "react-native", perfetto::DynamicString(eventName.data(), eventName.size()), track, - performanceNowToPerfettoTraceTime(startTime)); + highResTimeStampToPerfettoTraceTime(startTime)); TRACE_EVENT_END( - "react-native", track, performanceNowToPerfettoTraceTime(endTime)); + "react-native", track, highResTimeStampToPerfettoTraceTime(endTime)); } #endif } /* static */ void ReactPerfettoLogger::mark( const std::string_view& eventName, - double startTime, + HighResTimeStamp startTime, const std::optional& trackName) { #ifdef WITH_PERFETTO if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) { @@ -58,7 +72,7 @@ std::string toPerfettoTrackName( "react-native", perfetto::DynamicString(eventName.data(), eventName.size()), getPerfettoWebPerfTrackSync(toPerfettoTrackName(trackName)), - performanceNowToPerfettoTraceTime(startTime)); + highResTimeStampToPerfettoTraceTime(startTime)); } #endif } diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h index c08c695035451d..7206d679faba48 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -20,15 +21,17 @@ namespace facebook::react { */ class ReactPerfettoLogger { public: + static bool isTracing(); + static void mark( const std::string_view& eventName, - double startTime, + HighResTimeStamp startTime, const std::optional& trackName); static void measure( const std::string_view& eventName, - double startTime, - double endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackName); }; diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index c84b3964feb022..9dd01cf4f6c6c8 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -17,6 +17,8 @@ import type { } from './PerformanceEntry'; import type {DetailType, PerformanceMarkOptions} from './UserTiming'; +import DOMException from '../errors/DOMException'; +import {setPlatformObject} from '../webidl/PlatformObjects'; import {EventCounts} from './EventTiming'; import MemoryInfo from './MemoryInfo'; import { @@ -46,6 +48,36 @@ export type PerformanceMeasureOptions = { const ENTRY_TYPES_AVAILABLE_FROM_TIMELINE: $ReadOnlyArray = ['mark', 'measure']; +const cachedReportMark = NativePerformance?.reportMark; +const cachedReportMeasure = NativePerformance?.reportMeasure; +const cachedGetMarkTime = NativePerformance?.getMarkTime; +const cachedNativeClearMarks = NativePerformance?.clearMarks; +const cachedNativeClearMeasures = NativePerformance?.clearMeasures; + +const MARK_OPTIONS_REUSABLE_OBJECT: {...PerformanceMarkOptions} = { + startTime: 0, + detail: undefined, +}; + +const MEASURE_OPTIONS_REUSABLE_OBJECT: {...PerformanceMeasureInit} = { + startTime: 0, + duration: 0, + detail: undefined, +}; + +const getMarkTimeForMeasure = cachedGetMarkTime + ? (markName: string): number => { + const markTime = cachedGetMarkTime(markName); + if (markTime == null) { + throw new DOMException( + `Failed to execute 'measure' on 'Performance': The mark '${markName}' does not exist.`, + 'SyntaxError', + ); + } + return markTime; + } + : undefined; + /** * Partial implementation of the Performance interface for RN, * corresponding to the standard in @@ -109,21 +141,69 @@ export default class Performance { markName: string, markOptions?: PerformanceMarkOptions, ): PerformanceMark { - let computedStartTime; - if (NativePerformance?.markWithResult) { - computedStartTime = NativePerformance.markWithResult( - markName, - markOptions?.startTime, + // IMPORTANT: this method has been micro-optimized. + // Please run the benchmarks in `Performance-benchmarks-itest` to ensure + // changes do not regress performance. + + if (cachedReportMark === undefined) { + warnNoNativePerformance(); + return new PerformanceMark(markName, {startTime: 0}); + } + + if (markName === undefined) { + throw new TypeError( + `Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present.`, ); + } + + const resolvedMarkName = + typeof markName === 'string' ? markName : String(markName); + + let resolvedStartTime; + let resolvedDetail; + + let startTime; + let detail; + if (markOptions != null) { + ({startTime, detail} = markOptions); + } + + if (startTime !== undefined) { + resolvedStartTime = + typeof startTime === 'number' ? startTime : Number(startTime); + if (resolvedStartTime < 0) { + throw new TypeError( + `Failed to execute 'mark' on 'Performance': '${resolvedMarkName}' cannot have a negative start time.`, + ); + } else if ( + // This is faster than calling Number.isFinite() + // eslint-disable-next-line no-self-compare + resolvedStartTime !== resolvedStartTime || + resolvedStartTime === Infinity + ) { + throw new TypeError( + `Failed to execute 'mark' on 'Performance': Failed to read the 'startTime' property from 'PerformanceMarkOptions': The provided double value is non-finite.`, + ); + } } else { - warnNoNativePerformance(); - computedStartTime = performance.now(); + resolvedStartTime = getCurrentTimeStamp(); } - return new PerformanceMark(markName, { - startTime: computedStartTime, - detail: markOptions?.detail, - }); + if (detail !== undefined) { + resolvedDetail = structuredClone(detail); + } + + MARK_OPTIONS_REUSABLE_OBJECT.startTime = resolvedStartTime; + MARK_OPTIONS_REUSABLE_OBJECT.detail = resolvedDetail; + + const entry = new PerformanceMark( + resolvedMarkName, + MARK_OPTIONS_REUSABLE_OBJECT, + ); + + cachedReportMark(resolvedMarkName, resolvedStartTime, entry); + + return entry; } clearMarks(markName?: string): void { @@ -147,70 +227,183 @@ export default class Performance { startTime = 0, endTime = 0; - if (typeof startMarkOrOptions === 'string') { - startMarkName = startMarkOrOptions; - options = {}; - } else if (startMarkOrOptions !== undefined) { - options = startMarkOrOptions; - if (endMark !== undefined) { - throw new TypeError( - "Performance.measure: Can't have both options and endMark", - ); - } - if (options.start === undefined && options.end === undefined) { - throw new TypeError( - 'Performance.measure: Must have at least one of start/end specified in options', - ); - } - if ( - options.start !== undefined && - options.end !== undefined && - options.duration !== undefined - ) { - throw new TypeError( - "Performance.measure: Can't have both start/end and duration explicitly in options", - ); - } + if ( + getMarkTimeForMeasure === undefined || + cachedReportMeasure === undefined + ) { + warnNoNativePerformance(); + return new PerformanceMeasure(measureName, {startTime: 0, duration: 0}); + } - if (typeof options.start === 'number') { - startTime = options.start; - } else { - startMarkName = options.start; + let resolvedMeasureName: string; + let resolvedStartTime: number; + let resolvedDuration: number; + let resolvedDetail: mixed; + + if (measureName === undefined) { + throw new TypeError( + `Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present.`, + ); + } + + resolvedMeasureName = + measureName === 'string' ? measureName : String(measureName); + + if (startMarkOrOptions != null) { + switch (typeof startMarkOrOptions) { + case 'object': { + if (endMark !== undefined) { + throw new TypeError( + `Failed to execute 'measure' on 'Performance': If a non-empty PerformanceMeasureOptions object was passed, |end_mark| must not be passed.`, + ); + } + + const {start, end, duration, detail} = startMarkOrOptions; + + let resolvedEndTime; + + if ( + start !== undefined && + end !== undefined && + duration !== undefined + ) { + throw new TypeError( + `Failed to execute 'measure' on 'Performance': If a non-empty PerformanceMeasureOptions object was passed, it must not have all of its 'start', 'duration', and 'end' properties defined`, + ); + } + + switch (typeof start) { + case 'undefined': { + // This will be handled after all options have been processed. + break; + } + case 'number': { + resolvedStartTime = start; + break; + } + case 'string': { + resolvedStartTime = getMarkTimeForMeasure(start); + break; + } + default: { + resolvedStartTime = getMarkTimeForMeasure(String(start)); + } + } + + switch (typeof end) { + case 'undefined': { + // This will be handled after all options have been processed. + break; + } + case 'number': { + resolvedEndTime = end; + break; + } + case 'string': { + resolvedEndTime = getMarkTimeForMeasure(end); + break; + } + default: { + resolvedEndTime = getMarkTimeForMeasure(String(end)); + } + } + + switch (typeof duration) { + case 'undefined': { + // This will be handled after all options have been processed. + break; + } + case 'number': { + resolvedDuration = duration; + break; + } + default: { + resolvedDuration = Number(duration); + if (!Number.isFinite(resolvedDuration)) { + throw new TypeError( + `Failed to execute 'measure' on 'Performance': Failed to read the 'duration' property from 'PerformanceMeasureOptions': The provided double value is non-finite.`, + ); + } + } + } + + if (resolvedStartTime === undefined) { + if ( + resolvedEndTime !== undefined && + resolvedDuration !== undefined + ) { + resolvedStartTime = resolvedEndTime - resolvedDuration; + } else { + resolvedStartTime = 0; + } + } + + if (resolvedDuration === undefined) { + if ( + resolvedStartTime !== undefined && + resolvedEndTime !== undefined + ) { + resolvedDuration = resolvedEndTime - resolvedStartTime; + } else { + resolvedDuration = getCurrentTimeStamp() - resolvedStartTime; + } + } + + if (detail !== undefined) { + resolvedDetail = structuredClone(detail); + } + + break; + } + case 'string': { + resolvedStartTime = getMarkTimeForMeasure(startMarkOrOptions); + + if (endMark !== undefined) { + resolvedDuration = + getMarkTimeForMeasure(endMark) - resolvedStartTime; + } else { + resolvedDuration = getCurrentTimeStamp() - resolvedStartTime; + } + break; + } + default: { + resolvedStartTime = getMarkTimeForMeasure(String(startMarkOrOptions)); + + if (endMark !== undefined) { + resolvedDuration = + getMarkTimeForMeasure(endMark) - resolvedStartTime; + } else { + resolvedDuration = getCurrentTimeStamp() - resolvedStartTime; + } + } } + } else { + resolvedStartTime = 0; - if (typeof options.end === 'number') { - endTime = options.end; + if (endMark !== undefined) { + resolvedDuration = getMarkTimeForMeasure(endMark) - resolvedStartTime; } else { - endMarkName = options.end; + resolvedDuration = getCurrentTimeStamp() - resolvedStartTime; } - - duration = options.duration ?? duration; } - let computedStartTime = startTime; - let computedDuration = duration; - - if (NativePerformance?.measureWithResult) { - [computedStartTime, computedDuration] = - NativePerformance.measureWithResult( - measureName, - startTime, - endTime, - duration, - startMarkName, - endMarkName, - ); - } else { - warnNoNativePerformance(); - } + MEASURE_OPTIONS_REUSABLE_OBJECT.startTime = resolvedStartTime; + MEASURE_OPTIONS_REUSABLE_OBJECT.duration = resolvedDuration; + MEASURE_OPTIONS_REUSABLE_OBJECT.detail = resolvedDetail; + + const entry = new PerformanceMeasure( + resolvedMeasureName, + MEASURE_OPTIONS_REUSABLE_OBJECT, + ); - const measure = new PerformanceMeasure(measureName, { - startTime: computedStartTime, - duration: computedDuration ?? 0, - detail: options?.detail, - }); + cachedReportMeasure( + resolvedMeasureName, + resolvedStartTime, + resolvedDuration, + entry, + ); - return measure; + return entry; } clearMeasures(measureName?: string): void { diff --git a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js index 5e5cd830d018b3..fcf39c33a67f97 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js @@ -11,7 +11,12 @@ // flowlint unsafe-getters-setters:off export type DOMHighResTimeStamp = number; -export type PerformanceEntryType = 'mark' | 'measure' | 'event' | 'longtask'; +export type PerformanceEntryType = + | 'mark' + | 'measure' + | 'event' + | 'longtask' + | 'resource'; export type PerformanceEntryJSON = { name: string, diff --git a/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js b/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js index 7fe59110fe6770..14f224e28b09d9 100644 --- a/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js @@ -24,6 +24,7 @@ export const RawPerformanceEntryTypeValues = { MEASURE: 2, EVENT: 3, LONGTASK: 4, + RESOURCE: 5, }; export function rawToPerformanceEntry( @@ -76,6 +77,8 @@ export function rawToPerformanceEntryType( return 'event'; case RawPerformanceEntryTypeValues.LONGTASK: return 'longtask'; + case RawPerformanceEntryTypeValues.RESOURCE: + return 'resource'; default: throw new TypeError( `rawToPerformanceEntryType: unexpected performance entry type received: ${type}`, @@ -95,6 +98,8 @@ export function performanceEntryTypeToRaw( return RawPerformanceEntryTypeValues.EVENT; case 'longtask': return RawPerformanceEntryTypeValues.LONGTASK; + case 'resource': + return RawPerformanceEntryTypeValues.RESOURCE; default: // Verify exhaustive check with Flow (type: empty); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js new file mode 100644 index 00000000000000..4139eaaa29dff7 --- /dev/null +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js @@ -0,0 +1,251 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import type Performance from 'react-native/src/private/webapis/performance/Performance'; +import type {PerformanceObserverEntryList} from 'react-native/src/private/webapis/performance/PerformanceObserver'; + +import NativePerformance from '../specs/NativePerformance'; +import * as Fantom from '@react-native/fantom'; +import nullthrows from 'nullthrows'; +import {useState} from 'react'; +import {Text, View} from 'react-native'; +import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerformanceObserver'; +import {PerformanceEventTiming} from 'react-native/src/private/webapis/performance/EventTiming'; +import {PerformanceObserver} from 'react-native/src/private/webapis/performance/PerformanceObserver'; + +setUpPerformanceObserver(); + +declare var performance: Performance; + +function sleep(ms: number) { + const end = performance.now() + ms; + while (performance.now() < end) {} +} + +function ensurePerformanceEventTiming(value: mixed): PerformanceEventTiming { + if (!(value instanceof PerformanceEventTiming)) { + throw new Error( + `Expected instance of PerformanceEventTiming but got ${String(value)}`, + ); + } + + return value; +} + +describe('Event Timing API', () => { + it('reports events without event handlers or updates', () => { + const callback = jest.fn(); + + const observer = new PerformanceObserver(callback); + observer.observe({entryTypes: ['event']}); + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + const element = nullthrows(root.document.documentElement.firstElementChild); + + expect(callback).not.toHaveBeenCalled(); + + Fantom.dispatchNativeEvent(element, 'click'); + + expect(callback).toHaveBeenCalledTimes(1); + + const entryList = callback.mock.lastCall[0] as PerformanceObserverEntryList; + const entries = entryList.getEntries(); + + expect(entries.length).toBe(1); + + const entry = ensurePerformanceEventTiming(entries[0]); + + expect(entry.entryType).toBe('event'); + expect(entry.name).toBe('click'); + + expect(entry.startTime).toBeGreaterThanOrEqual(0); + expect(entry.processingStart).toBeGreaterThan(entry.startTime); + expect(entry.processingEnd).toBeGreaterThanOrEqual(entry.processingStart); + + expect(entry.duration).toBeGreaterThanOrEqual(0); + expect(entry.duration).toBeGreaterThan( + entry.processingEnd - entry.startTime, + ); + + expect(entry.interactionId).toBeGreaterThanOrEqual(0); + }); + + it('reports events with handlers but no updates', () => { + const callback = jest.fn(); + + const observer = new PerformanceObserver(callback); + observer.observe({entryTypes: ['event']}); + + const SIMULATED_PROCESSING_DELAY = 50; + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render( + { + sleep(SIMULATED_PROCESSING_DELAY); + }} + />, + ); + }); + + const element = nullthrows(root.document.documentElement.firstElementChild); + + expect(callback).not.toHaveBeenCalled(); + + Fantom.dispatchNativeEvent(element, 'click'); + + expect(callback).toHaveBeenCalledTimes(1); + + const entryList = callback.mock.lastCall[0] as PerformanceObserverEntryList; + const entries = entryList.getEntries(); + + expect(entries.length).toBe(1); + + const entry = ensurePerformanceEventTiming(entries[0]); + + expect(entry.entryType).toBe('event'); + expect(entry.name).toBe('click'); + + expect(entry.startTime).toBeGreaterThanOrEqual(0); + expect(entry.processingStart).toBeGreaterThan(entry.startTime); + expect(entry.processingEnd).toBeGreaterThanOrEqual(entry.processingStart); + + expect(entry.processingEnd - entry.processingStart).toBeGreaterThanOrEqual( + SIMULATED_PROCESSING_DELAY, + ); + + expect(entry.duration).toBeGreaterThanOrEqual(0); + expect(entry.duration).toBeGreaterThan( + entry.processingEnd - entry.startTime, + ); + + expect(entry.interactionId).toBeGreaterThanOrEqual(0); + }); + + it('reports events with updates', () => { + const callback = jest.fn(); + + const observer = new PerformanceObserver(callback); + observer.observe({entryTypes: ['event']}); + + function MyComponent() { + const [count, setCount] = useState(0); + + return ( + { + setCount(count + 1); + }}> + {count} + + ); + } + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + const element = nullthrows(root.document.documentElement.firstElementChild); + + expect(callback).not.toHaveBeenCalled(); + + Fantom.dispatchNativeEvent(element, 'click'); + + expect(callback).toHaveBeenCalledTimes(1); + + const entryList = callback.mock.lastCall[0] as PerformanceObserverEntryList; + const entries = entryList.getEntries(); + + expect(entries.length).toBe(1); + + const entry = ensurePerformanceEventTiming(entries[0]); + + expect(entry.entryType).toBe('event'); + expect(entry.name).toBe('click'); + + expect(entry.startTime).toBeGreaterThanOrEqual(0); + expect(entry.processingStart).toBeGreaterThan(entry.startTime); + expect(entry.processingEnd).toBeGreaterThanOrEqual(entry.processingStart); + + expect(entry.duration).toBeGreaterThanOrEqual(0); + expect(entry.duration).toBeGreaterThan( + entry.processingEnd - entry.startTime, + ); + + // TODO: When Fantom provides structured data from mounting manager, add timestamp to operations and verify that the duration includes that. + + expect(entry.interactionId).toBeGreaterThanOrEqual(0); + }); + + it('reports number of dispatched events via performance.eventCounts', () => { + NativePerformance?.clearEventCountsForTesting?.(); + + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + + const element = nullthrows(root.document.documentElement.firstElementChild); + + expect(performance.eventCounts).not.toBeInstanceOf(Map); + + // FIXME: this isn't spec compliant, as the map should be prepopulated with + // all the supported event names mapped to 0. + expect(performance.eventCounts.size).toBe(0); + expect([...performance.eventCounts.entries()]).toEqual([]); + const initialForEachCallback = jest.fn(); + performance.eventCounts.forEach(initialForEachCallback); + expect(initialForEachCallback.mock.calls).toEqual([]); + expect([...performance.eventCounts.keys()]).toEqual([]); + expect([...performance.eventCounts.values()]).toEqual([]); + + Fantom.dispatchNativeEvent(element, 'click'); + Fantom.dispatchNativeEvent(element, 'click'); + Fantom.dispatchNativeEvent(element, 'click'); + + Fantom.dispatchNativeEvent(element, 'pointerDown'); + Fantom.dispatchNativeEvent(element, 'pointerUp'); + + expect(performance.eventCounts.size).toBe(3); + expect(performance.eventCounts.get('click')).toBe(3); + expect(performance.eventCounts.get('pointerdown')).toBe(1); + expect(performance.eventCounts.get('pointerup')).toBe(1); + + expect([...performance.eventCounts.entries()]).toEqual([ + ['pointerup', 1], + ['pointerdown', 1], + ['click', 3], + ]); + + const forEachCallback = jest.fn(); + performance.eventCounts.forEach(forEachCallback); + expect(forEachCallback.mock.calls).toEqual([ + [1, 'pointerup', performance.eventCounts], + [1, 'pointerdown', performance.eventCounts], + [3, 'click', performance.eventCounts], + ]); + + expect([...performance.eventCounts.keys()]).toEqual([ + 'pointerup', + 'pointerdown', + 'click', + ]); + + expect([...performance.eventCounts.values()]).toEqual([1, 1, 3]); + }); +}); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js new file mode 100644 index 00000000000000..dc79c14e794d56 --- /dev/null +++ b/packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import type Performance from '../Performance'; + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +declare var performance: Performance; + +describe('Performance', () => { + it('measure validates mark names presence in the buffer, if specified', () => { + expect(() => { + performance.measure('measure', 'start', 'end'); + }).toThrow( + "Failed to execute 'measure' on 'Performance': The mark 'start' does not exist.", + ); // This should also check that Error is an instance of DOMException and is SyntaxError, + // but toThrow checked currently only supports string argument. + + performance.mark('start'); + expect(() => { + performance.measure('measure', 'start', 'end'); + }).toThrow( + "Failed to execute 'measure' on 'Performance': The mark 'end' does not exist.", + ); // This should also check that Error is an instance of DOMException and is SyntaxError, + // but toThrow checked currently only supports string argument. + + performance.mark('end'); + expect(() => { + performance.measure('measure', 'start', 'end'); + }).not.toThrow(); + }); +}); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js new file mode 100644 index 00000000000000..85fa6443510613 --- /dev/null +++ b/packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js @@ -0,0 +1,811 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment'; + +import type Performance from '../Performance'; +import type { + PerformanceEntryJSON, + PerformanceEntryList, +} from '../PerformanceEntry'; + +import ensureInstance from '../../../__tests__/utilities/ensureInstance'; +import DOMException from '../../errors/DOMException'; +import NativePerformance from '../specs/NativePerformance'; +import {PerformanceMark, PerformanceMeasure} from '../UserTiming'; + +declare var performance: Performance; + +function getThrownError(fn: () => mixed): mixed { + try { + fn(); + } catch (e) { + return e; + } + throw new Error('Expected function to throw'); +} + +function toJSON(entries: PerformanceEntryList): Array { + return entries.map(entry => entry.toJSON()); +} + +describe('User Timing', () => { + beforeEach(() => { + performance.clearMarks(); + performance.clearMeasures(); + }); + + describe('mark', () => { + it('works with default timestamp', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + const mark = performance.mark('mark-now'); + + expect(mark).toBeInstanceOf(PerformanceMark); + expect(mark.entryType).toBe('mark'); + expect(mark.name).toBe('mark-now'); + expect(mark.startTime).toBe(25); + expect(mark.duration).toBe(0); + expect(mark.detail).toBe(null); + }); + + it('works with custom timestamp', () => { + const mark = performance.mark('mark-custom', {startTime: 10}); + + expect(mark).toBeInstanceOf(PerformanceMark); + expect(mark.entryType).toBe('mark'); + expect(mark.name).toBe('mark-custom'); + expect(mark.startTime).toBe(10); + expect(mark.duration).toBe(0); + expect(mark.detail).toBe(null); + }); + + it('provides detail', () => { + const originalDetail = {foo: 'bar'}; + + const mark = performance.mark('some-mark', { + detail: originalDetail, + }); + + expect(mark.detail).toEqual(originalDetail); + expect(mark.detail).not.toBe(originalDetail); + }); + + it('provides a null detail if it is not provided or is undefined', () => { + expect(performance.mark('mark-without-detail').detail).toBe(null); + expect( + performance.mark('mark-without-detail', {detail: undefined}).detail, + ).toBe(null); + }); + + it('throws if no name is provided', () => { + expect(() => { + // $FlowExpectedError[incompatible-call] + performance.mark(); + }).toThrow( + `Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present.`, + ); + }); + + it('casts mark name to a string', () => { + // $FlowExpectedError[incompatible-call] + const mark = performance.mark(10); + + expect(mark.name).toBe('10'); + }); + + it('casts startTime to a number', () => { + const mark = performance.mark('some-mark', { + // $FlowExpectedError[incompatible-call] + startTime: '10', + }); + + expect(mark.startTime).toBe(10); + + const mark2 = performance.mark('some-mark', { + // $FlowExpectedError[incompatible-call] + startTime: null, + }); + + expect(mark2.startTime).toBe(0); + }); + + it('throws if startTime cannot be converted to a finite number', () => { + expect(() => { + performance.mark('some-mark', {startTime: NaN}); + }).toThrow( + `Failed to execute 'mark' on 'Performance': Failed to read the 'startTime' property from 'PerformanceMarkOptions': The provided double value is non-finite.`, + ); + + expect(() => { + // $FlowExpectedError[incompatible-call] + performance.mark('some-mark', {startTime: {}}); + }).toThrow( + `Failed to execute 'mark' on 'Performance': Failed to read the 'startTime' property from 'PerformanceMarkOptions': The provided double value is non-finite.`, + ); + }); + + it('throws if startTime is negative', () => { + expect(() => { + performance.mark('some-mark', {startTime: -1}); + }).toThrow( + `Failed to execute 'mark' on 'Performance': 'some-mark' cannot have a negative start time.`, + ); + }); + }); + + describe('measure', () => { + describe('with measureOptions', () => { + it('uses 0 as default start and now as default end', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + const measure = performance.measure('measure-with-defaults', {}); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-defaults'); + expect(measure.startTime).toBe(0); + expect(measure.duration).toBe(25); + expect(measure.detail).toBe(null); + }); + + it('works with a start timestamp', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + const measure = performance.measure('measure-with-start-timestamp', { + start: 10, + }); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-start-timestamp'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(15); + expect(measure.detail).toBe(null); + }); + + it('works with start mark', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + performance.mark('start-mark', { + startTime: 10, + }); + + const measure = performance.measure('measure-with-start-mark', { + start: 'start-mark', + }); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-start-mark'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(15); + expect(measure.detail).toBe(null); + }); + + it('works with end mark', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + performance.mark('end-mark', { + startTime: 50, + }); + + const measure = performance.measure('measure-with-end-mark', { + end: 'end-mark', + }); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-end-mark'); + expect(measure.startTime).toBe(0); + expect(measure.duration).toBe(50); + expect(measure.detail).toBe(null); + }); + + it('works with start mark and end mark', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + performance.mark('start-mark', { + startTime: 10, + }); + + performance.mark('end-mark', { + startTime: 50, + }); + + const measure = performance.measure( + 'measure-with-start-mark-and-end-mark', + { + start: 'start-mark', + end: 'end-mark', + }, + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-start-mark-and-end-mark'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(40); + expect(measure.detail).toBe(null); + }); + + it('works with a start timestamp and an end timestamp', () => { + const measure = performance.measure( + 'measure-with-start-timestamp-and-end-timestamp', + { + start: 10, + end: 25, + }, + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe( + 'measure-with-start-timestamp-and-end-timestamp', + ); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(15); + expect(measure.detail).toBe(null); + }); + + it('works with a start timestamp and a duration', () => { + const measure = performance.measure( + 'measure-with-start-timestamp-and-duration', + { + start: 10, + duration: 30, + }, + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-start-timestamp-and-duration'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(30); + expect(measure.detail).toBe(null); + }); + + it('works with a start mark and a duration', () => { + performance.mark('start-mark', { + startTime: 10, + }); + + const measure = performance.measure( + 'measure-with-start-mark-and-duration', + { + start: 'start-mark', + duration: 30, + }, + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-start-mark-and-duration'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(30); + expect(measure.detail).toBe(null); + }); + + it('works with an end timestamp and a duration', () => { + const measure = performance.measure( + 'measure-with-end-timestamp-and-duration', + { + end: 40, + duration: 30, + }, + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-end-timestamp-and-duration'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(30); + expect(measure.detail).toBe(null); + }); + + it('works with an end mark and a duration', () => { + performance.mark('end-mark', { + startTime: 40, + }); + + const measure = performance.measure( + 'measure-with-end-mark-and-duration', + { + end: 'end-mark', + duration: 30, + }, + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-end-mark-and-duration'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(30); + expect(measure.detail).toBe(null); + }); + + it('throws if the specified mark does NOT exist', () => { + const missingStartMarkError = ensureInstance( + getThrownError(() => { + performance.measure('measure', { + start: 'start', + end: 'end', + }); + }), + DOMException, + ); + + expect(missingStartMarkError.name).toBe('SyntaxError'); + expect(missingStartMarkError.message).toBe( + "Failed to execute 'measure' on 'Performance': The mark 'start' does not exist.", + ); + + performance.mark('start'); + + const missingEndMarkError = ensureInstance( + getThrownError(() => { + performance.measure('measure', { + start: 'start', + end: 'end', + }); + }), + DOMException, + ); + + expect(missingEndMarkError.message).toBe( + "Failed to execute 'measure' on 'Performance': The mark 'end' does not exist.", + ); + + performance.mark('end'); + expect(() => { + performance.measure('measure', { + start: 'start', + end: 'end', + }); + }).not.toThrow(); + }); + }); + + describe('with startMark / endMark', () => { + it('uses 0 as default start and now as default end', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + const measure = performance.measure('measure-with-defaults'); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-defaults'); + expect(measure.startTime).toBe(0); + expect(measure.duration).toBe(25); + expect(measure.detail).toBe(null); + }); + + it('works with startMark', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + performance.mark('start-mark', { + startTime: 10, + }); + + const measure = performance.measure( + 'measure-with-start-mark', + 'start-mark', + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-start-mark'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(15); + expect(measure.detail).toBe(null); + }); + + it('works with startMark and endMark', () => { + NativePerformance?.setCurrentTimeStampForTesting?.(25); + + performance.mark('start-mark', { + startTime: 10, + }); + + performance.mark('end-mark', { + startTime: 25, + }); + + const measure = performance.measure( + 'measure-with-start-mark-and-end-mark', + 'start-mark', + ); + + expect(measure).toBeInstanceOf(PerformanceMeasure); + expect(measure.entryType).toBe('measure'); + expect(measure.name).toBe('measure-with-start-mark-and-end-mark'); + expect(measure.startTime).toBe(10); + expect(measure.duration).toBe(15); + expect(measure.detail).toBe(null); + }); + + it('throws if the specified marks do NOT exist', () => { + const missingStartMarkError = ensureInstance( + getThrownError(() => { + performance.measure('measure', 'start', 'end'); + }), + DOMException, + ); + + expect(missingStartMarkError.name).toBe('SyntaxError'); + expect(missingStartMarkError.message).toBe( + "Failed to execute 'measure' on 'Performance': The mark 'start' does not exist.", + ); + + performance.mark('start'); + + const missingEndMarkError = ensureInstance( + getThrownError(() => { + performance.measure('measure', 'start', 'end'); + }), + DOMException, + ); + + expect(missingEndMarkError.message).toBe( + "Failed to execute 'measure' on 'Performance': The mark 'end' does not exist.", + ); + + performance.mark('end'); + expect(() => { + performance.measure('measure', 'start', 'end'); + }).not.toThrow(); + }); + }); + + it('provides detail', () => { + const originalDetail = {foo: 'bar'}; + + const measure = performance.measure('measure-with-detail', { + start: 10, + end: 20, + detail: originalDetail, + }); + + expect(measure.detail).toEqual(originalDetail); + expect(measure.detail).not.toBe(originalDetail); + }); + + it('provides a null detail if it is not provided or is undefined', () => { + expect(performance.measure('measure-without-detail').detail).toBe(null); + expect( + performance.measure('measure-without-detail', {detail: undefined}) + .detail, + ).toBe(null); + }); + + it('casts measure name to a string', () => { + // $FlowExpectedError[incompatible-call] + const measure = performance.measure(10); + + expect(measure.name).toBe('10'); + }); + + it('throws if no name is provided', () => { + expect(() => { + // $FlowExpectedError[incompatible-call] + performance.measure(); + }).toThrow( + `Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present.`, + ); + }); + }); + + describe('getting and clearing marks and measures', () => { + it('provides access to all buffered entries ordered by startTime', () => { + performance.mark('baz', {startTime: 20}); + performance.mark('bar', {startTime: 30}); + performance.mark('foo', {startTime: 10}); + + performance.mark('foo', {startTime: 50}); // again + + performance.measure('bar', {start: 20, duration: 40}); + performance.measure('foo', {start: 10, duration: 40}); + + const expectedInitialEntries = [ + { + duration: 0, + entryType: 'mark', + name: 'foo', + startTime: 10, + }, + { + duration: 40, + entryType: 'measure', + name: 'foo', + startTime: 10, + }, + { + duration: 0, + entryType: 'mark', + name: 'baz', + startTime: 20, + }, + { + duration: 40, + entryType: 'measure', + name: 'bar', + startTime: 20, + }, + { + duration: 0, + entryType: 'mark', + name: 'bar', + startTime: 30, + }, + { + duration: 0, + entryType: 'mark', + name: 'foo', + startTime: 50, + }, + ]; + + /* + * getEntries + */ + + expect(toJSON(performance.getEntries())).toEqual(expectedInitialEntries); + + // Returns the same list again + expect(toJSON(performance.getEntries())).toEqual(expectedInitialEntries); + + /* + * getEntriesByType + */ + + expect(toJSON(performance.getEntriesByType('mark'))).toEqual( + expectedInitialEntries.filter(entry => entry.entryType === 'mark'), + ); + + // Returns the same list again + expect(toJSON(performance.getEntriesByType('mark'))).toEqual( + expectedInitialEntries.filter(entry => entry.entryType === 'mark'), + ); + + expect(toJSON(performance.getEntriesByType('measure'))).toEqual( + expectedInitialEntries.filter(entry => entry.entryType === 'measure'), + ); + + // Returns the same list again + expect(toJSON(performance.getEntriesByType('measure'))).toEqual( + expectedInitialEntries.filter(entry => entry.entryType === 'measure'), + ); + + /* + * getEntriesByName + */ + + expect(toJSON(performance.getEntriesByName('foo'))).toEqual( + expectedInitialEntries.filter(entry => entry.name === 'foo'), + ); + + // Returns the same list again + expect(toJSON(performance.getEntriesByName('foo'))).toEqual( + expectedInitialEntries.filter(entry => entry.name === 'foo'), + ); + + expect(toJSON(performance.getEntriesByName('bar'))).toEqual( + expectedInitialEntries.filter(entry => entry.name === 'bar'), + ); + + // Returns the same list again + expect(toJSON(performance.getEntriesByName('bar'))).toEqual( + expectedInitialEntries.filter(entry => entry.name === 'bar'), + ); + }); + + it('clears entries as specified', () => { + performance.mark('baz', {startTime: 20}); + performance.mark('bar', {startTime: 30}); + performance.mark('foo', {startTime: 10}); + + performance.mark('foo', {startTime: 50}); // again + + performance.measure('bar', {start: 20, duration: 40}); + performance.measure('foo', {start: 10, duration: 40}); + + performance.clearMarks('foo'); + + expect(toJSON(performance.getEntries())).toEqual([ + { + duration: 40, + entryType: 'measure', + name: 'foo', + startTime: 10, + }, + { + duration: 0, + entryType: 'mark', + name: 'baz', + startTime: 20, + }, + { + duration: 40, + entryType: 'measure', + name: 'bar', + startTime: 20, + }, + { + duration: 0, + entryType: 'mark', + name: 'bar', + startTime: 30, + }, + ]); + + expect(toJSON(performance.getEntriesByType('mark'))).toEqual([ + { + duration: 0, + entryType: 'mark', + name: 'baz', + startTime: 20, + }, + { + duration: 0, + entryType: 'mark', + name: 'bar', + startTime: 30, + }, + ]); + + expect(toJSON(performance.getEntriesByName('foo'))).toEqual([ + { + duration: 40, + entryType: 'measure', + name: 'foo', + startTime: 10, + }, + ]); + + performance.clearMeasures('bar'); + + expect(toJSON(performance.getEntries())).toEqual([ + { + duration: 40, + entryType: 'measure', + name: 'foo', + startTime: 10, + }, + { + duration: 0, + entryType: 'mark', + name: 'baz', + startTime: 20, + }, + { + duration: 0, + entryType: 'mark', + name: 'bar', + startTime: 30, + }, + ]); + + expect(toJSON(performance.getEntriesByType('measure'))).toEqual([ + { + duration: 40, + entryType: 'measure', + name: 'foo', + startTime: 10, + }, + ]); + + expect(toJSON(performance.getEntriesByName('bar'))).toEqual([ + { + duration: 0, + entryType: 'mark', + name: 'bar', + startTime: 30, + }, + ]); + + performance.clearMarks(); + + expect(toJSON(performance.getEntries())).toEqual([ + { + duration: 40, + entryType: 'measure', + name: 'foo', + startTime: 10, + }, + ]); + + expect(toJSON(performance.getEntriesByType('mark'))).toEqual([]); + + performance.clearMeasures(); + + expect(toJSON(performance.getEntries())).toEqual([]); + + expect(toJSON(performance.getEntriesByType('measure'))).toEqual([]); + }); + + it('handles consecutive adding and clearing (marks)', () => { + performance.mark('foo', {startTime: 10}); + performance.mark('foo', {startTime: 20}); + + expect(toJSON(performance.getEntries())).toEqual([ + { + duration: 0, + entryType: 'mark', + name: 'foo', + startTime: 10, + }, + { + duration: 0, + entryType: 'mark', + name: 'foo', + startTime: 20, + }, + ]); + + performance.clearMarks(); + + expect(toJSON(performance.getEntries())).toEqual([]); + + performance.mark('foo', {startTime: 30}); + + expect(toJSON(performance.getEntries())).toEqual([ + { + duration: 0, + entryType: 'mark', + name: 'foo', + startTime: 30, + }, + ]); + + performance.clearMarks(); + + expect(toJSON(performance.getEntries())).toEqual([]); + }); + + it('handles consecutive adding and clearing (measures)', () => { + performance.measure('foo', {start: 10, end: 20}); + performance.measure('foo', {start: 20, end: 30}); + + expect(toJSON(performance.getEntries())).toEqual([ + { + duration: 10, + entryType: 'measure', + name: 'foo', + startTime: 10, + }, + { + duration: 10, + entryType: 'measure', + name: 'foo', + startTime: 20, + }, + ]); + + performance.clearMeasures(); + + expect(toJSON(performance.getEntries())).toEqual([]); + + performance.measure('foo', {start: 30, end: 40}); + + expect(toJSON(performance.getEntries())).toEqual([ + { + duration: 10, + entryType: 'measure', + name: 'foo', + startTime: 30, + }, + ]); + + performance.clearMeasures(); + + expect(toJSON(performance.getEntries())).toEqual([]); + }); + }); +}); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js index d1b81768f23184..498eeda19ddbe4 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -24,10 +24,19 @@ export type RawPerformanceEntry = { startTime: number, duration: number, - // For "event" entries only: + // For PerformanceEventTiming only processingStart?: number, processingEnd?: number, interactionId?: number, + + // For PerformanceResourceTiming only + fetchStart?: number, + requestStart?: number, + connectStart?: number, + connectEnd?: number, + responseStart?: number, + responseEnd?: number, + responseStatus?: number, }; export type OpaqueNativeObserverHandle = mixed; @@ -45,18 +54,14 @@ export type PerformanceObserverInit = { export interface Spec extends TurboModule { +now?: () => number; - +markWithResult?: ( - name: string, - startTime?: number, - ) => NativePerformanceMarkResult; - +measureWithResult?: ( + +reportMark?: (name: string, startTime: number, entry: mixed) => void; + +reportMeasure?: ( name: string, startTime: number, - endTime: number, - duration?: number, - startMark?: string, - endMark?: string, - ) => NativePerformanceMeasureResult; + duration: number, + entry: mixed, + ) => void; + +getMarkTime?: (name: string) => ?number; +clearMarks?: (entryName?: string) => void; +clearMeasures?: (entryName?: string) => void; +getEntries?: () => $ReadOnlyArray; @@ -87,6 +92,9 @@ export interface Spec extends TurboModule { ) => $ReadOnlyArray; +getSupportedPerformanceEntryTypes?: () => $ReadOnlyArray; + + +setCurrentTimeStampForTesting?: (timeStamp: number) => void; + +clearEventCountsForTesting?: () => void; } export default (TurboModuleRegistry.get('NativePerformanceCxx'): ?Spec); diff --git a/packages/rn-tester/NativeCxxModuleExample/CMakeLists.txt b/packages/rn-tester/NativeCxxModuleExample/CMakeLists.txt index 8ac742cda5d0d6..422777aa5f6476 100644 --- a/packages/rn-tester/NativeCxxModuleExample/CMakeLists.txt +++ b/packages/rn-tester/NativeCxxModuleExample/CMakeLists.txt @@ -6,19 +6,11 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -add_compile_options( - -fexceptions - -frtti - -std=c++20 - -Wall - -Wpedantic - -DFOLLY_NO_CONFIG=1 - -DLOG_TAG=\"ReactNative\") - file(GLOB nativecxxmoduleexample_SRC CONFIGURE_DEPENDS *.cpp) add_library(nativecxxmoduleexample STATIC ${nativecxxmoduleexample_SRC}) target_include_directories(nativecxxmoduleexample PUBLIC .) +target_compile_reactnative_options(nativecxxmoduleexample PRIVATE) target_link_libraries(nativecxxmoduleexample fbjni diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 0d72986570b4e8..4be1d6629b6c86 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -1305,7 +1305,46 @@ PODS: - React-runtimeexecutor (= 0.78.0) - React-jsinspectortracing (0.78.0): - RCT-Folly - - React-jsitracing (0.78.0): + - RCT-Folly/Fabric + - SocketRocket + - React-jsinspectornetwork (1000.0.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-featureflags + - React-jsinspectorcdp + - React-performancetimeline + - React-timing + - SocketRocket + - React-jsinspectortracing (1000.0.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-oscompat + - SocketRocket + - React-jsitooling (1000.0.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact (= 1000.0.0) + - React-jsi (= 1000.0.0) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - SocketRocket + - React-jsitracing (1000.0.0): - React-jsi - React-logger (0.78.0): - glog @@ -1898,69 +1937,75 @@ SPEC CHECKSUMS: MyNativeView: 7ec40c9be5553836f715bbebeaf8c3a6266b1dc4 NativeCxxModuleExample: c1ff88856150ffd095cec4e2f08cabd4890ca6a0 OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74 - OSSLibraryExample: ecce0b1001bd947153bf907472122b43693d8b4e - RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 - RCTDeprecation: b2eecf2d60216df56bc5e6be5f063826d3c1ee35 - RCTRequired: 78522de7dc73b81f3ed7890d145fa341f5bb32ea - RCTTypeSafety: c135dd2bf50402d87fd12884cbad5d5e64850edd - React: b229c49ed5898dab46d60f61ed5a0bfa2ee2fadb - React-callinvoker: 2ac508e92c8bd9cf834cc7d7787d94352e4af58f - React-Core: 325b4f6d9162ae8b9a6ff42fe78e260eb124180d - React-CoreModules: 558041e5258f70cd1092f82778d07b8b2ff01897 - React-cxxreact: 8fff17cbe76e6a8f9991b59552e1235429f9c74b - React-debug: 0a5fcdbacc6becba0521e910c1bcfdb20f32a3f6 - React-defaultsnativemodule: 618dc50a0fad41b489997c3eb7aba3a74479fd14 - React-domnativemodule: 7ba599afb6c2a7ec3eb6450153e2efe0b8747e9a - React-Fabric: 252112089d2c63308f4cbfade4010b6606db67d1 - React-FabricComponents: 0f643441916b898263701d273677652bcde75956 - React-FabricImage: 728b8061cdec2857ca885fd605ee03ad43ffca98 - React-featureflags: 19682e02ef5861d96b992af16a19109c3dfc1200 - React-featureflagsnativemodule: 23528c7e7d50782b7ef0804168ba40bbaf1e86ab - React-graphics: fefe48f71bfe6f48fd037f59e8277b12e91b6be1 - React-hermes: a9a0c8377627b5506ef9a7b6f60a805c306e3f51 - React-idlecallbacksnativemodule: 7e2b6a3b70e042f89cd91dbd73c479bb39a72a7e - React-ImageManager: e3300996ac2e2914bf821f71e2f2c92ae6e62ae2 - React-jserrorhandler: fa75876c662e5d7e79d6efc763fc9f4c88e26986 - React-jsi: f3f51595cc4c089037b536368f016d4742bf9cf7 - React-jsiexecutor: cca6c232db461e2fd213a11e9364cfa6fdaa20eb - React-jsinspector: 2bd4c9fddf189d6ec2abf4948461060502582bef - React-jsinspectortracing: a417d8a0ad481edaa415734b4dac81e3e5ee7dc6 - React-jsitracing: 1ff7172c5b0522cbf6c98d82bdbb160e49b5804e - React-logger: 018826bfd51b9f18e87f67db1590bc510ad20664 - React-Mapbuffer: 3c11cee7737609275c7b66bd0b1de475f094cedf - React-microtasksnativemodule: 843f352b32aacbe13a9c750190d34df44c3e6c2c - React-NativeModulesApple: 88433b6946778bea9c153e27b671de15411bf225 - React-perflogger: 9e8d3c0dc0194eb932162812a168aa5dc662f418 - React-performancetimeline: 5a2d6efef52bdcefac079c7baa30934978acd023 - React-RCTActionSheet: 592674cf61142497e0e820688f5a696e41bf16dd - React-RCTAnimation: e6d669872f9b3b4ab9527aab283b7c49283236b7 - React-RCTAppDelegate: de2343fe08be4c945d57e0ecce44afcc7dd8fc03 - React-RCTBlob: 3e2dce94c56218becc4b32b627fc2293149f798d - React-RCTFabric: cac2c033381d79a5956e08550b0220cb2d78ea93 - React-RCTFBReactNativeSpec: d10ca5e0ccbfeac8c047361fedf8e4ac653887b6 - React-RCTImage: dc04b176c022d12a8f55ae7a7279b1e091066ae0 - React-RCTLinking: 88f5e37fe4f26fbc80791aa2a5f01baf9b9a3fd5 - React-RCTNetwork: f213693565efbd698b8e9c18d700a514b49c0c8e - React-RCTPushNotification: 3c13fa7960f7093dc50773a973c1f2bedc550a4f - React-RCTSettings: a2d32a90c45a3575568cad850abc45924999b8a5 - React-RCTTest: 9d8d6e1d7008640cbc5135dcdb64f151de6ba40e - React-RCTText: 54cdcd1cbf6f6a91dc6317f5d2c2b7fc3f6bf7a0 - React-RCTVibration: 11dae0e7f577b5807bb7d31e2e881eb46f854fd4 - React-rendererconsistency: 64e897e00d2568fd8dfe31e2496f80e85c0aaad1 - React-rendererdebug: 41ce452460c44bba715d9e41d5493a96de277764 - React-rncore: 58748c2aa445f56b99e5118dad0aedb51c40ce9f - React-RuntimeApple: 7785ed0d8ae54da65a88736bb63ca97608a6d933 - React-RuntimeCore: 6029ea70bc77f98cfd43ebe69217f14e93ba1f12 - React-runtimeexecutor: a188df372373baf5066e6e229177836488799f80 - React-RuntimeHermes: a264609c28b796edfffc8ae4cb8fad1773ab948b - React-runtimescheduler: 23ec3a1e0fb1ec752d1a9c1fb15258c30bfc7222 - React-timing: bb220a53a795ed57976a4855c521f3de2f298fe5 - React-utils: 3b054aaebe658fc710a8d239d0e4b9fd3e0b78f9 - ReactAppDependencyProvider: a1fb08dfdc7ebc387b2e54cfc9decd283ed821d8 - ReactCodegen: eed7efebc54693a2e5fcb77ea9989b12fc7618d8 - ReactCommon: 0c097b53f03d6bf166edbcd0915da32f3015dd90 - ReactCommon-Samples: 3f45e4487d9628027cde97896dfe0a59b995fb89 - ScreenshotManager: 39b3895f01c6af3dc16fc5cabdef1e36de7d22c5 + OSSLibraryExample: 9264008f6ba6a068cb2db3765ffdf519c9c0f6a4 + RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 + RCTDeprecation: 3808e36294137f9ee5668f4df2e73dc079cd1dcf + RCTRequired: a00614e2da5344c2cda3d287050b6cee00e21dc6 + RCTTypeSafety: 459a16418c6b413060d35434ba3e83f5b0bd2651 + React: 170a01a19ba2525ab7f11243e2df6b19bf268093 + React-callinvoker: f08f425e4043cd1998a158b6e39a6aed1fd1d718 + React-Core: 798334ba9a8a5a97e84ab56b5565d6ed5ac09712 + React-CoreModules: 25e5ff9b077e8a3780672a9eae5197e54ce367e1 + React-cxxreact: 8a5de0fe0933c56fdf4c3548902d9525ea73322b + React-debug: 195df38487d3f48a7af04deddeb4a5c6d4440416 + React-defaultsnativemodule: 47959b704240e39c6b401d3e3fe1da114b271ac6 + React-domnativemodule: b6dd0c3be3e4514052f68d0b5a94a8d06e0f14fd + React-Fabric: 06175f348d6df210e7545ee513e38fe1709a6d82 + React-FabricComponents: ace73c8cc991f299d9b3aa9556cd5b6e86a8b8e6 + React-FabricImage: ff9dc6afafc92e0da3f3d33d96ec40317bb8ec10 + React-featureflags: 595651ea13c63a9f77f06d9a1973b665b4a28b7e + React-featureflagsnativemodule: f5f69151bc4c2945003fc502ebaecee7fda02c42 + React-graphics: 38cc9ba3336bd50960e6052648374f3591abc7a6 + React-hermes: 34666bbd8d6b585e290f000d4d31c2182ece8940 + React-idlecallbacksnativemodule: b66d99ffb2ff765e1bd952b6bc6bf4ba3d5204d3 + React-ImageManager: a6833445e17879933378b7c0ba45ee42115c14bc + React-jserrorhandler: bec134a192c50338193544404d45df24fb8a19ca + React-jsi: 4ad77650fb0ca4229569eb2532db7a87e3d12662 + React-jsiexecutor: 569425f7cd2c3e005a17e5211843e541c11d6916 + React-jsinspector: 885e8180e898f07e4d7df29e2681a89e69d736d3 + React-jsinspectorcdp: 5fb266e5f23d3a2819ba848e9d4d0b6b00f95934 + React-jsinspectornetwork: 1655a81f3fe14789df41e063bd56dd130cc3562a + React-jsinspectortracing: 80e9418ac67630c76f15ef06534087037a822330 + React-jsitooling: 0c28fbc10441f8b63f4c6bf443cb36416500ce2b + React-jsitracing: ce443686f52538d1033ce7db1e7d643e866262f0 + React-logger: 116c3ae5a9906671d157aa00882a5ee75a5a7ebc + React-Mapbuffer: fc937cfa41140d7724c559c3d16c50dd725361c8 + React-microtasksnativemodule: dd4dfd6306d8b42f5dab9ecb3bf04124e979a3da + React-NativeModulesApple: d3aec3f4d3cb80507777e1feeba3bdc70f9504a0 + React-oscompat: 7133e0e945cda067ae36b22502df663d73002864 + React-perflogger: ada3cdf3dfc8b7cd1fabe3c91b672e23981611ab + React-performancetimeline: e7d5849d89ee39557dcd56dfb6e7b0d49003d925 + React-RCTActionSheet: 1bf8cc8086ad1c15da3407dfb7bc9dd94dc7595d + React-RCTAnimation: 263593e66c89bf810604b1ace15dfa382a1ca2df + React-RCTAppDelegate: 3d35d7226338009b22d1cf9621eaa827acb8fd1d + React-RCTBlob: 7b76230c53fe87d305eeeb250b0aae031bb6cbae + React-RCTFabric: a43fc393b6e505fd60a7fea43edbdcf609f33bf0 + React-RCTFBReactNativeSpec: 503491a0584dc29f03ef9f8ed366794604cd59ef + React-RCTImage: de404b6b0ebe53976a97e3a0dee819c83e12977b + React-RCTLinking: 06742cfad41c506091403a414370743a4ed75af3 + React-RCTNetwork: b4577eec0092c16d8996e415e4cac7a372d6d362 + React-RCTPushNotification: ea11178d499696516e0ff9ae335edbe99b06f94b + React-RCTRuntime: 07b41aed797e8d950ada851c6363ecf931335663 + React-RCTSettings: d3c2dd305ec81f7faf42762ec598d57f07fd43be + React-RCTTest: 2db46eda60bc2228cb67622a580e8e86b00088d9 + React-RCTText: e416825b80c530647040ef91d23ffd35ccc87981 + React-RCTVibration: 1837a27fc16eeffc9509779c3334fde54c012bcc + React-rendererconsistency: 777c894edc43dde01499189917ac54ee76ae6a6a + React-renderercss: a9cb6ba7f49a80dc4b4f7008bae1590d12f27049 + React-rendererdebug: fea8bde927403a198742b2d940a5f1cd8230c0b4 + React-rncore: 4a81ce7b8e47448973a6b29c765b07e01715921e + React-RuntimeApple: 97755a0b9f6adff1e1911ef4894cb0c5a9e40c77 + React-RuntimeCore: 6854e513a18b7b8980732f561679de6cab1b5b4d + React-runtimeexecutor: fb2d342a477bb13f7128cceb711ee8311edce0c0 + React-RuntimeHermes: 90f09fae56f0b1f458927beb171177a157334fe6 + React-runtimescheduler: 0b50423a4c40db7d1d0c3cc6893b407bf7fb946c + React-timing: 9d49179631e5e3c759e6e82d4c613c73da80a144 + React-utils: 3ea3fa757fec88afb26db14889fb4e7e8b5ca134 + ReactAppDependencyProvider: 68f2d2cefd6c9b9f2865246be2bfe86ebd49238d + ReactCodegen: 0c8d830ce35b1b48f8b674b0d00e532abc448470 + ReactCommon: a53973ab35d399560ace331ec9e2b26db0592cec + ReactCommon-Samples: 3dd174c775b04ab7d59a1b3c1a832e04377c9538 + ScreenshotManager: 8687f6358b007230590842b03505606e905c3ce9 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Yoga: afd04ff05ebe0121a00c468a8a3c8080221cb14c