From cf264cf7884cbd7748aeb814f5206e6375e976d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 24 Feb 2025 10:57:29 -0800 Subject: [PATCH 01/17] Refactor tracing in performance.mark and performance.measure (#49639) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49639 Changelog: [internal] This refactors some code in the performance API and Perfetto to avoid doing unnecessary work when not tracing (e.g.: string manipulation for tracks). It also reduces the cost of logging marks/measures to tracing backends if not actively tracing (e.g.: moving the check to outside the lock). Reviewed By: bgirard Differential Revision: D69246772 fbshipit-source-id: 141a13f609f12be7ab8ca00f7aa1892b34770bbb --- .../tracing/PerformanceTracer.cpp | 65 ++++++++++- .../tracing/PerformanceTracer.h | 18 ++- .../webperformance/NativePerformance.cpp | 63 +---------- .../react/performance/timeline/CMakeLists.txt | 1 + .../timeline/PerformanceEntryReporter.cpp | 106 ++++++++++++++++-- .../timeline/PerformanceEntryReporter.h | 5 +- .../React-performancetimeline.podspec | 1 + .../reactperflogger/ReactPerfettoLogger.cpp | 8 ++ .../reactperflogger/ReactPerfettoLogger.h | 2 + 9 files changed, 192 insertions(+), 77 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index dad7b7cd5582c1..85292e79583ccb 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -114,6 +114,10 @@ bool PerformanceTracer::stopTracingAndCollectEvents( void PerformanceTracer::reportMark( const std::string_view& name, uint64_t start) { + if (!tracing_) { + return; + } + std::lock_guard lock(mutex_); if (!tracing_) { return; @@ -135,6 +139,10 @@ void PerformanceTracer::reportMeasure( uint64_t start, uint64_t duration, const std::optional& trackMetadata) { + if (!tracing_) { + return; + } + std::lock_guard lock(mutex_); if (!tracing_) { return; @@ -161,9 +169,60 @@ 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 = 0, + .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 = 0, + .pid = processId_, + .tid = id, + .args = folly::dynamic::object("name", name), }); } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index 2a8ccb31967e72..dc0992f6686ec1 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -85,7 +85,23 @@ 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); diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index cff8e8678a77d5..14b0c3f7ef94ba 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -14,10 +14,8 @@ #include #include #include -#include #include #include -#include #include "NativePerformance.h" @@ -25,10 +23,6 @@ #include "Plugins.h" #endif -#ifdef WITH_PERFETTO -#include -#endif - std::shared_ptr NativePerformanceModuleProvider( std::shared_ptr jsInvoker) { return std::make_shared( @@ -39,33 +33,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( @@ -103,11 +70,7 @@ std::shared_ptr tryGetObserver( } // 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*/) { return JSExecutor::performanceNow(); @@ -117,12 +80,8 @@ double NativePerformance::markWithResult( 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; } @@ -134,26 +93,8 @@ std::tuple NativePerformance::measureWithResult( 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); - + name, startTime, endTime, duration, startMark, endMark); return std::tuple{entry.startTime, entry.duration}; } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index 335539e390a082..e44b66d14e76d8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -20,5 +20,6 @@ add_library(react_performance_timeline OBJECT ${react_performance_timeline_SRC}) target_include_directories(react_performance_timeline PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(react_performance_timeline jsinspector_tracing + reactperflogger react_timing react_cxxreact) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 36c947cda77a2f..f0e9d47512eaee 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -10,6 +10,11 @@ #include #include #include +#include + +#ifdef WITH_PERFETTO +#include +#endif namespace facebook::react { @@ -33,6 +38,33 @@ uint64_t timestampToMicroseconds(DOMHighResTimeStamp timestamp) { return static_cast(timestamp * 1000); } +#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); +} + } // namespace std::shared_ptr& @@ -42,7 +74,11 @@ PerformanceEntryReporter::getInstance() { } PerformanceEntryReporter::PerformanceEntryReporter() - : observerRegistry_(std::make_unique()) {} + : observerRegistry_(std::make_unique()) { +#ifdef WITH_PERFETTO + initializePerfetto(); +#endif +} DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() @@ -135,26 +171,28 @@ void PerformanceEntryReporter::clearEntries( PerformanceEntry PerformanceEntryReporter::reportMark( const std::string& name, const std::optional& startTime) { + // Resolve timings auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp(); const auto entry = PerformanceEntry{ .name = name, .entryType = PerformanceEntryType::MARK, .startTime = startTimeVal}; + traceMark(entry); + + // 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, + const std::string& name, DOMHighResTimeStamp startTime, DOMHighResTimeStamp endTime, const std::optional& duration, @@ -181,18 +219,16 @@ PerformanceEntry PerformanceEntryReporter::reportMeasure( .startTime = startTimeVal, .duration = durationVal}; + traceMeasure(entry); + + // 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; } @@ -257,4 +293,52 @@ void PerformanceEntryReporter::reportLongTask( observerRegistry_->queuePerformanceEntry(entry); } +void PerformanceEntryReporter::traceMark(const PerformanceEntry& entry) const { + auto& performanceTracer = + jsinspector_modern::PerformanceTracer::getInstance(); + if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) { + auto [trackName, eventName] = parseTrackName(entry.name); + + if (performanceTracer.isTracing()) { + performanceTracer.reportMark( + entry.name, timestampToMicroseconds(entry.startTime)); + } + + if (ReactPerfettoLogger::isTracing()) { + ReactPerfettoLogger::mark(eventName, entry.startTime, trackName); + } + } +} + +void PerformanceEntryReporter::traceMeasure( + const PerformanceEntry& entry) const { + auto& performanceTracer = + jsinspector_modern::PerformanceTracer::getInstance(); + if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) { + auto [trackName, eventName] = parseTrackName(entry.name); + + if (performanceTracer.isTracing()) { + std::optional + trackMetadata; + + if (trackName.has_value()) { + trackMetadata = {.track = trackName.value()}; + } + performanceTracer.reportMeasure( + eventName, + timestampToMicroseconds(entry.startTime), + timestampToMicroseconds(entry.duration), + trackMetadata); + } + + if (ReactPerfettoLogger::isTracing()) { + ReactPerfettoLogger::measure( + eventName, + entry.startTime, + entry.startTime + entry.duration, + trackName); + } + } +} + } // 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..8b907cd649386f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -82,7 +82,7 @@ class PerformanceEntryReporter { const std::optional& startTime = std::nullopt); PerformanceEntry reportMeasure( - const std::string_view& name, + const std::string& name, double startTime, double endTime, const std::optional& duration = std::nullopt, @@ -148,6 +148,9 @@ class PerformanceEntryReporter { } throw std::logic_error("Unhandled PerformanceEntryType"); } + + void traceMark(const PerformanceEntry& entry) const; + void traceMeasure(const PerformanceEntry& entry) const; }; } // namespace facebook::react 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..553c9e81cd77d4 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec +++ b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec @@ -55,5 +55,6 @@ Pod::Spec.new do |s| 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/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp index 3e8e50b320bad1..94ccfcc23f44f3 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp @@ -29,6 +29,14 @@ std::string toPerfettoTrackName( } // 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, diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h index c08c695035451d..a85d06a5b3900c 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h @@ -20,6 +20,8 @@ namespace facebook::react { */ class ReactPerfettoLogger { public: + static bool isTracing(); + static void mark( const std::string_view& eventName, double startTime, From 7de8ee7708d7fa198e9e9013671589752b6bf85b Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 3 Mar 2025 09:41:24 -0800 Subject: [PATCH 02/17] Introduce the `target_compile_reactnative_options` function (#49747) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49747 I'm adding the `target_compile_reactnative_options` to centralize the management of compilation flags for React Native CMake build. Changelog: [Internal] [Changed] - Reviewed By: javache Differential Revision: D70386746 fbshipit-source-id: aefbd5a073d9fab48d09ebbba6507377ef626d73 --- .../cmake-utils/ReactNative-application.cmake | 17 +-------- .../cmake-utils/react-native-flags.cmake | 38 +++++++++++++++++++ .../ReactAndroid/src/main/jni/CMakeLists.txt | 16 ++++---- .../jni/first-party/yogajni/CMakeLists.txt | 11 ++---- .../main/jni/react/devsupport/CMakeLists.txt | 5 ++- .../src/main/jni/react/fabric/CMakeLists.txt | 11 +----- .../jni/react/featureflags/CMakeLists.txt | 12 +----- .../hermes/instrumentation/CMakeLists.txt | 7 +--- .../react/hermes/reactexecutor/CMakeLists.txt | 10 ++--- .../src/main/jni/react/jni/CMakeLists.txt | 9 ++--- .../main/jni/react/mapbuffer/CMakeLists.txt | 5 ++- .../jni/react/newarchdefaults/CMakeLists.txt | 9 ++--- .../jni/react/reactperflogger/CMakeLists.txt | 4 +- .../runtime/cxxreactpackage/CMakeLists.txt | 11 ++---- .../react/runtime/hermes/jni/CMakeLists.txt | 12 ++---- .../main/jni/react/runtime/jni/CMakeLists.txt | 12 +++--- .../main/jni/react/turbomodule/CMakeLists.txt | 11 ++---- .../main/jni/react/uimanager/CMakeLists.txt | 5 ++- .../jni/third-party/fast_float/CMakeLists.txt | 3 +- .../main/jni/third-party/fmt/CMakeLists.txt | 3 +- .../react/performance/timeline/CMakeLists.txt | 11 ++---- 21 files changed, 104 insertions(+), 118 deletions(-) create mode 100644 packages/react-native/ReactAndroid/cmake-utils/react-native-flags.cmake diff --git a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake index c29cfe392abb15..a29949d801d286 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +++ b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake @@ -18,6 +18,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${CMAKE_CURRENT_LIST_DIR}/folly-flags.cmake) +include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) # We configured the REACT_COMMON_DIR variable as it's commonly used to reference # shared C++ code in other targets. @@ -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 "ReactNative") # Prefab packages from React Native find_package(ReactAndroid REQUIRED CONFIG) diff --git a/packages/react-native/ReactAndroid/cmake-utils/react-native-flags.cmake b/packages/react-native/ReactAndroid/cmake-utils/react-native-flags.cmake new file mode 100644 index 00000000000000..b446816a776736 --- /dev/null +++ b/packages/react-native/ReactAndroid/cmake-utils/react-native-flags.cmake @@ -0,0 +1,38 @@ +# 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 +) + +function(target_compile_reactnative_options target_name scope) + target_compile_options(${target_name} + ${scope} + -Wall + -fexceptions + -frtti + -std=c++20 + -DFOLLY_NO_CONFIG=1 + ) + set (extra_args ${ARGN}) + list(LENGTH extra_args extra_count) + set (tag "ReactNative") + if (${extra_count} GREATER 0) + list(GET extra_args 0 user_provided_tag) + target_compile_options(${target_name} ${scope} -DLOG_TAG=\"${user_provided_tag}\") + endif () +endfunction() + diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 1c5049ba237bc5..c51f966fd2320f 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_ANDROID_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/yogajni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/first-party/yogajni/CMakeLists.txt index 4c0b4c357c6fde..b2b15e3e4a2749 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_ANDROID_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..83e53c7d66ed24 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_ANDROID_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 "ReactNative") 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..b91422de395445 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_ANDROID_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 "Fabric") 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..1fd8e7f030f948 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_ANDROID_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 "ReactNative") 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..be2ba4cd9e39c5 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_ANDROID_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..e3ff600fedfa46 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_ANDROID_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/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt index c119c96d93c8e2..7b2e5850fef813 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_ANDROID_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/mapbuffer/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/mapbuffer/CMakeLists.txt index f246ef3e1c8836..9e7ae35e2fe922 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_ANDROID_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 "Fabric") 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..cd13470bcc592d 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_ANDROID_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 "ReactNative") 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..6692d99eeda736 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_ANDROID_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..8a5d85e2a1e409 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_ANDROID_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..ccd9ea30d8599a 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_ANDROID_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..34e157d83892b5 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_ANDROID_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/turbomodule/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/CMakeLists.txt index 6673e1b6668a57..c262c5b930e21a 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_ANDROID_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..43d087edafc850 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_ANDROID_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 "ReactNative") 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..7dbc925873d840 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_ANDROID_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..b8fbf6e2704b5f 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_ANDROID_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/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index e44b66d14e76d8..ba01a74068d3a2 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -6,17 +6,14 @@ 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_ANDROID_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 "ReactNative") +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 From 2da35d408884a6f0489cee03ede3caaca4b056f1 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Mon, 3 Mar 2025 09:41:24 -0800 Subject: [PATCH 03/17] Move react-native-flags.cmake to ReactCommon (#49745) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49745 It turns out we want those flags to be available also inside ReactCommon so I'm moving it there. This will allow us to reference them also inside ReactCommon and will make sure Common does not depend on Android Changelog: [Internal] [Changed] - Reviewed By: javache Differential Revision: D70386742 fbshipit-source-id: 3675c01f5e3f6515af6423d75e3fe145ba3d8936 --- .../cmake-utils/ReactNative-application.cmake | 2 +- .../ReactAndroid/src/main/jni/CMakeLists.txt | 2 +- .../src/main/jni/first-party/fbgloginit/CMakeLists.txt | 2 ++ .../main/jni/first-party/jni-lib-merge/CMakeLists.txt | 2 ++ .../src/main/jni/first-party/yogajni/CMakeLists.txt | 2 +- .../src/main/jni/react/devsupport/CMakeLists.txt | 2 +- .../src/main/jni/react/fabric/CMakeLists.txt | 2 +- .../src/main/jni/react/featureflags/CMakeLists.txt | 2 +- .../jni/react/hermes/instrumentation/CMakeLists.txt | 2 +- .../main/jni/react/hermes/reactexecutor/CMakeLists.txt | 2 +- .../src/main/jni/react/hermes/tooling/CMakeLists.txt | 1 + .../ReactAndroid/src/main/jni/react/jni/CMakeLists.txt | 2 +- .../src/main/jni/react/jscexecutor/CMakeLists.txt | 1 + .../src/main/jni/react/jsctooling/CMakeLists.txt | 1 + .../src/main/jni/react/mapbuffer/CMakeLists.txt | 2 +- .../src/main/jni/react/newarchdefaults/CMakeLists.txt | 2 +- .../src/main/jni/react/reactnativeblob/CMakeLists.txt | 1 + .../src/main/jni/react/reactperflogger/CMakeLists.txt | 2 +- .../jni/react/runtime/cxxreactpackage/CMakeLists.txt | 2 +- .../main/jni/react/runtime/hermes/jni/CMakeLists.txt | 2 +- .../src/main/jni/react/runtime/jni/CMakeLists.txt | 2 +- .../src/main/jni/react/runtime/jsc/jni/CMakeLists.txt | 1 + .../src/main/jni/react/turbomodule/CMakeLists.txt | 2 +- .../src/main/jni/react/uimanager/CMakeLists.txt | 2 +- .../jni/third-party/double-conversion/CMakeLists.txt | 2 +- .../src/main/jni/third-party/fast_float/CMakeLists.txt | 2 +- .../src/main/jni/third-party/fmt/CMakeLists.txt | 2 +- .../src/main/jni/third-party/glog/CMakeLists.txt | 10 +--------- .../cmake-utils/react-native-flags.cmake | 0 .../react/performance/timeline/CMakeLists.txt | 2 +- 30 files changed, 31 insertions(+), 30 deletions(-) rename packages/react-native/{ReactAndroid => ReactCommon}/cmake-utils/react-native-flags.cmake (100%) diff --git a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake index a29949d801d286..407a7f67181e85 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +++ b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake @@ -18,11 +18,11 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) include(${CMAKE_CURRENT_LIST_DIR}/folly-flags.cmake) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-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) diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index c51f966fd2320f..1f8395f08f49b6 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_VERBOSE_MAKEFILE on) project(ReactAndroid) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +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) 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 b2b15e3e4a2749..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,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB yoga_SRC CONFIGURE_DEPENDS jni/*.cpp) add_library(yoga OBJECT ${yoga_SRC}) 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 83e53c7d66ed24..dc628158dfef49 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 @@ -7,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB react_devsupportjni_SRC CONFIGURE_DEPENDS *.cpp) 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 b91422de395445..d826c27ac79c7a 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,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB fabricjni_SRCS CONFIGURE_DEPENDS *.cpp) 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 1fd8e7f030f948..114322733bf091 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,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB react_featureflagsjni_SRCS CONFIGURE_DEPENDS *.cpp) 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 be2ba4cd9e39c5..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,7 +9,7 @@ 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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library( jsijniprofiler 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 e3ff600fedfa46..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,7 +9,7 @@ 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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(hermes_executor OBJECT 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 7b2e5850fef813..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,7 +6,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB reactnativejni_SRC CONFIGURE_DEPENDS *.cpp) 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 9e7ae35e2fe922..8c17a35bd1ceed 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,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB mapbuffer_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/react/common/mapbuffer/*.cpp) 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 cd13470bcc592d..0ef2ae5ee8075a 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,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB react_newarchdefaults_SRC CONFIGURE_DEPENDS *.cpp) 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 6692d99eeda736..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) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(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 8a5d85e2a1e409..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 @@ -22,6 +22,6 @@ target_link_libraries(react_cxxreactpackage fb fbjni) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +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 ccd9ea30d8599a..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,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB_RECURSE hermes_instance_jni_SRC CONFIGURE_DEPENDS *.cpp) 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 34e157d83892b5..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,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) file(GLOB_RECURSE bridgeless_jni_SRC CONFIGURE_DEPENDS *.cpp) 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 c262c5b930e21a..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 @@ -7,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) ######################### ### callinvokerholder ### 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 43d087edafc850..56c9ecacdc371f 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 @@ -7,7 +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_ANDROID_DIR}/cmake-utils/react-native-flags.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 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 7dbc925873d840..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,7 +6,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(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 b8fbf6e2704b5f..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,7 +6,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) add_library(fmt STATIC src/format.cc) 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/ReactAndroid/cmake-utils/react-native-flags.cmake b/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake similarity index 100% rename from packages/react-native/ReactAndroid/cmake-utils/react-native-flags.cmake rename to packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index ba01a74068d3a2..424573f976ae36 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) -include(${REACT_ANDROID_DIR}/cmake-utils/react-native-flags.cmake) +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}) From 5052f6cc8d9f7699e120ac7841d794d0d98ac156 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 1 May 2025 22:40:39 -0700 Subject: [PATCH 04/17] Refactor PerformanceEntry as std::variant (#50995) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50995 Refactors our previous single-struct representation for `PerformanceEntry` ([see MDN](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry)) as a `std::variant`. This maps closely to the `PerformanceEntry` type inheritance in the web spec, and makes this type substantially cleaner to extend and work with in D73861431. Changelog: [Internal] Reviewed By: rubennorte, rshest Differential Revision: D73860532 fbshipit-source-id: f3b7a7444d2456370620c1a1ba9a43f118cb9730 --- .../webperformance/NativePerformance.cpp | 52 +++- .../webperformance/NativePerformance.h | 26 +- .../performance/timeline/CircularBuffer.h | 1 - .../performance/timeline/PerformanceEntry.h | 51 ++-- .../PerformanceEntryCircularBuffer.cpp | 15 +- .../timeline/PerformanceEntryKeyedBuffer.cpp | 7 +- .../timeline/PerformanceEntryKeyedBuffer.h | 1 - .../timeline/PerformanceEntryReporter.cpp | 49 ++-- .../timeline/PerformanceEntryReporter.h | 8 +- .../timeline/PerformanceObserver.cpp | 11 +- .../tests/PerformanceEntryReporterTest.cpp | 244 ++++++------------ .../tests/PerformanceObserverTest.cpp | 109 +++----- .../tests/RuntimeSchedulerTest.cpp | 23 +- .../performance/specs/NativePerformance.js | 2 +- 14 files changed, 285 insertions(+), 314 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 14b0c3f7ef94ba..19de170d6bcb9a 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -8,8 +8,8 @@ #include "NativePerformance.h" #include -#include #include +#include #include #include @@ -47,6 +47,40 @@ 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; + } + + 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}}; @@ -122,7 +156,7 @@ void NativePerformance::clearMeasures( } } -std::vector NativePerformance::getEntries( +std::vector NativePerformance::getEntries( jsi::Runtime& /*rt*/) { std::vector entries; @@ -132,10 +166,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) { @@ -155,10 +189,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; @@ -169,7 +203,7 @@ std::vector NativePerformance::getEntriesByType( sortEntries(entries); - return entries; + return toNativePerformanceEntries(entries); } std::vector> NativePerformance::getEventCounts( @@ -304,7 +338,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) { @@ -320,7 +354,7 @@ std::vector NativePerformance::takeRecords( if (sort) { sortEntries(records); } - return records; + return toNativePerformanceEntries(records); } std::vector diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index b1954e975d9d17..1d04c4e7086b4a 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -49,9 +49,23 @@ 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; + DOMHighResTimeStamp startTime; + DOMHighResTimeStamp duration; + + // For PerformanceEventTiming only + std::optional processingStart; + std::optional processingEnd; + std::optional interactionId; +}; + template <> -struct Bridging - : NativePerformanceRawPerformanceEntryBridging {}; +struct Bridging + : NativePerformanceRawPerformanceEntryBridging {}; template <> struct Bridging @@ -98,15 +112,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 +139,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. 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..b9f89c08c83019 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -8,9 +8,8 @@ #pragma once #include -#include #include -#include +#include namespace facebook::react { @@ -25,28 +24,50 @@ enum class PerformanceEntryType { _NEXT = 5, }; -struct PerformanceEntry { +struct AbstractPerformanceEntry { std::string name; - PerformanceEntryType entryType; DOMHighResTimeStamp startTime; DOMHighResTimeStamp duration = 0; +}; + +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; + DOMHighResTimeStamp processingStart; + DOMHighResTimeStamp processingEnd; + PerformanceEntryInteractionId interactionId; +}; - // For "event" entries only: - std::optional processingStart; - std::optional processingEnd; - std::optional interactionId; +struct PerformanceLongTaskTiming : AbstractPerformanceEntry { + static constexpr PerformanceEntryType entryType = + PerformanceEntryType::LONGTASK; }; -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>; 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/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 f0e9d47512eaee..658911b6aa3b23 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -16,6 +16,8 @@ #include #endif +#include + namespace facebook::react { namespace { @@ -168,15 +170,12 @@ void PerformanceEntryReporter::clearEntries( getBufferRef(entryType).clear(entryName); } -PerformanceEntry PerformanceEntryReporter::reportMark( +PerformanceMark PerformanceEntryReporter::reportMark( const std::string& name, const std::optional& startTime) { // Resolve timings auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp(); - const auto entry = PerformanceEntry{ - .name = name, - .entryType = PerformanceEntryType::MARK, - .startTime = startTimeVal}; + const auto entry = PerformanceMark{{.name = name, .startTime = startTimeVal}}; traceMark(entry); @@ -191,7 +190,7 @@ PerformanceEntry PerformanceEntryReporter::reportMark( return entry; } -PerformanceEntry PerformanceEntryReporter::reportMeasure( +PerformanceMeasure PerformanceEntryReporter::reportMeasure( const std::string& name, DOMHighResTimeStamp startTime, DOMHighResTimeStamp endTime, @@ -213,11 +212,10 @@ PerformanceEntry PerformanceEntryReporter::reportMeasure( DOMHighResTimeStamp durationVal = duration ? *duration : endTimeVal - startTimeVal; - const auto entry = PerformanceEntry{ - .name = std::string(name), - .entryType = PerformanceEntryType::MEASURE, - .startTime = startTimeVal, - .duration = durationVal}; + const auto entry = PerformanceMeasure{ + {.name = std::string(name), + .startTime = startTimeVal, + .duration = durationVal}}; traceMeasure(entry); @@ -237,7 +235,8 @@ DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( std::shared_lock lock(buffersMutex_); if (auto it = markBuffer_.find(markName); it) { - return it->startTime; + return std::visit( + [](const auto& entryData) { return entryData.startTime; }, *it); } else { return 0.0; } @@ -258,14 +257,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_); @@ -279,11 +275,10 @@ 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}; + const auto entry = PerformanceLongTaskTiming{ + {.name = std::string{"self"}, + .startTime = startTime, + .duration = duration}}; { std::unique_lock lock(buffersMutex_); @@ -293,7 +288,7 @@ void PerformanceEntryReporter::reportLongTask( observerRegistry_->queuePerformanceEntry(entry); } -void PerformanceEntryReporter::traceMark(const PerformanceEntry& entry) const { +void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { auto& performanceTracer = jsinspector_modern::PerformanceTracer::getInstance(); if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) { @@ -311,7 +306,7 @@ void PerformanceEntryReporter::traceMark(const PerformanceEntry& entry) const { } void PerformanceEntryReporter::traceMeasure( - const PerformanceEntry& entry) const { + const PerformanceMeasure& entry) const { auto& performanceTracer = jsinspector_modern::PerformanceTracer::getInstance(); if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 8b907cd649386f..650a7ec346057b 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -77,11 +77,11 @@ class PerformanceEntryReporter { return eventCounts_; } - PerformanceEntry reportMark( + PerformanceMark reportMark( const std::string& name, const std::optional& startTime = std::nullopt); - PerformanceEntry reportMeasure( + PerformanceMeasure reportMeasure( const std::string& name, double startTime, double endTime, @@ -149,8 +149,8 @@ class PerformanceEntryReporter { throw std::logic_error("Unhandled PerformanceEntryType"); } - void traceMark(const PerformanceEntry& entry) const; - void traceMeasure(const PerformanceEntry& entry) const; + void traceMark(const PerformanceMark& entry) const; + void traceMeasure(const PerformanceMeasure& entry) 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/tests/PerformanceEntryReporterTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp index d11642a68765ed..bd77d6189e5221 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,8 @@ #include "../PerformanceEntryReporter.h" +#include + using namespace facebook::react; namespace facebook::react { @@ -18,11 +20,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); } [[maybe_unused]] static std::ostream& operator<<( @@ -34,11 +52,18 @@ namespace facebook::react { "PerformanceEntryType::MEASURE", "PerformanceEntryType::EVENT", }; - return os << "{ .name = \"" << entry.name << "\"" << ", .entryType = " - << entryTypeNames[static_cast(entry.entryType)] - << ", .startTime = " << entry.startTime - << ", .duration = " << entry.duration << " }"; + + return std::visit( + [&](const auto& entryDetails) -> std::ostream& { + os << "{ .name = \"" << entryDetails.name << "\"" << ", .entryType = " + << entryTypeNames[static_cast(entryDetails.entryType) - 1] + << ", .startTime = " << entryDetails.startTime + << ", .duration = " << entryDetails.duration << " }"; + return os; + }, + entry); } + } // namespace facebook::react namespace { @@ -65,22 +90,10 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { 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 = 0, .duration = 0}}, + PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, + PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, + PerformanceMark{{.name = "mark0", .startTime = 3, .duration = 0}}}; ASSERT_EQ(expected, entries); } @@ -113,62 +126,21 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { 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 = 0, .duration = 0}}, + PerformanceMeasure{{.name = "measure0", .startTime = 0, .duration = 2}}, + PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 4}}, + PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, + PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 1}}, + PerformanceMeasure{{.name = "measure7", .startTime = 1, .duration = 2}}, + PerformanceMeasure{{.name = "measure3", .startTime = 1, .duration = 5}}, + PerformanceMeasure{ + {.name = "measure4", .startTime = 1.5, .duration = 0.5}}, + PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, + PerformanceMeasure{{.name = "measure6", .startTime = 2, .duration = 0}}, + PerformanceMeasure{{.name = "measure5", .startTime = 2, .duration = 1.5}}, + PerformanceMark{{.name = "mark4", .startTime = 2.1, .duration = 0}}, + PerformanceMark{{.name = "mark3", .startTime = 2.5, .duration = 0}}, + PerformanceMark{{.name = "mark4", .startTime = 3, .duration = 0}}}; ASSERT_EQ(expected, entries); } @@ -196,38 +168,16 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { { 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 = 0, .duration = 0}}, + PerformanceMeasure{ + {.name = "common_name", .startTime = 0, .duration = 2}}, + PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 4}}, + PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, + PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 1}}, + PerformanceMeasure{{.name = "measure3", .startTime = 1, .duration = 5}}, + PerformanceMeasure{ + {.name = "measure4", .startTime = 1.5, .duration = 0.5}}, + PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}}; ASSERT_EQ(expected, allEntries); } @@ -235,18 +185,9 @@ 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 = 0, .duration = 0}}, + PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, + PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}}; ASSERT_EQ(expected, marks); } @@ -254,45 +195,27 @@ 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 = 0, .duration = 2}}, + PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 4}}, + PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 1}}, + PerformanceMeasure{{.name = "measure3", .startTime = 1, .duration = 5}}, + PerformanceMeasure{ + {.name = "measure4", .startTime = 1.5, .duration = 0.5}}}; ASSERT_EQ(expected, measures); } { const std::vector expected = { - {.name = "common_name", - .entryType = PerformanceEntryType::MARK, - .startTime = 0}}; + PerformanceMark{{.name = "common_name", .startTime = 0}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MARK, "common_name"); ASSERT_EQ(expected, commonName); } { - const std::vector expected = { - {.name = "common_name", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 2}}; + const std::vector expected = {PerformanceMeasure{ + {.name = "common_name", .startTime = 0, .duration = 2}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MEASURE, "common_name"); ASSERT_EQ(expected, commonName); @@ -320,18 +243,9 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) { { 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 = 1, .duration = 0}}, + PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, + PerformanceMark{{.name = "mark1", .startTime = 2.1, .duration = 0}}, }; 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..06cf2c3e09bffe 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 @@ -127,15 +143,9 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) { reporter->reportMark("test3", 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 = 10}}, + PerformanceMark{{.name = "test2", .startTime = 20}}, + PerformanceMark{{.name = "test3", .startTime = 30}}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -157,24 +167,9 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveDurationThreshold) { reporter->reportEvent("test3", 0, 60, 0, 0, 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", .duration = 50}, 0, 0, 0}, + PerformanceEventTiming{{.name = "test2", .duration = 100}, 0, 0, 0}, + PerformanceEventTiming{{.name = "test3", .duration = 60}, 0, 0, 0}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -197,27 +192,13 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveBuffered) { PerformanceEntryType::EVENT, {.buffered = true, .durationThreshold = 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 = 0, .duration = 50}, 0, 0, 0}, + PerformanceEventTiming{ + {.name = "test2", .startTime = 0, .duration = 100}, 0, 0, 0}, + PerformanceEventTiming{ + {.name = "test4", .startTime = 0, .duration = 100}, 0, 0, 0}, + }; ASSERT_EQ(expected, observer->takeRecords()); @@ -242,27 +223,13 @@ TEST(PerformanceObserver, PerformanceObserverTestMultiple) { reporter->reportEvent("event3", 0, 60, 0, 0, 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", .duration = 100}, 0, 0, 0}, + PerformanceEventTiming{{.name = "event3", .duration = 60}, 0, 0, 0}, }; const std::vector expected2 = { - {.name = "event1", - .entryType = PerformanceEntryType::EVENT, - .duration = 100, - .processingStart = 0, - .processingEnd = 0, - .interactionId = 0}}; + PerformanceEventTiming{{.name = "event1", .duration = 100}, 0, 0, 0}, + }; ASSERT_EQ(expected1, observer1->takeRecords()); ASSERT_EQ(expected2, observer2->takeRecords()); 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..ead7beadfe29e1 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "StubClock.h" #include "StubErrorUtils.h" @@ -1246,9 +1247,14 @@ 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, 100); + EXPECT_EQ(entryDetails.duration, 50); + }, + entry); } TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { @@ -1326,9 +1332,14 @@ 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, 100); + EXPECT_EQ(entryDetails.duration, 120); + }, + entry); } INSTANTIATE_TEST_SUITE_P( 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..a504e89e93013c 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -24,7 +24,7 @@ export type RawPerformanceEntry = { startTime: number, duration: number, - // For "event" entries only: + // For PerformanceEventTiming only processingStart?: number, processingEnd?: number, interactionId?: number, From 7e1b595aca19b95145bd75f0dcc868cf94cc16b5 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 1 May 2025 22:40:39 -0700 Subject: [PATCH 05/17] Add internal support for PerformanceResourceTiming (#50996) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50996 Adds a representation of the [`PerformanceResourceTiming`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming) event type to the Web Performance subsystem, ahead of integration with our network event reporting. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D73861431 fbshipit-source-id: 8258058c432727808d30c338bba79ca92a17c1c8 --- .../__snapshots__/public-api-test.js.snap | 201 ++++++++++++++++++ .../webperformance/NativePerformance.cpp | 10 + .../webperformance/NativePerformance.h | 9 + .../performance/timeline/PerformanceEntry.h | 21 +- .../timeline/PerformanceEntryReporter.h | 5 + .../tests/PerformanceEntryReporterTest.cpp | 1 + .../webapis/performance/PerformanceEntry.js | 7 +- .../performance/RawPerformanceEntry.js | 3 + .../performance/specs/NativePerformance.js | 9 + 9 files changed, 263 insertions(+), 3 deletions(-) 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/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 19de170d6bcb9a..50165c3b17b6db 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -65,6 +65,16 @@ NativePerformanceEntry toNativePerformanceEntry(const PerformanceEntry& entry) { 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; } diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index 1d04c4e7086b4a..7723a1a2c81cff 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -61,6 +61,15 @@ struct NativePerformanceEntry { 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 <> diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index b9f89c08c83019..45a7044934c725 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -21,7 +22,8 @@ enum class PerformanceEntryType { MEASURE = 2, EVENT = 3, LONGTASK = 4, - _NEXT = 5, + RESOURCE = 5, + _NEXT = 6, }; struct AbstractPerformanceEntry { @@ -51,11 +53,26 @@ struct PerformanceLongTaskTiming : AbstractPerformanceEntry { PerformanceEntryType::LONGTASK; }; +struct PerformanceResourceTiming : AbstractPerformanceEntry { + static constexpr PerformanceEntryType entryType = + PerformanceEntryType::RESOURCE; + /** Aligns with `startTime`. */ + std::optional fetchStart; + std::optional requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; + /** Aligns with `duration`. */ + std::optional responseEnd; + std::optional responseStatus; +}; + using PerformanceEntry = std::variant< PerformanceMark, PerformanceMeasure, PerformanceEventTiming, - PerformanceLongTaskTiming>; + PerformanceLongTaskTiming, + PerformanceResourceTiming>; struct PerformanceEntrySorter { bool operator()(const PerformanceEntry& lhs, const PerformanceEntry& rhs) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 650a7ec346057b..a8a75fbfcd6b7d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -109,6 +109,7 @@ class PerformanceEntryReporter { PerformanceEntryCircularBuffer longTaskBuffer_{LONG_TASK_BUFFER_SIZE}; PerformanceEntryKeyedBuffer markBuffer_; PerformanceEntryKeyedBuffer measureBuffer_; + PerformanceEntryKeyedBuffer resourceBuffer_; std::unordered_map eventCounts_; @@ -127,6 +128,8 @@ class PerformanceEntryReporter { return measureBuffer_; case PerformanceEntryType::LONGTASK: return longTaskBuffer_; + case PerformanceEntryType::RESOURCE: + return resourceBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } @@ -143,6 +146,8 @@ class PerformanceEntryReporter { return measureBuffer_; case PerformanceEntryType::LONGTASK: return longTaskBuffer_; + case PerformanceEntryType::RESOURCE: + return resourceBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } 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 bd77d6189e5221..2bce5af730cde7 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -51,6 +51,7 @@ namespace facebook::react { "PerformanceEntryType::MARK", "PerformanceEntryType::MEASURE", "PerformanceEntryType::EVENT", + "PerformanceEntryType::RESOURCE", }; return std::visit( 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..e95762a217f632 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( @@ -95,6 +96,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/specs/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js index a504e89e93013c..809037e8a731e4 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -28,6 +28,15 @@ export type RawPerformanceEntry = { 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; From 6bfd829185c50bacc08e514e0b7ea707495be9fd Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 1 May 2025 22:40:39 -0700 Subject: [PATCH 06/17] Refactor performanceNow call, remove cxxreact dependency (#51024) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51024 Prevents a dependency cycle in the next diff. Changelog: [Internal] Reviewed By: rubennorte, hoxyq Differential Revision: D73933671 fbshipit-source-id: 79c66cd5f6a32ed7287a2d6e7840a512466306a9 --- .../ReactCommon/react/performance/timeline/CMakeLists.txt | 3 +-- .../performance/timeline/PerformanceEntryReporter.cpp | 8 ++++++-- .../timeline/React-performancetimeline.podspec | 1 - 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index 424573f976ae36..c507e4fd449ec6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -18,5 +18,4 @@ target_include_directories(react_performance_timeline PUBLIC ${REACT_COMMON_DIR} target_link_libraries(react_performance_timeline jsinspector_tracing reactperflogger - react_timing - react_cxxreact) + react_timing) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 658911b6aa3b23..b6e9af0370f1b7 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -7,9 +7,9 @@ #include "PerformanceEntryReporter.h" -#include #include #include +#include #include #ifdef WITH_PERFETTO @@ -40,6 +40,10 @@ uint64_t timestampToMicroseconds(DOMHighResTimeStamp timestamp) { return static_cast(timestamp * 1000); } +double performanceNow() { + return chronoToDOMHighResTimeStamp(std::chrono::steady_clock::now()); +} + #if defined(__clang__) #define NO_DESTROY [[clang::no_destroy]] #else @@ -84,7 +88,7 @@ PerformanceEntryReporter::PerformanceEntryReporter() DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() - : JSExecutor::performanceNow(); + : performanceNow(); } std::vector 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 553c9e81cd77d4..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,7 +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 From b3fe48b148fac71955c5063b7aadb844c56c3b34 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Fri, 2 May 2025 08:14:40 -0700 Subject: [PATCH 07/17] Report PerformanceResourceTiming events (#51025) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51025 (Sparsely) wires up reporting of Network events to the Web Performance subsystem. Our plan is to report to the Web Performance APIs (lightweight timing metadata, here) for all build flavours, and report to CDP (more costly full metadata/previews) in dev/profiling builds. **Notes** - Introduces `PerformanceEntryReporter::unstable_reportResourceTiming` — this will become "stable" when further network events/fields are fully hydrated on Android and iOS. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D73922341 fbshipit-source-id: bcfc03c3d8a9a286ae72ba00a3313602fb2adea8 --- .../jsinspector-modern/network/CMakeLists.txt | 28 ++ .../network/NetworkReporter.cpp | 275 ++++++++++++++++++ .../network/NetworkReporter.h | 162 +++++++++++ .../network/React-jsinspectornetwork.podspec | 55 ++++ .../react/performance/timeline/CMakeLists.txt | 1 + .../performance/timeline/PerformanceEntry.h | 4 +- .../timeline/PerformanceEntryReporter.cpp | 35 ++- .../timeline/PerformanceEntryReporter.h | 20 +- .../performance/RawPerformanceEntry.js | 2 + packages/rn-tester/Podfile.lock | 173 +++++++---- 10 files changed, 684 insertions(+), 71 deletions(-) create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec 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..1eb6e19f20f6d6 --- /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()) { + double 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()) { + double 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()) { + double 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()) { + double 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()) { + double 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..4227deca3df0b8 --- /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; + DOMHighResTimeStamp fetchStart; + DOMHighResTimeStamp 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/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index c507e4fd449ec6..4de6a14a509859 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -18,4 +18,5 @@ target_include_directories(react_performance_timeline PUBLIC ${REACT_COMMON_DIR} target_link_libraries(react_performance_timeline jsinspector_tracing reactperflogger + react_featureflags react_timing) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index 45a7044934c725..e794902f85dc86 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -57,8 +57,8 @@ struct PerformanceResourceTiming : AbstractPerformanceEntry { static constexpr PerformanceEntryType entryType = PerformanceEntryType::RESOURCE; /** Aligns with `startTime`. */ - std::optional fetchStart; - std::optional requestStart; + DOMHighResTimeStamp fetchStart; + DOMHighResTimeStamp requestStart; std::optional connectStart; std::optional connectEnd; std::optional responseStart; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index b6e9af0370f1b7..06cc6f9f2a24a4 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -29,8 +29,8 @@ std::vector getSupportedEntryTypesInternal() { PerformanceEntryType::EVENT, }; - if (ReactNativeFeatureFlags::enableLongTaskAPI()) { - supportedEntryTypes.emplace_back(PerformanceEntryType::LONGTASK); + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + supportedEntryTypes.emplace_back(PerformanceEntryType::RESOURCE); } return supportedEntryTypes; @@ -292,6 +292,37 @@ void PerformanceEntryReporter::reportLongTask( observerRegistry_->queuePerformanceEntry(entry); } +PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( + const std::string& url, + DOMHighResTimeStamp fetchStart, + DOMHighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + DOMHighResTimeStamp responseStart, + DOMHighResTimeStamp 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); + + return entry; +} + void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { auto& performanceTracer = jsinspector_modern::PerformanceTracer::getInstance(); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index a8a75fbfcd6b7d..fba99695551f42 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -21,8 +21,11 @@ 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; @@ -101,15 +104,26 @@ class PerformanceEntryReporter { void reportLongTask(double startTime, double duration); + PerformanceResourceTiming reportResourceTiming( + const std::string& url, + DOMHighResTimeStamp fetchStart, + DOMHighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + DOMHighResTimeStamp responseStart, + DOMHighResTimeStamp responseEnd, + const std::optional& responseStatus); + private: std::unique_ptr observerRegistry_; 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_; - PerformanceEntryKeyedBuffer resourceBuffer_; std::unordered_map eventCounts_; @@ -129,7 +143,7 @@ class PerformanceEntryReporter { case PerformanceEntryType::LONGTASK: return longTaskBuffer_; case PerformanceEntryType::RESOURCE: - return resourceBuffer_; + return resourceTimingBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } @@ -147,7 +161,7 @@ class PerformanceEntryReporter { case PerformanceEntryType::LONGTASK: return longTaskBuffer_; case PerformanceEntryType::RESOURCE: - return resourceBuffer_; + return resourceTimingBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } diff --git a/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js b/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js index e95762a217f632..14f224e28b09d9 100644 --- a/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js @@ -77,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}`, 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 From e82de26c8c551ab0e9ddd2e4be667848c288794e Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Mon, 19 May 2025 15:24:36 -0700 Subject: [PATCH 08/17] Validate marks presense, if specified (#51389) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51389 This is the pre-requisite for the diff on top, which migrates performance-related classes to start using `HighResTimeStamp`. We should be throwing `SyntaxError` if the specified mark name is not buffered. Like Chromium does: {F1978032319} In this diff: - `PerformanceEntryReporter::getMarkTime` is now public, ca be called by `NativePerformance` and returns `std::optional`. - `NativePerformance` is responsible for validating that marks are present in the buffer, if their names are specified in `.measure()` call. - Mark names take precedence over `startTime` and `endTime` values, so if they are specified and not available, then we will throw Error over `jsi` that will be catched on JavaScript side in `Performance.js`. Reviewed By: rubennorte Differential Revision: D74837812 fbshipit-source-id: ca2ba198c4c9e6e2d8d37f852affea667f1c174c --- .../webperformance/NativePerformance.cpp | 37 ++++++++++++- .../timeline/PerformanceEntryReporter.cpp | 26 ++------- .../timeline/PerformanceEntryReporter.h | 7 +-- .../tests/PerformanceEntryReporterTest.cpp | 55 ++++++------------- .../webapis/performance/Performance.js | 25 ++++++--- .../__tests__/Performance-itest.js | 39 +++++++++++++ 6 files changed, 114 insertions(+), 75 deletions(-) create mode 100644 packages/react-native/src/private/webapis/performance/__tests__/Performance-itest.js diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 50165c3b17b6db..e8b60299d66603 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -130,15 +130,46 @@ double NativePerformance::markWithResult( } std::tuple NativePerformance::measureWithResult( - jsi::Runtime& rt, + jsi::Runtime& runtime, std::string name, double startTime, double endTime, std::optional duration, std::optional startMark, std::optional endMark) { - auto entry = PerformanceEntryReporter::getInstance()->reportMeasure( - name, startTime, endTime, duration, startMark, endMark); + auto reporter = PerformanceEntryReporter::getInstance(); + + DOMHighResTimeStamp startTimeValue = startTime; + // If the start time mark name is specified, it takes precedence over the + // startTime parameter, which can be set to 0 by default from JavaScript. + if (startMark) { + if (auto startMarkBufferedTime = reporter->getMarkTime(*startMark)) { + startTimeValue = *startMarkBufferedTime; + } else { + throw jsi::JSError( + runtime, "The mark '" + *startMark + "' does not exist."); + } + } + + DOMHighResTimeStamp endTimeValue = endTime; + // If the end time mark name is specified, it takes precedence over the + // startTime parameter, which can be set to 0 by default from JavaScript. + if (endMark) { + if (auto endMarkBufferedTime = reporter->getMarkTime(*endMark)) { + endTimeValue = *endMarkBufferedTime; + } else { + throw jsi::JSError( + runtime, "The mark '" + *endMark + "' does not exist."); + } + } else if (duration) { + endTimeValue = startTimeValue + *duration; + } else if (endTimeValue < startTimeValue) { + // The end time is not specified, take the current time, according to the + // standard + endTimeValue = reporter->getCurrentTimeStamp(); + } + + auto entry = reporter->reportMeasure(name, startTime, endTime); return std::tuple{entry.startTime, entry.duration}; } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 06cc6f9f2a24a4..36680afb7e8f00 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -198,28 +198,14 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure( const std::string& 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; + DOMHighResTimeStamp duration = endTime - startTime; const auto entry = PerformanceMeasure{ {.name = std::string(name), - .startTime = startTimeVal, - .duration = durationVal}}; + .startTime = startTime, + .duration = duration}}; traceMeasure(entry); @@ -234,16 +220,16 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure( return entry; } -DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( +std::optional PerformanceEntryReporter::getMarkTime( const std::string& markName) const { std::shared_lock lock(buffersMutex_); if (auto it = markBuffer_.find(markName); it) { return std::visit( [](const auto& entryData) { return entryData.startTime; }, *it); - } else { - return 0.0; } + + return std::nullopt; } void PerformanceEntryReporter::reportEvent( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index fba99695551f42..549fe6f591a724 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -80,6 +80,8 @@ class PerformanceEntryReporter { return eventCounts_; } + std::optional getMarkTime(const std::string& markName) const; + PerformanceMark reportMark( const std::string& name, const std::optional& startTime = std::nullopt); @@ -88,9 +90,6 @@ class PerformanceEntryReporter { const std::string& 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); @@ -129,8 +128,6 @@ class PerformanceEntryReporter { std::function timeStampProvider_ = nullptr; - double getMarkTime(const std::string& markName) const; - const inline PerformanceEntryBuffer& getBuffer( PerformanceEntryType entryType) const { switch (entryType) { 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 2bce5af730cde7..5217debd97d262 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -108,38 +108,21 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { reporter->reportMark("mark2", 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"); + reporter->reportMeasure("measure1", 0, 3); reporter->reportMark("mark3", 2.5); - reporter->reportMeasure("measure6", 2.0, 2.0); - reporter->reportMark("mark4", 2.1); + reporter->reportMeasure("measure2", 2.0, 2.0); reporter->reportMark("mark4", 3.0); - // Uses the last reported time for mark4 - reporter->reportMeasure("measure7", 0, 0, std::nullopt, "mark1", "mark4"); const auto entries = toSorted(reporter->getEntries()); const std::vector expected = { PerformanceMark{{.name = "mark0", .startTime = 0, .duration = 0}}, PerformanceMeasure{{.name = "measure0", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 4}}, + PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 1}}, - PerformanceMeasure{{.name = "measure7", .startTime = 1, .duration = 2}}, - PerformanceMeasure{{.name = "measure3", .startTime = 1, .duration = 5}}, - PerformanceMeasure{ - {.name = "measure4", .startTime = 1.5, .duration = 0.5}}, PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, - PerformanceMeasure{{.name = "measure6", .startTime = 2, .duration = 0}}, - PerformanceMeasure{{.name = "measure5", .startTime = 2, .duration = 1.5}}, - PerformanceMark{{.name = "mark4", .startTime = 2.1, .duration = 0}}, + PerformanceMeasure{{.name = "measure2", .startTime = 2, .duration = 0}}, PerformanceMark{{.name = "mark3", .startTime = 2.5, .duration = 0}}, PerformanceMark{{.name = "mark4", .startTime = 3, .duration = 0}}}; @@ -160,11 +143,9 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { reporter->reportMark("mark2", 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"); + reporter->reportMeasure("measure1", 0, 3); + reporter->reportMeasure("measure2", 1, 6); + reporter->reportMeasure("measure3", 1.5, 2); { const auto allEntries = toSorted(reporter->getEntries()); @@ -172,12 +153,11 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { PerformanceMark{{.name = "common_name", .startTime = 0, .duration = 0}}, PerformanceMeasure{ {.name = "common_name", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 4}}, + PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 1}}, - PerformanceMeasure{{.name = "measure3", .startTime = 1, .duration = 5}}, + PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 5}}, PerformanceMeasure{ - {.name = "measure4", .startTime = 1.5, .duration = 0.5}}, + {.name = "measure3", .startTime = 1.5, .duration = 0.5}}, PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}}; ASSERT_EQ(expected, allEntries); } @@ -198,11 +178,10 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { const std::vector expected = { PerformanceMeasure{ {.name = "common_name", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 4}}, - PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 1}}, - PerformanceMeasure{{.name = "measure3", .startTime = 1, .duration = 5}}, + PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, + PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 5}}, PerformanceMeasure{ - {.name = "measure4", .startTime = 1.5, .duration = 0.5}}}; + {.name = "measure3", .startTime = 1.5, .duration = 0.5}}}; ASSERT_EQ(expected, measures); } @@ -233,11 +212,9 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) { reporter->reportMark("mark2", 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"); + reporter->reportMeasure("measure1", 0, 3); + reporter->reportMeasure("measure2", 1, 6); + reporter->reportMeasure("measure3", 1.5, 2); reporter->clearEntries(PerformanceEntryType::MARK, "common_name"); diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index c84b3964feb022..086baf75ae89ef 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 { @@ -191,15 +193,22 @@ export default class Performance { let computedDuration = duration; if (NativePerformance?.measureWithResult) { - [computedStartTime, computedDuration] = - NativePerformance.measureWithResult( - measureName, - startTime, - endTime, - duration, - startMarkName, - endMarkName, + try { + [computedStartTime, computedDuration] = + NativePerformance.measureWithResult( + measureName, + startTime, + endTime, + duration, + startMarkName, + endMarkName, + ); + } catch (error) { + throw new DOMException( + "Failed to execute 'measure' on 'Performance': " + error.message, + 'SyntaxError', ); + } } else { warnNoNativePerformance(); } 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(); + }); +}); From 37b7d4838b069b6ce3cb76a90f7ce0c396395ee3 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 22 May 2025 10:16:45 -0700 Subject: [PATCH 09/17] Use HighResTimeStamp (#51511) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51511 There are multiple changes: 1. `PerformanceTracer` class, `TraceEvent` struct are moved to `tracing` namespace. These are parts of the Tracing subsystems of the jsinspector, this should bring more clarity and make things more explicit. 2. Added `Timing.h` class which defines conversion logic from `HighResTimeStamp` to absolute units that are expected by CDP. 3. `PerformanceTracer` will receive timestamps for Performance Web API entries in `HighResTimeStamp`. Also, we will explicilty define a Tracking Clock time origin that will be epoch of the `steady_clock`. This aligns with the approach in Chromium and saves us from aligning custom DOMHighResTimeStamps that can be specified in performance.mark / performance.measure calls: these should not extend the timeline window. I've confirmed that this is the current behavior in Chromium. Reviewed By: rubennorte, huntie Differential Revision: D75185467 fbshipit-source-id: 37444392f12e8c9c4479c47c42b2c4badca7ecfd --- .../jsinspector-modern/RuntimeTarget.cpp | 35 +++ .../jsinspector-modern/TracingAgent.cpp | 30 +- .../jsinspector-modern/TracingAgent.h | 18 ++ .../jsinspector-modern/tracing/CMakeLists.txt | 2 + .../tracing/EventLoopReporter.cpp | 48 +++ .../tracing/EventLoopReporter.h | 39 +++ .../tracing/PerformanceTracer.cpp | 185 ++++++++--- .../tracing/PerformanceTracer.h | 64 +++- .../tracing/React-jsinspectortracing.podspec | 5 +- ...imeSamplingProfileTraceEventSerializer.cpp | 264 ++++++++++++++++ ...ntimeSamplingProfileTraceEventSerializer.h | 165 ++++++++++ .../jsinspector-modern/tracing/Timing.h | 42 +++ .../jsinspector-modern/tracing/TraceEvent.h | 63 ++++ .../tracing/TraceEventProfile.h | 113 +++++++ ...amplingProfileTraceEventSerializerTest.cpp | 290 ++++++++++++++++++ .../timeline/PerformanceEntryReporter.cpp | 15 +- 16 files changed, 1314 insertions(+), 64 deletions(-) create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/Timing.h create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h create mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp 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/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/EventLoopReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp new file mode 100644 index 00000000000000..0c9ccba02086f2 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp @@ -0,0 +1,48 @@ +/* + * 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 "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 85292e79583ccb..bd0bfd74326102 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -6,23 +6,14 @@ */ #include "PerformanceTracer.h" +#include "Timing.h" #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 +21,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 +67,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,7 +111,7 @@ bool PerformanceTracer::stopTracingAndCollectEvents( void PerformanceTracer::reportMark( const std::string_view& name, - uint64_t start) { + HighResTimeStamp start) { if (!tracing_) { return; } @@ -136,8 +134,8 @@ void PerformanceTracer::reportMark( void PerformanceTracer::reportMeasure( const std::string_view& name, - uint64_t start, - uint64_t duration, + HighResTimeStamp start, + HighResDuration duration, const std::optional& trackMetadata) { if (!tracing_) { return; @@ -198,7 +196,7 @@ void PerformanceTracer::reportProcess(uint64_t id, const std::string& name) { .name = "process_name", .cat = "__metadata", .ph = 'M', - .ts = 0, + .ts = TRACING_TIME_ORIGIN, .pid = id, .tid = 0, .args = folly::dynamic::object("name", name), @@ -219,28 +217,129 @@ void PerformanceTracer::reportThread(uint64_t id, const std::string& name) { .name = "thread_name", .cat = "__metadata", .ph = 'M', - .ts = 0, + .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 dc0992f6686ec1..448f096b19fbfa 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -9,14 +9,15 @@ #include "CdpTracing.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). @@ -111,7 +112,7 @@ 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); /** * Record a `Performance.measure()` event - a labelled duration. If not @@ -121,10 +122,57 @@ class PerformanceTracer { */ void reportMeasure( const std::string_view& name, - uint64_t start, - uint64_t duration, + HighResTimeStamp start, + HighResDuration duration, const std::optional& trackMetadata); + /** + * 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; PerformanceTracer(const PerformanceTracer&) = delete; @@ -134,9 +182,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/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 36680afb7e8f00..669d02d756964d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -36,10 +36,6 @@ std::vector getSupportedEntryTypesInternal() { return supportedEntryTypes; } -uint64_t timestampToMicroseconds(DOMHighResTimeStamp timestamp) { - return static_cast(timestamp * 1000); -} - double performanceNow() { return chronoToDOMHighResTimeStamp(std::chrono::steady_clock::now()); } @@ -311,13 +307,14 @@ PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { auto& performanceTracer = - jsinspector_modern::PerformanceTracer::getInstance(); + jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) { auto [trackName, eventName] = parseTrackName(entry.name); if (performanceTracer.isTracing()) { performanceTracer.reportMark( - entry.name, timestampToMicroseconds(entry.startTime)); + entry.name, + HighResTimeStamp::fromDOMHighResTimeStamp(entry.startTime)); } if (ReactPerfettoLogger::isTracing()) { @@ -329,7 +326,7 @@ void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { void PerformanceEntryReporter::traceMeasure( const PerformanceMeasure& entry) const { auto& performanceTracer = - jsinspector_modern::PerformanceTracer::getInstance(); + jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) { auto [trackName, eventName] = parseTrackName(entry.name); @@ -342,8 +339,8 @@ void PerformanceEntryReporter::traceMeasure( } performanceTracer.reportMeasure( eventName, - timestampToMicroseconds(entry.startTime), - timestampToMicroseconds(entry.duration), + HighResTimeStamp::fromDOMHighResTimeStamp(entry.startTime), + HighResDuration::fromDOMHighResTimeStamp(entry.duration), trackMetadata); } From 579f821cdad1e7cc35cb409f334cff70ce0047f8 Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Thu, 22 May 2025 10:16:45 -0700 Subject: [PATCH 10/17] Replace DOMHighResTimeStamp alias in ReactCommon with new abstractions (#51512) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51512 Pull Request resolved: https://github.com/facebook/react-native/pull/50585 Replaces `DOMHighResTimeStamp` alias completely in `ReactCommon` with `HighResTimeStamp`. `DOMHighResTimeStamp` as a type is now expected to be used only in JavaScript. I didn't update places where we explcitly use `std::chrono::high_resolution_clock`, since it is platform-specific and there is no guarantee that `std::chrono::high_resolution_clock` == `std::chrono::steady_clock`. Also, places that are isolated and not part of the Web Performance APIs, such as Telemetry for Fabric, are not updates as part of this diff. Although these subsystems are also using `std::chrono::steady_clock` as a low-level representation, they are not sharing it with other parts of the React Native core. Reviewed By: rubennorte Differential Revision: D75185613 fbshipit-source-id: 889719368de163e6f529689df6cc16d816fde66c --- .../ReactCommon/cxxreact/JSExecutor.cpp | 7 +- .../ReactCommon/cxxreact/JSExecutor.h | 3 +- .../jsiexecutor/jsireact/JSIExecutor.cpp | 4 +- .../network/NetworkReporter.cpp | 10 +- .../network/NetworkReporter.h | 10 +- .../tracing/EventLoopReporter.cpp | 1 + .../tracing/PerformanceTracer.cpp | 3 + .../tracing/PerformanceTracer.h | 2 + .../react/bridging/tests/BridgingTest.cpp | 18 ++ .../NativeIntersectionObserver.h | 2 +- .../webperformance/NativePerformance.cpp | 22 +- .../webperformance/NativePerformance.h | 36 +-- .../performance/timeline/PerformanceEntry.h | 21 +- .../timeline/PerformanceEntryBuffer.h | 4 +- .../timeline/PerformanceEntryReporter.cpp | 51 ++- .../timeline/PerformanceEntryReporter.h | 40 +-- .../timeline/PerformanceObserver.h | 13 +- .../tests/PerformanceEntryReporterTest.cpp | 268 ++++++++++++---- .../tests/PerformanceObserverTest.cpp | 282 ++++++++++++++--- .../react/renderer/core/EventLogger.h | 3 +- .../react/renderer/core/RawEvent.h | 2 +- .../core/tests/EventQueueProcessorTest.cpp | 2 +- .../events/EventPerformanceLogger.cpp | 14 +- .../observers/events/EventPerformanceLogger.h | 12 +- .../intersection/IntersectionObserver.cpp | 8 +- .../intersection/IntersectionObserver.h | 18 +- .../IntersectionObserverManager.cpp | 6 +- .../IntersectionObserverManager.h | 7 +- .../RuntimeScheduler_Modern.cpp | 10 +- .../tests/RuntimeSchedulerTest.cpp | 14 +- .../renderer/uimanager/UIManagerMountHook.h | 4 +- .../ReactCommon/react/timing/primitives.h | 295 +++++++++++++++++- .../react/timing/tests/PrimitivesTest.cpp | 106 +++++-- .../HermesPerfettoDataSource.cpp | 2 +- .../reactperflogger/ReactPerfetto.cpp | 16 +- .../reactperflogger/ReactPerfetto.h | 3 +- .../reactperflogger/ReactPerfettoLogger.cpp | 18 +- .../reactperflogger/ReactPerfettoLogger.h | 7 +- 38 files changed, 1021 insertions(+), 323 deletions(-) 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/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/network/NetworkReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp index 1eb6e19f20f6d6..9f207a0a23620b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp @@ -78,7 +78,7 @@ void NetworkReporter::reportRequestStart( int encodedDataLength, const std::optional& redirectResponse) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -127,7 +127,7 @@ void NetworkReporter::reportRequestStart( void NetworkReporter::reportConnectionTiming(const std::string& requestId) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -168,7 +168,7 @@ void NetworkReporter::reportResponseStart( const ResponseInfo& responseInfo, int encodedDataLength) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -205,7 +205,7 @@ void NetworkReporter::reportResponseStart( void NetworkReporter::reportDataReceived(const std::string& requestId) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -233,7 +233,7 @@ void NetworkReporter::reportResponseEnd( const std::string& requestId, int encodedDataLength) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Report PerformanceResourceTiming event { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h index 4227deca3df0b8..90701c412bfe47 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h @@ -35,11 +35,11 @@ using FrontendChannel = std::function; */ struct ResourceTimingData { std::string url; - DOMHighResTimeStamp fetchStart; - DOMHighResTimeStamp requestStart; - std::optional connectStart; - std::optional connectEnd; - std::optional responseStart; + HighResTimeStamp fetchStart; + HighResTimeStamp requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; std::optional responseStatus; }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp index 0c9ccba02086f2..535fe2af5f1444 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp @@ -8,6 +8,7 @@ #include "EventLoopReporter.h" #if defined(REACT_NATIVE_DEBUGGER_ENABLED) +#include #include "PerformanceTracer.h" #endif diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index bd0bfd74326102..a2000ef4170908 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -8,6 +8,9 @@ #include "PerformanceTracer.h" #include "Timing.h" +#include +#include + #include #include diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index 448f096b19fbfa..89709d1c6493b6 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -11,6 +11,8 @@ #include +#include + #include #include #include 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/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/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index e8b60299d66603..d6ab76d20d846b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -116,30 +116,31 @@ std::shared_ptr tryGetObserver( NativePerformance::NativePerformance(std::shared_ptr jsInvoker) : NativePerformanceCxxSpec(std::move(jsInvoker)) {} -double NativePerformance::now(jsi::Runtime& /*rt*/) { +HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) { return JSExecutor::performanceNow(); } -double NativePerformance::markWithResult( +HighResTimeStamp NativePerformance::markWithResult( jsi::Runtime& rt, std::string name, - std::optional startTime) { + std::optional startTime) { auto entry = PerformanceEntryReporter::getInstance()->reportMark(name, startTime); return entry.startTime; } -std::tuple NativePerformance::measureWithResult( +std::tuple +NativePerformance::measureWithResult( jsi::Runtime& runtime, std::string name, - double startTime, - double endTime, - std::optional duration, + HighResTimeStamp startTime, + HighResTimeStamp endTime, + std::optional duration, std::optional startMark, std::optional endMark) { auto reporter = PerformanceEntryReporter::getInstance(); - DOMHighResTimeStamp startTimeValue = startTime; + HighResTimeStamp startTimeValue = startTime; // If the start time mark name is specified, it takes precedence over the // startTime parameter, which can be set to 0 by default from JavaScript. if (startMark) { @@ -151,7 +152,7 @@ std::tuple NativePerformance::measureWithResult( } } - DOMHighResTimeStamp endTimeValue = endTime; + HighResTimeStamp endTimeValue = endTime; // If the end time mark name is specified, it takes precedence over the // startTime parameter, which can be set to 0 by default from JavaScript. if (endMark) { @@ -345,7 +346,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()) { diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index 7723a1a2c81cff..672d5c6c62b262 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 { @@ -54,21 +54,21 @@ struct Bridging { struct NativePerformanceEntry { std::string name; PerformanceEntryType entryType; - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp duration; + HighResTimeStamp startTime; + HighResDuration duration; // For PerformanceEventTiming only - std::optional processingStart; - std::optional processingEnd; + 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 fetchStart; + std::optional requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; + std::optional responseEnd; std::optional responseStatus; }; @@ -88,23 +88,23 @@ 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( + HighResTimeStamp markWithResult( jsi::Runtime& rt, std::string name, - std::optional startTime); + std::optional startTime); // https://w3c.github.io/user-timing/#measure-method - std::tuple measureWithResult( + std::tuple measureWithResult( jsi::Runtime& rt, std::string name, - double startTime, - double endTime, - std::optional duration, + HighResTimeStamp startTime, + HighResTimeStamp endTime, + std::optional duration, std::optional startMark, std::optional endMark); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index e794902f85dc86..887af0d1a0753c 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -8,6 +8,7 @@ #pragma once #include + #include #include #include @@ -28,8 +29,8 @@ enum class PerformanceEntryType { struct AbstractPerformanceEntry { std::string name; - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp duration = 0; + HighResTimeStamp startTime; + HighResDuration duration = HighResDuration::zero(); }; struct PerformanceMark : AbstractPerformanceEntry { @@ -43,8 +44,8 @@ struct PerformanceMeasure : AbstractPerformanceEntry { struct PerformanceEventTiming : AbstractPerformanceEntry { static constexpr PerformanceEntryType entryType = PerformanceEntryType::EVENT; - DOMHighResTimeStamp processingStart; - DOMHighResTimeStamp processingEnd; + HighResTimeStamp processingStart; + HighResTimeStamp processingEnd; PerformanceEntryInteractionId interactionId; }; @@ -57,13 +58,13 @@ struct PerformanceResourceTiming : AbstractPerformanceEntry { static constexpr PerformanceEntryType entryType = PerformanceEntryType::RESOURCE; /** Aligns with `startTime`. */ - DOMHighResTimeStamp fetchStart; - DOMHighResTimeStamp requestStart; - std::optional connectStart; - std::optional connectEnd; - std::optional responseStart; + HighResTimeStamp fetchStart; + HighResTimeStamp requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; /** Aligns with `duration`. */ - std::optional responseEnd; + std::optional responseEnd; std::optional responseStatus; }; 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/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 669d02d756964d..2719b8f029418a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -36,10 +36,6 @@ std::vector getSupportedEntryTypesInternal() { return supportedEntryTypes; } -double performanceNow() { - return chronoToDOMHighResTimeStamp(std::chrono::steady_clock::now()); -} - #if defined(__clang__) #define NO_DESTROY [[clang::no_destroy]] #else @@ -82,9 +78,9 @@ PerformanceEntryReporter::PerformanceEntryReporter() #endif } -DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { +HighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() - : performanceNow(); + : HighResTimeStamp::now(); } std::vector @@ -172,7 +168,7 @@ void PerformanceEntryReporter::clearEntries( PerformanceMark PerformanceEntryReporter::reportMark( const std::string& name, - const std::optional& startTime) { + const std::optional& startTime) { // Resolve timings auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp(); const auto entry = PerformanceMark{{.name = name, .startTime = startTimeVal}}; @@ -192,11 +188,11 @@ PerformanceMark PerformanceEntryReporter::reportMark( PerformanceMeasure PerformanceEntryReporter::reportMeasure( const std::string& name, - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackMetadata) { - DOMHighResTimeStamp duration = endTime - startTime; + HighResDuration duration = endTime - startTime; const auto entry = PerformanceMeasure{ {.name = std::string(name), @@ -216,7 +212,7 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure( return entry; } -std::optional PerformanceEntryReporter::getMarkTime( +std::optional PerformanceEntryReporter::getMarkTime( const std::string& markName) const { std::shared_lock lock(buffersMutex_); @@ -230,10 +226,10 @@ std::optional PerformanceEntryReporter::getMarkTime( 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]++; @@ -259,8 +255,8 @@ void PerformanceEntryReporter::reportEvent( } void PerformanceEntryReporter::reportLongTask( - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp duration) { + HighResTimeStamp startTime, + HighResDuration duration) { const auto entry = PerformanceLongTaskTiming{ {.name = std::string{"self"}, .startTime = startTime, @@ -276,12 +272,12 @@ void PerformanceEntryReporter::reportLongTask( PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( const std::string& url, - DOMHighResTimeStamp fetchStart, - DOMHighResTimeStamp requestStart, - std::optional connectStart, - std::optional connectEnd, - DOMHighResTimeStamp responseStart, - DOMHighResTimeStamp responseEnd, + 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}, @@ -312,9 +308,7 @@ void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { auto [trackName, eventName] = parseTrackName(entry.name); if (performanceTracer.isTracing()) { - performanceTracer.reportMark( - entry.name, - HighResTimeStamp::fromDOMHighResTimeStamp(entry.startTime)); + performanceTracer.reportMark(entry.name, entry.startTime); } if (ReactPerfettoLogger::isTracing()) { @@ -338,10 +332,7 @@ void PerformanceEntryReporter::traceMeasure( trackMetadata = {.track = trackName.value()}; } performanceTracer.reportMeasure( - eventName, - HighResTimeStamp::fromDOMHighResTimeStamp(entry.startTime), - HighResDuration::fromDOMHighResTimeStamp(entry.duration), - trackMetadata); + eventName, entry.startTime, entry.duration, trackMetadata); } if (ReactPerfettoLogger::isTracing()) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 549fe6f591a724..2eb67ceca0331f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -27,7 +27,8 @@ 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: @@ -66,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); } @@ -80,37 +81,38 @@ class PerformanceEntryReporter { return eventCounts_; } - std::optional getMarkTime(const std::string& markName) const; + std::optional getMarkTime( + const std::string& markName) const; PerformanceMark reportMark( const std::string& name, - const std::optional& startTime = std::nullopt); + const std::optional& startTime = std::nullopt); PerformanceMeasure reportMeasure( const std::string& name, - double startTime, - double endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackMetadata = std::nullopt); 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); PerformanceResourceTiming reportResourceTiming( const std::string& url, - DOMHighResTimeStamp fetchStart, - DOMHighResTimeStamp requestStart, - std::optional connectStart, - std::optional connectEnd, - DOMHighResTimeStamp responseStart, - DOMHighResTimeStamp responseEnd, + HighResTimeStamp fetchStart, + HighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + HighResTimeStamp responseStart, + HighResTimeStamp responseEnd, const std::optional& responseStatus); private: @@ -126,7 +128,7 @@ class PerformanceEntryReporter { std::unordered_map eventCounts_; - std::function timeStampProvider_ = nullptr; + std::function timeStampProvider_ = nullptr; const inline PerformanceEntryBuffer& getBuffer( PerformanceEntryType entryType) const { 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/tests/PerformanceEntryReporterTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp index 5217debd97d262..330eb185f44d5f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -58,8 +58,10 @@ namespace facebook::react { [&](const auto& entryDetails) -> std::ostream& { os << "{ .name = \"" << entryDetails.name << "\"" << ", .entryType = " << entryTypeNames[static_cast(entryDetails.entryType) - 1] - << ", .startTime = " << entryDetails.startTime - << ", .duration = " << entryDetails.duration << " }"; + << ", .startTime = " + << entryDetails.startTime.toDOMHighResTimeStamp() + << ", .duration = " << entryDetails.duration.toDOMHighResTimeStamp() + << " }"; return os; }, entry); @@ -77,60 +79,116 @@ 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 = { - PerformanceMark{{.name = "mark0", .startTime = 0, .duration = 0}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, - PerformanceMark{{.name = "mark0", .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->reportMeasure("measure0", 0, 2); - reporter->reportMeasure("measure1", 0, 3); - - reporter->reportMark("mark3", 2.5); - reporter->reportMeasure("measure2", 2.0, 2.0); - reporter->reportMark("mark4", 3.0); + reporter->reportMark("mark0", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); + + reporter->reportMeasure( + "measure0", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(3)); + + reporter->reportMark( + "mark3", timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(2), + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMark( + "mark4", timeOrigin + HighResDuration::fromMilliseconds(3)); const auto entries = toSorted(reporter->getEntries()); const std::vector expected = { - PerformanceMark{{.name = "mark0", .startTime = 0, .duration = 0}}, - PerformanceMeasure{{.name = "measure0", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, - PerformanceMeasure{{.name = "measure2", .startTime = 2, .duration = 0}}, - PerformanceMark{{.name = "mark3", .startTime = 2.5, .duration = 0}}, - PerformanceMark{{.name = "mark4", .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::zero()}}, + 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(); { @@ -138,27 +196,61 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { ASSERT_EQ(0, entries.size()); } - reporter->reportMark("common_name", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark2", 2); - - reporter->reportMeasure("common_name", 0, 2); - reporter->reportMeasure("measure1", 0, 3); - reporter->reportMeasure("measure2", 1, 6); - reporter->reportMeasure("measure3", 1.5, 2); + reporter->reportMark("common_name", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); + + reporter->reportMeasure( + "common_name", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(3)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(1), + timeOrigin + HighResDuration::fromMilliseconds(6)); + reporter->reportMeasure( + "measure3", + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + timeOrigin + HighResDuration::fromMilliseconds(2)); { const auto allEntries = toSorted(reporter->getEntries()); const std::vector expected = { - PerformanceMark{{.name = "common_name", .startTime = 0, .duration = 0}}, + PerformanceMark{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, PerformanceMeasure{ - {.name = "common_name", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 5}}, + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, PerformanceMeasure{ - {.name = "measure3", .startTime = 1.5, .duration = 0.5}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}}; + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::fromMilliseconds(5)}}, + PerformanceMeasure{ + {.name = "measure3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + .duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, allEntries); } @@ -166,9 +258,18 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { const auto marks = toSorted(reporter->getEntries(PerformanceEntryType::MARK)); const std::vector expected = { - PerformanceMark{{.name = "common_name", .startTime = 0, .duration = 0}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .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); } @@ -177,17 +278,28 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { toSorted(reporter->getEntries(PerformanceEntryType::MEASURE)); const std::vector expected = { PerformanceMeasure{ - {.name = "common_name", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, - PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 5}}, + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMeasure{ + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, PerformanceMeasure{ - {.name = "measure3", .startTime = 1.5, .duration = 0.5}}}; + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::fromMilliseconds(5)}}, + PerformanceMeasure{ + {.name = "measure3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + .duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}}; ASSERT_EQ(expected, measures); } { const std::vector expected = { - PerformanceMark{{.name = "common_name", .startTime = 0}}}; + PerformanceMark{{.name = "common_name", .startTime = timeOrigin}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MARK, "common_name"); ASSERT_EQ(expected, commonName); @@ -195,7 +307,9 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { { const std::vector expected = {PerformanceMeasure{ - {.name = "common_name", .startTime = 0, .duration = 2}}}; + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MEASURE, "common_name"); ASSERT_EQ(expected, commonName); @@ -204,26 +318,52 @@ 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->reportMeasure("common_name", 0, 2); - reporter->reportMeasure("measure1", 0, 3); - reporter->reportMeasure("measure2", 1, 6); - reporter->reportMeasure("measure3", 1.5, 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", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(3)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(1), + timeOrigin + HighResDuration::fromMilliseconds(6)); + reporter->reportMeasure( + "measure3", + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + timeOrigin + HighResDuration::fromMilliseconds(2)); reporter->clearEntries(PerformanceEntryType::MARK, "common_name"); { auto entries = toSorted(reporter->getEntries(PerformanceEntryType::MARK)); std::vector expected = { - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, - PerformanceMark{{.name = "mark1", .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 06cf2c3e09bffe..0a954958288728 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp @@ -49,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(); @@ -64,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); @@ -79,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); @@ -101,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); @@ -116,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); @@ -134,18 +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), + timeOrigin + HighResDuration::fromMilliseconds(20)); + reporter->reportMark( + "test2", timeOrigin + HighResDuration::fromMilliseconds(20)); + reporter->reportMark( + "test3", timeOrigin + HighResDuration::fromMilliseconds(30)); const std::vector expected = { - PerformanceMark{{.name = "test1", .startTime = 10}}, - PerformanceMark{{.name = "test2", .startTime = 20}}, - PerformanceMark{{.name = "test3", .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()); @@ -157,19 +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 = { - PerformanceEventTiming{{.name = "test1", .duration = 50}, 0, 0, 0}, - PerformanceEventTiming{{.name = "test2", .duration = 100}, 0, 0, 0}, - PerformanceEventTiming{{.name = "test3", .duration = 60}, 0, 0, 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()); @@ -181,23 +288,65 @@ 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 = { PerformanceEventTiming{ - {.name = "test1", .startTime = 0, .duration = 50}, 0, 0, 0}, + {.name = "test1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(50)}, + timeOrigin, + timeOrigin, + 0}, PerformanceEventTiming{ - {.name = "test2", .startTime = 0, .duration = 100}, 0, 0, 0}, + {.name = "test2", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, PerformanceEventTiming{ - {.name = "test4", .startTime = 0, .duration = 100}, 0, 0, 0}, + {.name = "test4", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -209,26 +358,71 @@ 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, + 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 = { - PerformanceEventTiming{{.name = "event1", .duration = 100}, 0, 0, 0}, - PerformanceEventTiming{{.name = "event3", .duration = 60}, 0, 0, 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 = { - PerformanceEventTiming{{.name = "event1", .duration = 100}, 0, 0, 0}, + PerformanceEventTiming{ + {.name = "event1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, }; ASSERT_EQ(expected1, observer1->takeRecords()); 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/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/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 ead7beadfe29e1..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,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -1251,8 +1252,10 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasks) { std::visit( [](const auto& entryDetails) { EXPECT_EQ(entryDetails.entryType, PerformanceEntryType::LONGTASK); - EXPECT_EQ(entryDetails.startTime, 100); - EXPECT_EQ(entryDetails.duration, 50); + EXPECT_EQ( + entryDetails.startTime.toDOMHighResTimeStamp(), + startTime.toDOMHighResTimeStamp() + 100); + EXPECT_EQ(entryDetails.duration, HighResDuration::fromMilliseconds(50)); }, entry); } @@ -1336,8 +1339,11 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { std::visit( [](const auto& entryDetails) { EXPECT_EQ(entryDetails.entryType, PerformanceEntryType::LONGTASK); - EXPECT_EQ(entryDetails.startTime, 100); - EXPECT_EQ(entryDetails.duration, 120); + EXPECT_EQ( + entryDetails.startTime.toDOMHighResTimeStamp(), + startTime.toDOMHighResTimeStamp() + 100); + EXPECT_EQ( + entryDetails.duration, HighResDuration::fromMilliseconds(120)); }, entry); } 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/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/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 94ccfcc23f44f3..df907c0e20507b 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp @@ -25,6 +25,12 @@ 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 @@ -39,8 +45,8 @@ std::string toPerfettoTrackName( /* 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")) { @@ -49,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")) { @@ -66,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 a85d06a5b3900c..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 @@ -24,13 +25,13 @@ class ReactPerfettoLogger { 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); }; From 761301e74f2d3dc2a34d78ca77812e6ee20c4d85 Mon Sep 17 00:00:00 2001 From: Nicola Corti Date: Wed, 28 May 2025 02:36:26 -0700 Subject: [PATCH 11/17] Cleanup custom LOG_TAG in our CMakeLists files. (#51622) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51622 We currently support an optional 3rd params for `target_compile_reactnative_options` which allows to specify a LOG_TAG macro. No one is actually reading that Macro. The only usage would be logging from the Android SDK which we don't explicitely use. Here I'm updating our build to specify a LOG_TAG as `ReactNative` for all the targets without allowing to customize it as it just complicates our build setup. Changelog: [Internal] [Changed] - Reviewed By: mdvacca Differential Revision: D75445577 fbshipit-source-id: a426ce77ba6d1dfd0800e874d9f7838bfdc5b877 --- .../cmake-utils/ReactNative-application.cmake | 2 +- .../main/jni/react/devsupport/CMakeLists.txt | 2 +- .../src/main/jni/react/fabric/CMakeLists.txt | 2 +- .../jni/react/featureflags/CMakeLists.txt | 2 +- .../main/jni/react/mapbuffer/CMakeLists.txt | 2 +- .../jni/react/newarchdefaults/CMakeLists.txt | 2 +- .../main/jni/react/uimanager/CMakeLists.txt | 2 +- .../cmake-utils/react-native-flags.cmake | 25 ++++++------- .../ReactCommon/cxxreact/CMakeLists.txt | 3 ++ .../ReactCommon/jsc/CMakeLists.txt | 3 ++ .../ReactCommon/jsi/CMakeLists.txt | 3 ++ .../ReactCommon/jsitooling/CMakeLists.txt | 26 ++++++++++++++ .../ReactCommon/react/bridging/CMakeLists.txt | 4 ++- .../ReactCommon/react/debug/CMakeLists.txt | 2 ++ .../react/featureflags/CMakeLists.txt | 5 +-- .../react/nativemodule/core/CMakeLists.txt | 2 ++ .../nativemodule/defaults/CMakeLists.txt | 2 ++ .../devtoolsruntimesettings/CMakeLists.txt | 2 ++ .../react/nativemodule/dom/CMakeLists.txt | 2 ++ .../nativemodule/featureflags/CMakeLists.txt | 2 ++ .../nativemodule/idlecallbacks/CMakeLists.txt | 2 ++ .../nativemodule/microtasks/CMakeLists.txt | 2 ++ .../samples/platform/android/CMakeLists.txt | 3 ++ .../react/performance/timeline/CMakeLists.txt | 2 +- .../react/renderer/animations/CMakeLists.txt | 2 ++ .../renderer/attributedstring/CMakeLists.txt | 2 ++ .../renderer/componentregistry/CMakeLists.txt | 2 ++ .../componentregistry/native/CMakeLists.txt | 2 ++ .../renderer/components/image/CMakeLists.txt | 2 ++ .../legacyviewmanagerinterop/CMakeLists.txt | 2 ++ .../renderer/components/modal/CMakeLists.txt | 2 ++ .../components/progressbar/CMakeLists.txt | 2 ++ .../renderer/components/root/CMakeLists.txt | 2 ++ .../components/safeareaview/CMakeLists.txt | 12 ++----- .../components/scrollview/CMakeLists.txt | 2 ++ .../renderer/components/switch/CMakeLists.txt | 12 ++----- .../renderer/components/text/CMakeLists.txt | 2 ++ .../components/textinput/CMakeLists.txt | 2 ++ .../unimplementedview/CMakeLists.txt | 2 ++ .../renderer/components/view/CMakeLists.txt | 2 ++ .../react/renderer/consistency/CMakeLists.txt | 4 ++- .../react/renderer/core/CMakeLists.txt | 2 ++ .../react/renderer/css/CMakeLists.txt | 36 +++++++++++++++++++ .../react/renderer/debug/CMakeLists.txt | 6 ++-- .../react/renderer/dom/CMakeLists.txt | 2 ++ .../react/renderer/element/CMakeLists.txt | 2 ++ .../react/renderer/graphics/CMakeLists.txt | 2 ++ .../renderer/imagemanager/CMakeLists.txt | 2 ++ .../react/renderer/leakchecker/CMakeLists.txt | 2 ++ .../react/renderer/mapbuffer/CMakeLists.txt | 9 +++-- .../react/renderer/mounting/CMakeLists.txt | 2 ++ .../renderer/observers/events/CMakeLists.txt | 2 ++ .../renderer/runtimescheduler/CMakeLists.txt | 5 ++- .../react/renderer/scheduler/CMakeLists.txt | 2 ++ .../react/renderer/telemetry/CMakeLists.txt | 2 ++ .../renderer/textlayoutmanager/CMakeLists.txt | 2 ++ .../react/renderer/uimanager/CMakeLists.txt | 3 ++ .../uimanager/consistency/CMakeLists.txt | 6 ++-- .../react/runtime/hermes/CMakeLists.txt | 1 + .../ReactCommon/react/utils/CMakeLists.txt | 2 ++ .../NativeCxxModuleExample/CMakeLists.txt | 10 +----- 61 files changed, 194 insertions(+), 66 deletions(-) create mode 100644 packages/react-native/ReactCommon/jsitooling/CMakeLists.txt create mode 100644 packages/react-native/ReactCommon/react/renderer/css/CMakeLists.txt diff --git a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake index 407a7f67181e85..7c8af8eb9b0f68 100644 --- a/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake +++ b/packages/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake @@ -61,7 +61,7 @@ target_include_directories(${CMAKE_PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_BUILD_DIR}/generated/autolinking/src/main/jni) -target_compile_reactnative_options(${CMAKE_PROJECT_NAME} PRIVATE "ReactNative") +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/react/devsupport/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt index dc628158dfef49..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 @@ -21,4 +21,4 @@ target_link_libraries(react_devsupportjni fbjni jsinspector) -target_compile_reactnative_options(react_devsupportjni PRIVATE "ReactNative") +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 d826c27ac79c7a..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 @@ -63,4 +63,4 @@ target_link_libraries( yoga ) -target_compile_reactnative_options(fabricjni PRIVATE "Fabric") +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 114322733bf091..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 @@ -26,4 +26,4 @@ target_link_libraries( ) target_merge_so(react_featureflagsjni) -target_compile_reactnative_options(react_featureflagsjni PRIVATE "ReactNative") +target_compile_reactnative_options(react_featureflagsjni PRIVATE) 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 8c17a35bd1ceed..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 @@ -34,4 +34,4 @@ target_link_libraries(mapbufferjni yoga ) -target_compile_reactnative_options(mapbufferjni PRIVATE "Fabric") +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 0ef2ae5ee8075a..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 @@ -31,4 +31,4 @@ target_link_libraries(react_newarchdefaults react_nativemodule_idlecallbacks jsi) -target_compile_reactnative_options(react_newarchdefaults PRIVATE "ReactNative") +target_compile_reactnative_options(react_newarchdefaults 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 56c9ecacdc371f..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 @@ -36,4 +36,4 @@ target_link_libraries(uimanagerjni yoga ) -target_compile_reactnative_options(uimanagerjni PRIVATE "ReactNative") +target_compile_reactnative_options(uimanagerjni PRIVATE) diff --git a/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake b/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake index b446816a776736..bbbd534d9ad3ff 100644 --- a/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake +++ b/packages/react-native/ReactCommon/cmake-utils/react-native-flags.cmake @@ -16,23 +16,18 @@ SET(reactnative_FLAGS -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} - -Wall - -fexceptions - -frtti - -std=c++20 - -DFOLLY_NO_CONFIG=1 - ) - set (extra_args ${ARGN}) - list(LENGTH extra_args extra_count) - set (tag "ReactNative") - if (${extra_count} GREATER 0) - list(GET extra_args 0 user_provided_tag) - target_compile_options(${target_name} ${scope} -DLOG_TAG=\"${user_provided_tag}\") - endif () + 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/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/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/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/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/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index 4de6a14a509859..4250fb241c32d8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -11,7 +11,7 @@ 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 "ReactNative") +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}) 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/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/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/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/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/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/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 From b1fc945d07889fd16212de7690de389b23637d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 7 Jul 2025 06:42:23 -0700 Subject: [PATCH 12/17] Add tests for performance.eventCounts (#52463) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52463 Changelog: [internal] This adds Fantom tests for `performance.eventCounts`. Reviewed By: huntie Differential Revision: D77860881 fbshipit-source-id: 26b9ef56b9c610cbad7011bc0adde27251fda909 --- .../webperformance/NativePerformance.cpp | 12 + .../webperformance/NativePerformance.h | 8 + .../timeline/PerformanceEntryReporter.cpp | 4 + .../timeline/PerformanceEntryReporter.h | 2 + .../__tests__/EventTimingAPI-itest.js | 251 ++++++++++++++++++ .../performance/specs/NativePerformance.js | 3 + 6 files changed, 280 insertions(+) create mode 100644 packages/react-native/src/private/webapis/performance/__tests__/EventTimingAPI-itest.js diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index d6ab76d20d846b..9e4e0655a05c28 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -406,4 +406,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 672d5c6c62b262..4849068423c8e3 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -186,6 +186,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/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 2719b8f029418a..3c44a7f003e07f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -212,6 +212,10 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure( return entry; } +void PerformanceEntryReporter::clearEventCounts() { + eventCounts_.clear(); +} + std::optional PerformanceEntryReporter::getMarkTime( const std::string& markName) const { std::shared_lock lock(buffersMutex_); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 2eb67ceca0331f..1bacd16e1d900a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -81,6 +81,8 @@ class PerformanceEntryReporter { return eventCounts_; } + void clearEventCounts(); + std::optional getMarkTime( const std::string& markName) const; 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/specs/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js index 809037e8a731e4..ec50c477f9f523 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -96,6 +96,9 @@ export interface Spec extends TurboModule { ) => $ReadOnlyArray; +getSupportedPerformanceEntryTypes?: () => $ReadOnlyArray; + + +setCurrentTimeStampForTesting?: (timeStamp: number) => void; + +clearEventCountsForTesting?: () => void; } export default (TurboModuleRegistry.get('NativePerformanceCxx'): ?Spec); From 98df71aa2aa46e0f1e4054918f8c55cd7d3abaf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 16 Jul 2025 02:48:43 -0700 Subject: [PATCH 13/17] Refactor implementation of performance.mark and performance.measure (#52586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52586 Changelog: [internal] This fixes a "bug" (or spec-compliance issue) in the `performance.measure` method where `end` and `duration` couldn't be used together (only `start` and `duration`, and `start` and `end` could be used). This also refactors the API to be "JS-first", preparing the native module methods to support passing instances of entries to fix other issues. Verified performance impact: `mark` is slightly regressed (~7% slower) due to an additional JSI call to get the default start time and `measure` is significantly optimized (25/30% faster) due to simplified parameter handling in JSI calls. * Before | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'mark (default)' | '1621.03 ± 1.17%' | '1562.00' | '636986 ± 0.01%' | '640205' | 616890 | | 1 | 'mark (with custom startTime)' | '1756.88 ± 1.15%' | '1693.00' | '586826 ± 0.01%' | '590667' | 569190 | | 2 | 'measure (default)' | '2424.66 ± 1.35%' | '2333.00' | '426122 ± 0.02%' | '428633' | 412429 | | 3 | 'measure (with start and end timestamps)' | '2679.96 ± 1.23%' | '2574.00' | '385266 ± 0.02%' | '388500' | 373140 | | 4 | 'measure (with mark names)' | '2713.49 ± 0.50%' | '2644.00' | '375383 ± 0.02%' | '378215' | 368530 | | 5 | 'clearMarks' | '691.13 ± 0.07%' | '681.00' | '1467016 ± 0.01%' | '1468429' | 1446900 | | 6 | 'clearMeasures' | '706.00 ± 0.05%' | '691.00' | '1431489 ± 0.01%' | '1447178' | 1416435 | | 7 | 'mark + clearMarks' | '2083.21 ± 1.14%' | '2003.00' | '497974 ± 0.01%' | '499251' | 480028 | | 8 | 'measure + clearMeasures (with start and end timestamps)' | '3085.14 ± 0.88%' | '2974.00' | '334337 ± 0.02%' | '336247' | 324135 | | 9 | 'measure + clearMeasures (with mark names)' | '2949.45 ± 0.62%' | '2884.00' | '345335 ± 0.02%' | '346741' | 339046 | * After | (index) | Task name | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples | | ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- | | 0 | 'mark (default)' | '1740.06 ± 1.01%' | '1692.00' | '587400 ± 0.01%' | '591017' | 574695 | | 1 | 'mark (with custom startTime)' | '1661.64 ± 1.16%' | '1612.00' | '617453 ± 0.01%' | '620347' | 601815 | | 2 | 'measure (default)' | '1808.71 ± 1.28%' | '1753.00' | '566516 ± 0.01%' | '570451' | 552882 | | 3 | 'measure (with start and end timestamps)' | '1869.21 ± 1.00%' | '1823.00' | '546571 ± 0.01%' | '548546' | 534987 | | 4 | 'measure (with mark names)' | '2016.40 ± 0.74%' | '1983.00' | '502987 ± 0.01%' | '504286' | 496075 | | 5 | 'clearMarks' | '682.18 ± 0.03%' | '671.00' | '1476364 ± 0.01%' | '1490313' | 1465899 | | 6 | 'clearMeasures' | '686.78 ± 0.03%' | '681.00' | '1467264 ± 0.01%' | '1468429' | 1456081 | | 7 | 'mark + clearMarks' | '2148.90 ± 1.28%' | '2073.00' | '480925 ± 0.01%' | '482393' | 465356 | | 8 | 'measure + clearMeasures (with start and end timestamps)' | '2277.26 ± 1.10%' | '2204.00' | '451016 ± 0.01%' | '453721' | 439125 | | 9 | 'measure + clearMeasures (with mark names)' | '2277.82 ± 0.51%' | '2243.00' | '443853 ± 0.01%' | '445831' | 439016 | Reviewed By: hoxyq Differential Revision: D78193072 fbshipit-source-id: 03b52927a1999f19a2baf0d02335a959525d7add --- .../webperformance/NativePerformance.cpp | 62 +- .../webperformance/NativePerformance.h | 17 +- .../timeline/PerformanceEntryReporter.cpp | 22 +- .../timeline/PerformanceEntryReporter.h | 10 +- .../tests/PerformanceEntryReporterTest.cpp | 44 +- .../tests/PerformanceObserverTest.cpp | 6 +- .../webapis/performance/Performance.js | 330 +++++-- .../performance/__tests__/UserTiming-itest.js | 811 ++++++++++++++++++ .../performance/specs/NativePerformance.js | 16 +- 9 files changed, 1127 insertions(+), 191 deletions(-) create mode 100644 packages/react-native/src/private/webapis/performance/__tests__/UserTiming-itest.js diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 9e4e0655a05c28..f6aa7625d3ec99 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -120,58 +120,30 @@ HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) { return JSExecutor::performanceNow(); } -HighResTimeStamp NativePerformance::markWithResult( +void NativePerformance::reportMark( jsi::Runtime& rt, std::string name, - std::optional startTime) { - auto entry = - PerformanceEntryReporter::getInstance()->reportMark(name, startTime); - return entry.startTime; + HighResTimeStamp startTime, + jsi::Value /*entry*/) { + PerformanceEntryReporter::getInstance()->reportMark(name, startTime); } -std::tuple -NativePerformance::measureWithResult( - jsi::Runtime& runtime, +void NativePerformance::reportMeasure( + jsi::Runtime& rt, std::string name, HighResTimeStamp startTime, - HighResTimeStamp endTime, - std::optional duration, - std::optional startMark, - std::optional endMark) { - auto reporter = PerformanceEntryReporter::getInstance(); - - HighResTimeStamp startTimeValue = startTime; - // If the start time mark name is specified, it takes precedence over the - // startTime parameter, which can be set to 0 by default from JavaScript. - if (startMark) { - if (auto startMarkBufferedTime = reporter->getMarkTime(*startMark)) { - startTimeValue = *startMarkBufferedTime; - } else { - throw jsi::JSError( - runtime, "The mark '" + *startMark + "' does not exist."); - } - } - - HighResTimeStamp endTimeValue = endTime; - // If the end time mark name is specified, it takes precedence over the - // startTime parameter, which can be set to 0 by default from JavaScript. - if (endMark) { - if (auto endMarkBufferedTime = reporter->getMarkTime(*endMark)) { - endTimeValue = *endMarkBufferedTime; - } else { - throw jsi::JSError( - runtime, "The mark '" + *endMark + "' does not exist."); - } - } else if (duration) { - endTimeValue = startTimeValue + *duration; - } else if (endTimeValue < startTimeValue) { - // The end time is not specified, take the current time, according to the - // standard - endTimeValue = reporter->getCurrentTimeStamp(); - } + HighResDuration duration, + jsi::Value /*entry*/) { + PerformanceEntryReporter::getInstance()->reportMeasure( + name, startTime, duration); +} - auto entry = reporter->reportMeasure(name, startTime, endTime); - 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( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index 4849068423c8e3..883e2c245c8a9c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -92,21 +92,20 @@ class NativePerformance : public NativePerformanceCxxSpec { #pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/) - // https://w3c.github.io/user-timing/#mark-method - HighResTimeStamp 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, HighResTimeStamp startTime, - HighResTimeStamp endTime, - std::optional duration, - std::optional startMark, - std::optional endMark); + HighResDuration duration, + jsi::Value entry); + + std::optional getMarkTime(jsi::Runtime& rt, std::string name); // https://w3c.github.io/user-timing/#clearmarks-method void clearMarks( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 3c44a7f003e07f..f18aa227e83033 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -166,12 +166,10 @@ void PerformanceEntryReporter::clearEntries( getBufferRef(entryType).clear(entryName); } -PerformanceMark PerformanceEntryReporter::reportMark( +void PerformanceEntryReporter::reportMark( const std::string& name, - const std::optional& startTime) { - // Resolve timings - auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp(); - const auto entry = PerformanceMark{{.name = name, .startTime = startTimeVal}}; + const HighResTimeStamp startTime) { + const auto entry = PerformanceMark{{.name = name, .startTime = startTime}}; traceMark(entry); @@ -182,18 +180,14 @@ PerformanceMark PerformanceEntryReporter::reportMark( } observerRegistry_->queuePerformanceEntry(entry); - - return entry; } -PerformanceMeasure PerformanceEntryReporter::reportMeasure( +void PerformanceEntryReporter::reportMeasure( const std::string& name, HighResTimeStamp startTime, - HighResTimeStamp endTime, + HighResDuration duration, const std::optional& trackMetadata) { - HighResDuration duration = endTime - startTime; - const auto entry = PerformanceMeasure{ {.name = std::string(name), .startTime = startTime, @@ -208,8 +202,6 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure( } observerRegistry_->queuePerformanceEntry(entry); - - return entry; } void PerformanceEntryReporter::clearEventCounts() { @@ -274,7 +266,7 @@ void PerformanceEntryReporter::reportLongTask( observerRegistry_->queuePerformanceEntry(entry); } -PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( +void PerformanceEntryReporter::reportResourceTiming( const std::string& url, HighResTimeStamp fetchStart, HighResTimeStamp requestStart, @@ -301,8 +293,6 @@ PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( } observerRegistry_->queuePerformanceEntry(entry); - - return entry; } void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 1bacd16e1d900a..434625bdf8f924 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -86,14 +86,12 @@ class PerformanceEntryReporter { std::optional getMarkTime( const std::string& markName) const; - PerformanceMark reportMark( - const std::string& name, - const std::optional& startTime = std::nullopt); + void reportMark(const std::string& name, HighResTimeStamp startTime); - PerformanceMeasure reportMeasure( + void reportMeasure( const std::string& name, HighResTimeStamp startTime, - HighResTimeStamp endTime, + HighResDuration duration, const std::optional& trackMetadata = std::nullopt); @@ -107,7 +105,7 @@ class PerformanceEntryReporter { void reportLongTask(HighResTimeStamp startTime, HighResDuration duration); - PerformanceResourceTiming reportResourceTiming( + void reportResourceTiming( const std::string& url, HighResTimeStamp fetchStart, HighResTimeStamp requestStart, 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 330eb185f44d5f..bf8aaf06dba9a2 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -129,20 +129,16 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); reporter->reportMeasure( - "measure0", - timeOrigin, - timeOrigin + HighResDuration::fromMilliseconds(2)); + "measure0", timeOrigin, HighResDuration::fromMilliseconds(2)); reporter->reportMeasure( - "measure1", - timeOrigin, - timeOrigin + HighResDuration::fromMilliseconds(3)); + "measure1", timeOrigin, HighResDuration::fromMilliseconds(3)); reporter->reportMark( "mark3", timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6)); reporter->reportMeasure( "measure2", timeOrigin + HighResDuration::fromMilliseconds(2), - timeOrigin + HighResDuration::fromMilliseconds(2)); + HighResDuration::fromMilliseconds(2)); reporter->reportMark( "mark4", timeOrigin + HighResDuration::fromMilliseconds(3)); @@ -172,7 +168,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { PerformanceMeasure{ {.name = "measure2", .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), - .duration = HighResDuration::zero()}}, + .duration = HighResDuration::fromMilliseconds(2)}}, PerformanceMark{ {.name = "mark3", .startTime = @@ -203,21 +199,17 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); reporter->reportMeasure( - "common_name", - timeOrigin, - timeOrigin + HighResDuration::fromMilliseconds(2)); + "common_name", timeOrigin, HighResDuration::fromMilliseconds(2)); reporter->reportMeasure( - "measure1", - timeOrigin, - timeOrigin + HighResDuration::fromMilliseconds(3)); + "measure1", timeOrigin, HighResDuration::fromMilliseconds(3)); reporter->reportMeasure( "measure2", timeOrigin + HighResDuration::fromMilliseconds(1), - timeOrigin + HighResDuration::fromMilliseconds(6)); + HighResDuration::fromMilliseconds(6)); reporter->reportMeasure( "measure3", timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), - timeOrigin + HighResDuration::fromMilliseconds(2)); + HighResDuration::fromMilliseconds(2)); { const auto allEntries = toSorted(reporter->getEntries()); @@ -241,12 +233,12 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { PerformanceMeasure{ {.name = "measure2", .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), - .duration = HighResDuration::fromMilliseconds(5)}}, + .duration = HighResDuration::fromMilliseconds(6)}}, PerformanceMeasure{ {.name = "measure3", .startTime = timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), - .duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}, + .duration = HighResDuration::fromMilliseconds(2)}}, PerformanceMark{ {.name = "mark2", .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), @@ -288,12 +280,12 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { PerformanceMeasure{ {.name = "measure2", .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), - .duration = HighResDuration::fromMilliseconds(5)}}, + .duration = HighResDuration::fromMilliseconds(6)}}, PerformanceMeasure{ {.name = "measure3", .startTime = timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), - .duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}}; + .duration = HighResDuration::fromMilliseconds(2)}}}; ASSERT_EQ(expected, measures); } @@ -330,21 +322,17 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) { "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); reporter->reportMeasure( - "common_name", - timeOrigin, - timeOrigin + HighResDuration::fromMilliseconds(2)); + "common_name", timeOrigin, HighResDuration::fromMilliseconds(2)); reporter->reportMeasure( - "measure1", - timeOrigin, - timeOrigin + HighResDuration::fromMilliseconds(3)); + "measure1", timeOrigin, HighResDuration::fromMilliseconds(3)); reporter->reportMeasure( "measure2", timeOrigin + HighResDuration::fromMilliseconds(1), - timeOrigin + HighResDuration::fromMilliseconds(6)); + HighResDuration::fromMilliseconds(6)); reporter->reportMeasure( "measure3", timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), - timeOrigin + HighResDuration::fromMilliseconds(2)); + HighResDuration::fromMilliseconds(2)); reporter->clearEntries(PerformanceEntryType::MARK, "common_name"); 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 0a954958288728..8bb6a9163afcf3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp @@ -190,7 +190,7 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) { reporter->reportMeasure( "off", timeOrigin + HighResDuration::fromMilliseconds(10), - timeOrigin + HighResDuration::fromMilliseconds(20)); + HighResDuration::fromMilliseconds(20)); reporter->reportMark( "test2", timeOrigin + HighResDuration::fromMilliseconds(20)); reporter->reportMark( @@ -371,9 +371,7 @@ TEST(PerformanceObserver, PerformanceObserverTestMultiple) { {.durationThreshold = HighResDuration::fromMilliseconds(80)}); reporter->reportMeasure( - "measure", - timeOrigin, - timeOrigin + HighResDuration::fromMilliseconds(50)); + "measure", timeOrigin, HighResDuration::fromMilliseconds(50)); reporter->reportEvent( "event1", timeOrigin, diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 086baf75ae89ef..9dd01cf4f6c6c8 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -48,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 @@ -111,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(); + } + + if (detail !== undefined) { + resolvedDetail = structuredClone(detail); } - return new PerformanceMark(markName, { - startTime: computedStartTime, - detail: markOptions?.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 { @@ -149,77 +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 (typeof options.start === 'number') { - startTime = options.start; - } else { - startMarkName = options.start; - } + if ( + getMarkTimeForMeasure === undefined || + cachedReportMeasure === undefined + ) { + warnNoNativePerformance(); + return new PerformanceMeasure(measureName, {startTime: 0, duration: 0}); + } - if (typeof options.end === 'number') { - endTime = options.end; - } else { - endMarkName = options.end; - } + let resolvedMeasureName: string; + let resolvedStartTime: number; + let resolvedDuration: number; + let resolvedDetail: mixed; - duration = options.duration ?? duration; + if (measureName === undefined) { + throw new TypeError( + `Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present.`, + ); } - let computedStartTime = startTime; - let computedDuration = duration; - - if (NativePerformance?.measureWithResult) { - try { - [computedStartTime, computedDuration] = - NativePerformance.measureWithResult( - measureName, - startTime, - endTime, - duration, - startMarkName, - endMarkName, - ); - } catch (error) { - throw new DOMException( - "Failed to execute 'measure' on 'Performance': " + error.message, - 'SyntaxError', - ); + 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 { - warnNoNativePerformance(); + resolvedStartTime = 0; + + if (endMark !== undefined) { + resolvedDuration = getMarkTimeForMeasure(endMark) - resolvedStartTime; + } else { + resolvedDuration = getCurrentTimeStamp() - resolvedStartTime; + } } - const measure = new PerformanceMeasure(measureName, { - startTime: computedStartTime, - duration: computedDuration ?? 0, - detail: options?.detail, - }); + 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, + ); + + cachedReportMeasure( + resolvedMeasureName, + resolvedStartTime, + resolvedDuration, + entry, + ); - return measure; + return entry; } clearMeasures(measureName?: string): void { 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 ec50c477f9f523..498eeda19ddbe4 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -54,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; From 1d48c11a532e539593d04d58ce2e6e184b9836c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 16 Jul 2025 05:55:03 -0700 Subject: [PATCH 14/17] Remove support for specifying track names for Perfetto and RNDT using the "Track:" prefix (#52614) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52614 Changelog: [internal] We're adding first-class support for custom tracks, so we can remove the legacy mechanism to specify tracks with the "Tracks:" prefix in the event names. Reviewed By: sbuggay Differential Revision: D78340910 fbshipit-source-id: cbbadd519baf7bb50072cb97d8cd1ccc87a8a35c --- .../tracing/PerformanceTracer.h | 3 +- .../timeline/PerformanceEntryReporter.cpp | 46 ++----------------- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index 89709d1c6493b6..aa96d86fc56420 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -126,7 +126,8 @@ class PerformanceTracer { const std::string_view& name, HighResTimeStamp start, HighResDuration duration, - const std::optional& trackMetadata); + const std::optional& trackMetadata = + std::nullopt); /** * Record a corresponding Trace Event for OS-level process. diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index f18aa227e83033..46b9e2fb764322 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -36,33 +36,6 @@ std::vector getSupportedEntryTypesInternal() { return supportedEntryTypes; } -#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); -} - } // namespace std::shared_ptr& @@ -299,14 +272,12 @@ void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { auto& performanceTracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) { - auto [trackName, eventName] = parseTrackName(entry.name); - if (performanceTracer.isTracing()) { performanceTracer.reportMark(entry.name, entry.startTime); } if (ReactPerfettoLogger::isTracing()) { - ReactPerfettoLogger::mark(eventName, entry.startTime, trackName); + ReactPerfettoLogger::mark(entry.name, entry.startTime); } } } @@ -316,25 +287,14 @@ void PerformanceEntryReporter::traceMeasure( auto& performanceTracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) { - auto [trackName, eventName] = parseTrackName(entry.name); - if (performanceTracer.isTracing()) { - std::optional - trackMetadata; - - if (trackName.has_value()) { - trackMetadata = {.track = trackName.value()}; - } performanceTracer.reportMeasure( - eventName, entry.startTime, entry.duration, trackMetadata); + entry.name, entry.startTime, entry.duration); } if (ReactPerfettoLogger::isTracing()) { ReactPerfettoLogger::measure( - eventName, - entry.startTime, - entry.startTime + entry.duration, - trackName); + entry.name, entry.startTime, entry.startTime + entry.duration); } } } From 8c22d879a619fc27acc57fe08dcc46f09aea872b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 16 Jul 2025 05:55:03 -0700 Subject: [PATCH 15/17] Add support for details field and custom tracks in performance.mark and performance.measure for DevTools (#52613) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52613 Changelog: [internal] This adds first-class support for the `detail` field in `performance.mark` and `performance.measure`. Now that we have access the JS entry in native, we can access the `detail` field to propagate it to DevTools (and use it to extract track names for Perfetto). In order to avoid the performance overhead of always having to extract the `detail` field from the entry, this is done lazily only if we're actively profiling with DevTools or Perfetto. Reviewed By: sbuggay Differential Revision: D78340911 fbshipit-source-id: 383dd1cb6fcc8a04be9e65038503986f196e23c9 --- .../jsinspector-modern/tracing/CdpTracing.h | 21 ----- .../tracing/PerformanceTracer.cpp | 32 ++++++-- .../tracing/PerformanceTracer.h | 17 ++-- .../webperformance/NativePerformance.cpp | 23 +++++- .../react/performance/timeline/CMakeLists.txt | 3 +- .../timeline/PerformanceEntryReporter.cpp | 80 +++++++++++++++---- .../timeline/PerformanceEntryReporter.h | 20 +++-- 7 files changed, 133 insertions(+), 63 deletions(-) delete mode 100644 packages/react-native/ReactCommon/jsinspector-modern/tracing/CdpTracing.h 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/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index a2000ef4170908..f58efda4071438 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -114,8 +114,9 @@ bool PerformanceTracer::stopTracingAndCollectEvents( void PerformanceTracer::reportMark( const std::string_view& name, - HighResTimeStamp start) { - if (!tracing_) { + HighResTimeStamp start, + folly::dynamic&& detail) { + if (!tracingAtomic_) { return; } @@ -124,14 +125,21 @@ void PerformanceTracer::reportMark( 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, }); } @@ -139,11 +147,19 @@ void PerformanceTracer::reportMeasure( const std::string_view& name, HighResTimeStamp start, HighResDuration duration, - const std::optional& trackMetadata) { - if (!tracing_) { + 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; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index aa96d86fc56420..f1fe52aad6e0e9 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -7,9 +7,9 @@ #pragma once -#include "CdpTracing.h" - -#include +#include "ConsoleTimeStamp.h" +#include "TraceEvent.h" +#include "TraceEventProfile.h" #include @@ -106,7 +106,8 @@ class PerformanceTracer { */ void collectEvents( const std::function& - resultCallback); + resultCallback, + uint16_t chunkSize); /** * Record a `Performance.mark()` event - a labelled timestamp. If not @@ -114,7 +115,10 @@ class PerformanceTracer { * * See https://w3c.github.io/user-timing/#mark-method. */ - void reportMark(const std::string_view& name, HighResTimeStamp start); + void reportMark( + const std::string_view& name, + HighResTimeStamp start, + folly::dynamic&& detail = nullptr); /** * Record a `Performance.measure()` event - a labelled duration. If not @@ -126,8 +130,7 @@ class PerformanceTracer { const std::string_view& name, HighResTimeStamp start, HighResDuration duration, - const std::optional& trackMetadata = - std::nullopt); + folly::dynamic&& detail = nullptr); /** * Record a corresponding Trace Event for OS-level process. diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index f6aa7625d3ec99..9bf3f276f95849 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -111,6 +112,19 @@ 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) @@ -124,8 +138,9 @@ void NativePerformance::reportMark( jsi::Runtime& rt, std::string name, HighResTimeStamp startTime, - jsi::Value /*entry*/) { - PerformanceEntryReporter::getInstance()->reportMark(name, startTime); + jsi::Value entry) { + PerformanceEntryReporter::getInstance()->reportMark( + name, startTime, getDetailProviderFromEntry(rt, entry)); } void NativePerformance::reportMeasure( @@ -133,9 +148,9 @@ void NativePerformance::reportMeasure( std::string name, HighResTimeStamp startTime, HighResDuration duration, - jsi::Value /*entry*/) { + jsi::Value entry) { PerformanceEntryReporter::getInstance()->reportMeasure( - name, startTime, duration); + name, startTime, duration, getDetailProviderFromEntry(rt, entry)); } std::optional NativePerformance::getMarkTime( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index 4250fb241c32d8..742f6f9fefe3e4 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -19,4 +19,5 @@ target_link_libraries(react_performance_timeline jsinspector_tracing reactperflogger react_featureflags - react_timing) + react_timing + folly_runtime) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 46b9e2fb764322..6f78c6a349de8e 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -36,6 +36,42 @@ std::vector getSupportedEntryTypesInternal() { return supportedEntryTypes; } +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 std::shared_ptr& @@ -141,10 +177,11 @@ void PerformanceEntryReporter::clearEntries( void PerformanceEntryReporter::reportMark( const std::string& name, - const HighResTimeStamp startTime) { + const HighResTimeStamp startTime, + UserTimingDetailProvider&& detailProvider) { const auto entry = PerformanceMark{{.name = name, .startTime = startTime}}; - traceMark(entry); + traceMark(entry, std::move(detailProvider)); // Add to buffers & notify observers { @@ -159,14 +196,13 @@ void PerformanceEntryReporter::reportMeasure( const std::string& name, HighResTimeStamp startTime, HighResDuration duration, - const std::optional& - trackMetadata) { + UserTimingDetailProvider&& detailProvider) { const auto entry = PerformanceMeasure{ {.name = std::string(name), .startTime = startTime, .duration = duration}}; - traceMeasure(entry); + traceMeasure(entry, std::move(detailProvider)); // Add to buffers & notify observers { @@ -268,33 +304,45 @@ void PerformanceEntryReporter::reportResourceTiming( observerRegistry_->queuePerformanceEntry(entry); } -void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { +void PerformanceEntryReporter::traceMark( + const PerformanceMark& entry, + UserTimingDetailProvider&& detailProvider) const { auto& performanceTracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) { - if (performanceTracer.isTracing()) { - performanceTracer.reportMark(entry.name, entry.startTime); - } - 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) const { + const PerformanceMeasure& entry, + UserTimingDetailProvider&& detailProvider) const { auto& performanceTracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) { - if (performanceTracer.isTracing()) { - performanceTracer.reportMeasure( - entry.name, entry.startTime, entry.duration); - } + auto detail = detailProvider != nullptr ? detailProvider() : nullptr; if (ReactPerfettoLogger::isTracing()) { ReactPerfettoLogger::measure( - entry.name, entry.startTime, entry.startTime + entry.duration); + 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)); } } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 434625bdf8f924..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 @@ -86,14 +86,18 @@ class PerformanceEntryReporter { std::optional getMarkTime( const std::string& markName) const; - void reportMark(const std::string& name, HighResTimeStamp startTime); + using UserTimingDetailProvider = std::function; + + void reportMark( + const std::string& name, + HighResTimeStamp startTime, + UserTimingDetailProvider&& detailProvider = nullptr); void reportMeasure( const std::string& name, HighResTimeStamp startTime, HighResDuration duration, - const std::optional& - trackMetadata = std::nullopt); + UserTimingDetailProvider&& detailProvider = nullptr); void reportEvent( std::string name, @@ -167,8 +171,12 @@ class PerformanceEntryReporter { throw std::logic_error("Unhandled PerformanceEntryType"); } - void traceMark(const PerformanceMark& entry) const; - void traceMeasure(const PerformanceMeasure& entry) const; + void traceMark( + const PerformanceMark& entry, + UserTimingDetailProvider&& detailProvider) const; + void traceMeasure( + const PerformanceMeasure& entry, + UserTimingDetailProvider&& detailProvider) const; }; } // namespace facebook::react From bc52f24d0e54ecdf1f29c97f7798b0982b264201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 22 Jul 2025 04:14:48 -0700 Subject: [PATCH 16/17] fix: remove redundant if checks in traceMark (#52756) Summary: There are some duplicated function calls in `PerformanceEntryReporter::reportMark()` . I know this is a micro optimization but I feel this way the code is cleaner. This is called through `performance.mark` so there is potentially a tiny little performance improvement here? ## Changelog: [INTERNAL] [FIXED] - Removed redundant checks in `PerformanceEntryReporter::reportMark()` Pull Request resolved: https://github.com/facebook/react-native/pull/52756 Test Plan: The existing testing infrastructure should cover these callsites i believe Reviewed By: rubennorte Differential Revision: D78731441 Pulled By: cortinico fbshipit-source-id: e0de12c3c6f55e12eb454ea4b7081f3d6003126c --- .../timeline/PerformanceEntryReporter.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 6f78c6a349de8e..c46a1a4faf7b65 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -309,17 +309,16 @@ void PerformanceEntryReporter::traceMark( UserTimingDetailProvider&& detailProvider) const { auto& performanceTracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); - if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) { - if (ReactPerfettoLogger::isTracing()) { - ReactPerfettoLogger::mark(entry.name, entry.startTime); - } - if (performanceTracer.isTracing()) { - performanceTracer.reportMark( - entry.name, - entry.startTime, - detailProvider != nullptr ? detailProvider() : nullptr); - } + if (ReactPerfettoLogger::isTracing()) { + ReactPerfettoLogger::mark(entry.name, entry.startTime); + } + + if (performanceTracer.isTracing()) { + performanceTracer.reportMark( + entry.name, + entry.startTime, + detailProvider != nullptr ? detailProvider() : nullptr); } } From 6bf59a4068c7d424bda9c1f4601b201c4ce4d06d Mon Sep 17 00:00:00 2001 From: generatedunixname537391475639613 Date: Wed, 23 Jul 2025 04:31:56 -0700 Subject: [PATCH 17/17] xplat/js/react-native-github/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp (#52780) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52780 Reviewed By: rshest Differential Revision: D78798597 fbshipit-source-id: 28cdd8b2fdd4c5d5889f527ee7574f87e18215a7 --- .../timeline/tests/PerformanceEntryReporterTest.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 bf8aaf06dba9a2..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,7 @@ #include "../PerformanceEntryReporter.h" +#include #include using namespace facebook::react; @@ -46,13 +47,13 @@ namespace facebook::react { [[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", "PerformanceEntryType::RESOURCE", - }; + }); return std::visit( [&](const auto& entryDetails) -> std::ostream& {