From 9d3bac6fd56be0062e38aefefdb09673f089202e Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 3 Jun 2024 08:55:48 -0700 Subject: [PATCH] Integrate perfetto with UserTiming API (#44702) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44702 Based on bgirard initial changes in D53478653, this creates an initial integration of the Perfetto SDK with the User Timing API, allowing performance information to be logged from JS to Perfetto traces. We only enable this for Android right now, but may be able to leverage this on other platforms too in the future. The logic in `initializePerfetto` may need to moved to another common target (eg reactperflogger) once we want to make this usable in other components, but keeping it scoped to User Timing for now. Changelog: [Internal] Reviewed By: bgirard Differential Revision: D57881823 fbshipit-source-id: 11ba09cbc01a102a72eee65ce6d6aeca508e864a --- .../webperformance/NativePerformance.cpp | 108 +++++++++++++++++- .../timeline/PerformanceEntryReporter.cpp | 2 +- .../timeline/PerformanceEntryReporter.h | 2 +- .../ReactPerfettoCategories.cpp | 14 +++ .../reactperflogger/ReactPerfettoCategories.h | 18 +++ 5 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.cpp create mode 100644 packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoCategories.h 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