diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index 0225fda9ea9dcd..5aa79f070c54ec 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -102,6 +102,10 @@ void PerformanceTracer::collectEvents( void PerformanceTracer::reportMark( const std::string_view& name, uint64_t start) { + if (!tracing_) { + return; + } + std::lock_guard lock(mutex_); if (!tracing_) { return; @@ -122,6 +126,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; @@ -160,6 +168,10 @@ void PerformanceTracer::reportMeasure( } void PerformanceTracer::reportProcess(uint64_t id, const std::string& name) { + if (!tracing_) { + return; + } + std::lock_guard lock(mutex_); if (!tracing_) { return; @@ -177,6 +189,10 @@ void PerformanceTracer::reportProcess(uint64_t id, const std::string& name) { } void PerformanceTracer::reportThread(uint64_t id, const std::string& name) { + if (!tracing_) { + return; + } + 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 cf80b68080c0bc..afb693d2a47947 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -40,6 +40,17 @@ class PerformanceTracer { */ 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. */ 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, diff --git a/packages/react-native/src/private/webapis/performance/__tests__/Performance-benchmark-itest.js b/packages/react-native/src/private/webapis/performance/__tests__/Performance-benchmark-itest.js new file mode 100644 index 00000000000000..2e709e8d8ca6af --- /dev/null +++ b/packages/react-native/src/private/webapis/performance/__tests__/Performance-benchmark-itest.js @@ -0,0 +1,123 @@ +/** + * 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 + * @oncall react_native + * @fantom_mode opt + */ + +import '../../../../../Libraries/Core/InitializeCore.js'; + +import Fantom from '@react-native/fantom'; + +const clearMarksAndMeasures = () => { + performance.clearMarks(); + performance.clearMeasures(); +}; + +Fantom.unstable_benchmark + .suite('Performance API') + .test( + 'mark (default)', + () => { + performance.mark('mark'); + }, + { + afterEach: clearMarksAndMeasures, + }, + ) + .test( + 'mark (with custom startTime)', + () => { + performance.mark('mark', { + startTime: 100, + }); + }, + { + afterEach: clearMarksAndMeasures, + }, + ) + .test( + 'measure (with start and end timestamps)', + () => { + performance.measure('measure', { + start: 100, + end: 300, + }); + }, + { + afterEach: clearMarksAndMeasures, + }, + ) + .test( + 'measure (with mark names)', + () => { + performance.measure('measure', 'measure-start', 'measure-end'); + }, + { + beforeEach: () => { + performance.mark('measure-start', { + startTime: 100, + }); + performance.mark('measure-end', { + startTime: 300, + }); + }, + afterEach: clearMarksAndMeasures, + }, + ) + .test( + 'clearMarks', + () => { + performance.clearMarks('mark'); + }, + { + beforeEach: () => performance.mark('mark'), + }, + ) + .test( + 'clearMeasures', + () => { + performance.clearMeasures('measure'); + }, + { + beforeEach: () => + performance.measure('measure', { + start: 100, + end: 300, + }), + }, + ) + .test('mark + clearMarks', () => { + performance.mark('mark'); + performance.clearMarks('mark'); + }) + .test('measure + clearMeasures (with start and end timestamps)', () => { + performance.measure('measure', { + start: 100, + end: 300, + }); + performance.clearMeasures('measure'); + }) + .test( + 'measure + clearMeasures (with mark names)', + () => { + performance.measure('measure', 'measure-start', 'measure-end'); + performance.clearMeasures('measure'); + }, + { + beforeEach: () => { + performance.mark('measure-start', { + startTime: 100, + }); + performance.mark('measure-end', { + startTime: 300, + }); + }, + afterEach: clearMarksAndMeasures, + }, + );