From a45e44fda8c4cc7b973faa48d6c18877259fb8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 16 Jul 2025 05:31:35 -0700 Subject: [PATCH 1/2] Remove support for specifying track names for Perfetto and RNDT using the "Track:" prefix (#52614) Summary: 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 --- .../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 a91de55fe07d8b..c2044d79aed2a9 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -78,7 +78,8 @@ class PerformanceTracer { const std::string_view& name, HighResTimeStamp start, HighResDuration duration, - const std::optional& trackMetadata); + const std::optional& trackMetadata = + std::nullopt); /** * Record a "TimeStamp" Trace Event - a labelled entry on Performance diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 2aa284f3eb9165..a59efdab0ee9af 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -37,33 +37,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& @@ -300,14 +273,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); } } } @@ -317,25 +288,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 ed555215bbbdfcbe6450c14eaf7a68fb58dc3143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 16 Jul 2025 05:31:35 -0700 Subject: [PATCH 2/2] Add support for details field and custom tracks in performance.mark and performance.measure for DevTools (#52613) Summary: 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 --- .../jsinspector-modern/tracing/CdpTracing.h | 21 ----- .../tracing/PerformanceTracer.cpp | 20 +++-- .../tracing/PerformanceTracer.h | 10 ++- .../webperformance/NativePerformance.cpp | 23 +++++- .../react/performance/timeline/CMakeLists.txt | 3 +- .../timeline/PerformanceEntryReporter.cpp | 80 +++++++++++++++---- .../timeline/PerformanceEntryReporter.h | 20 +++-- 7 files changed, 118 insertions(+), 59 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 ff75da2e068bd7..3b848f20b31809 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -113,7 +113,8 @@ void PerformanceTracer::collectEvents( void PerformanceTracer::reportMark( const std::string_view& name, - HighResTimeStamp start) { + HighResTimeStamp start, + folly::dynamic&& detail) { if (!tracingAtomic_) { return; } @@ -123,6 +124,13 @@ void PerformanceTracer::reportMark( return; } + 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", @@ -130,6 +138,7 @@ void PerformanceTracer::reportMark( .ts = start, .pid = processId_, .tid = oscompat::getCurrentThreadId(), + .args = eventArgs, }); } @@ -137,18 +146,15 @@ void PerformanceTracer::reportMeasure( const std::string_view& name, HighResTimeStamp start, HighResDuration duration, - const std::optional& trackMetadata) { + folly::dynamic&& detail) { if (!tracingAtomic_) { return; } folly::dynamic beginEventArgs = folly::dynamic::object(); - if (trackMetadata.has_value()) { - folly::dynamic devtoolsObject = folly::dynamic::object( - "devtools", - folly::dynamic::object("track", trackMetadata.value().track)); + if (detail != nullptr) { beginEventArgs = - folly::dynamic::object("detail", folly::toJson(devtoolsObject)); + folly::dynamic::object("detail", folly::toJson(std::move(detail))); } auto currentThreadId = oscompat::getCurrentThreadId(); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index c2044d79aed2a9..7623b773a25290 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -7,7 +7,6 @@ #pragma once -#include "CdpTracing.h" #include "ConsoleTimeStamp.h" #include "TraceEvent.h" #include "TraceEventProfile.h" @@ -60,13 +59,17 @@ class PerformanceTracer { const std::function& resultCallback, uint16_t chunkSize); + /** * Record a `Performance.mark()` event - a labelled timestamp. If not * currently tracing, this is a no-op. * * 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 @@ -78,8 +81,7 @@ class PerformanceTracer { const std::string_view& name, HighResTimeStamp start, HighResDuration duration, - const std::optional& trackMetadata = - std::nullopt); + folly::dynamic&& detail = nullptr); /** * Record a "TimeStamp" Trace Event - a labelled entry on Performance diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index cb833ab134c124..9766b8d05c1180 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 a59efdab0ee9af..2f429e27d62df0 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -37,6 +37,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& @@ -142,10 +178,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 { @@ -160,14 +197,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 { @@ -269,33 +305,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