diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 4f377122c15..abfe1ebfab9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -5,16 +5,24 @@ * LICENSE file in the root directory of this source tree. */ +#include "NativePerformance.h" + #include +#include +#include #include #include #include #include -#include "NativePerformance.h" #include "Plugins.h" +#ifdef WITH_PERFETTO +#include +#include +#endif + std::shared_ptr NativePerformanceModuleProvider( std::shared_ptr jsInvoker) { return std::make_shared( @@ -23,8 +31,79 @@ std::shared_ptr NativePerformanceModuleProvider( namespace facebook::react { +namespace { + +#ifdef WITH_PERFETTO + +// Offset for custom perfetto tracks +uint64_t trackId = 0x5F3759DF; + +// Extract this once we start emitting perfetto markers from other modules +std::once_flag perfettoInit; +void initializePerfetto() { + std::call_once(perfettoInit, []() { + perfetto::TracingInitArgs args; + args.backends |= perfetto::kSystemBackend; + args.use_monotonic_clock = true; + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); + }); +} + +const std::string TRACK_PREFIX = "Track:"; +const std::string DEFAULT_TRACK_NAME = "Web Performance"; + +std::tuple parsePerfettoTrack( + 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); + } + } + + auto& trackNameRef = trackName.has_value() ? *trackName : DEFAULT_TRACK_NAME; + static std::unordered_map tracks; + auto it = tracks.find(trackNameRef); + if (it == tracks.end()) { + auto track = perfetto::Track(trackId++); + auto desc = track.Serialize(); + desc.set_name(trackNameRef); + perfetto::TrackEvent::SetTrackDescriptor(track, desc); + tracks.emplace(trackNameRef, track); + return std::make_tuple(track, eventName); + } else { + return std::make_tuple(it->second, eventName); + } +} + +// Perfetto's monotonic clock seems to match the std::chrono::steady_clock we +// use in JSExecutor::performanceNow 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); +} + +#endif + +} // namespace + NativePerformance::NativePerformance(std::shared_ptr jsInvoker) - : NativePerformanceCxxSpec(std::move(jsInvoker)) {} + : NativePerformanceCxxSpec(std::move(jsInvoker)) { +#ifdef WITH_PERFETTO + initializePerfetto(); +#endif +} double NativePerformance::now(jsi::Runtime& /*rt*/) { return JSExecutor::performanceNow(); @@ -34,6 +113,16 @@ void NativePerformance::mark( jsi::Runtime& rt, std::string name, double startTime) { +#ifdef WITH_PERFETTO + if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) { + auto [track, eventName] = parsePerfettoTrack(name); + TRACE_EVENT_INSTANT( + "react-native", + perfetto::DynamicString(eventName.data(), eventName.size()), + track, + performanceNowToPerfettoTraceTime(startTime)); + } +#endif PerformanceEntryReporter::getInstance()->mark(name, startTime); } @@ -45,6 +134,21 @@ void NativePerformance::measure( std::optional duration, std::optional startMark, std::optional endMark) { +#ifdef WITH_PERFETTO + if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) { + // TODO T190600850 support startMark/endMark + if (!startMark && !endMark) { + auto [track, eventName] = parsePerfettoTrack(name); + TRACE_EVENT_BEGIN( + "react-native", + perfetto::DynamicString(eventName.data(), eventName.size()), + track, + performanceNowToPerfettoTraceTime(startTime)); + TRACE_EVENT_END( + "react-native", track, performanceNowToPerfettoTraceTime(endTime)); + } + } +#endif PerformanceEntryReporter::getInstance()->measure( name, startTime, endTime, duration, startMark, endMark); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 4adeda741da..33fa784d72b 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -11,7 +11,7 @@ namespace facebook::react { -std::shared_ptr +std::shared_ptr& PerformanceEntryReporter::getInstance() { static auto instance = std::make_shared(); return instance; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 3a65f025374..d5d7b772a59 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -89,7 +89,7 @@ class PerformanceEntryReporter { // the same thread. // TODO: Consider passing it as a parameter to the corresponding modules at // creation time instead of having the singleton. - static std::shared_ptr getInstance(); + static std::shared_ptr& getInstance(); struct PopPendingEntriesResult { std::vector entries; diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.cpp new file mode 100644 index 00000000000..696b7542762 --- /dev/null +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.cpp @@ -0,0 +1,14 @@ +/* + * 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 "ReactPerfettoCategories.h" + +#ifdef WITH_PERFETTO + +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + +#endif diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.h b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.h new file mode 100644 index 00000000000..ab22c2c2f72 --- /dev/null +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.h @@ -0,0 +1,18 @@ +/* + * 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 + +#ifdef WITH_PERFETTO + +#include + +PERFETTO_DEFINE_CATEGORIES( + perfetto::Category("react-native") + .SetDescription("User timing events from React Native")); + +#endif