From 62b8a7b4ef127024d1be59637cd99bf1a497325d Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Thu, 21 Aug 2025 03:06:52 -0700 Subject: [PATCH] Switch Perf Monitor metric to Long Tasks (#53297) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53297 Pivots our display metric for the V2 Perf Monitor experiment by switching to Long Tasks. - Implements a new "__ReactNative__LongTask" metrics event (note: prefixed, since this sits outside the Web Vitals spec). Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D79556595 fbshipit-source-id: 239cf44884f67bf62295b92e2262ae1811d17e4a --- .../ReactAndroid/api/ReactAndroid.api | 2 +- .../react/devsupport/DevSupportManagerBase.kt | 10 ++----- .../PerfMonitorOverlayViewManager.kt | 7 +++-- .../interfaces/PerfMonitorOverlayManager.kt | 7 +---- .../interfaces/PerfMonitorV2Handler.kt | 3 +-- .../facebook/react/runtime/ReactHostImpl.kt | 5 ++-- .../runtime/jni/JReactHostInspectorTarget.cpp | 1 - .../runtime/jni/JReactHostInspectorTarget.h | 15 +++-------- .../jsinspector-modern/PerfMonitorV2.cpp | 26 ++++++++++--------- .../jsinspector-modern/PerfMonitorV2.h | 11 ++++---- .../cdpmetrics/CdpMetricsReporter.cpp | 23 ++++++++++++++++ .../cdpmetrics/CdpMetricsReporter.h | 1 + .../timeline/PerformanceEntryReporter.cpp | 10 +++++++ .../PerformanceEntryReporterListeners.h | 4 ++- 14 files changed, 70 insertions(+), 55 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 8708804f63c..1095f6a08af 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -2031,7 +2031,7 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/ public fun startInspector ()V public fun stopInspector ()V public fun toggleElementInspector ()V - public fun unstable_updatePerfMonitor (Ljava/lang/String;III)V + public fun unstable_updatePerfMonitor (III)V } public abstract interface class com/facebook/react/devsupport/DevSupportManagerBase$CallbackWithBundleLoader { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt index ac7f03b04f1..faa541cab90 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt @@ -937,18 +937,12 @@ public abstract class DevSupportManagerBase( } override fun unstable_updatePerfMonitor( - eventName: String, - durationMs: Int, + longTaskDuration: Int, responsivenessScore: Int, ttl: Int, ) { perfMonitorOverlayManager?.update( - PerfMonitorOverlayManager.PerfMonitorUpdateData( - eventName, - durationMs, - responsivenessScore, - ttl, - ) + PerfMonitorOverlayManager.PerfMonitorUpdateData(longTaskDuration, responsivenessScore, ttl) ) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/PerfMonitorOverlayViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/PerfMonitorOverlayViewManager.kt index 3b6c05c2800..74f0a9c980c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/PerfMonitorOverlayViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/PerfMonitorOverlayViewManager.kt @@ -39,7 +39,6 @@ internal class PerfMonitorOverlayViewManager( private var hasInteractionData: Boolean = false private var interactionDialog: Dialog? = null private var buttonDialog: Dialog? = null - private var interactionNameLabel: TextView? = null private var durationLabel: TextView? = null private var ttl: Int = 0 private var hideAfterTimeoutHandler: Handler? = null @@ -70,7 +69,6 @@ internal class PerfMonitorOverlayViewManager( override fun update(data: PerfMonitorOverlayManager.PerfMonitorUpdateData) { UiThreadUtil.runOnUiThread { ensureInitialized() - interactionNameLabel?.text = data.eventName durationLabel?.text = String.format(Locale.US, "%d ms", data.durationMs) durationLabel?.setTextColor(getDurationHighlightColor(data.responsivenessScore)) hasInteractionData = true @@ -115,9 +113,10 @@ internal class PerfMonitorOverlayViewManager( private fun createDialog(context: Context) { val containerLayout = createInnerLayout(context) - interactionNameLabel = + val longTaskLabel = TextView(context).apply { textSize = TEXT_SIZE_PRIMARY + text = "Long Task" setTextColor(Color.WHITE) typeface = TYPEFACE_BOLD } @@ -127,7 +126,7 @@ internal class PerfMonitorOverlayViewManager( setTextColor(COLOR_TEXT_GREEN) typeface = TYPEFACE_BOLD } - containerLayout.addView(interactionNameLabel) + containerLayout.addView(longTaskLabel) containerLayout.addView(durationLabel) val dialog = diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorOverlayManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorOverlayManager.kt index 64cd2c87975..ca5b5fc6d70 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorOverlayManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorOverlayManager.kt @@ -9,12 +9,7 @@ package com.facebook.react.devsupport.interfaces /** [Experimental] Interface to manage the V2 Perf Monitor overlay. */ internal interface PerfMonitorOverlayManager { - data class PerfMonitorUpdateData( - val eventName: String, - val durationMs: Int, - val responsivenessScore: Int, - val ttl: Int, - ) + data class PerfMonitorUpdateData(val durationMs: Int, val responsivenessScore: Int, val ttl: Int) /** Enable the Perf Monitor overlay. Will be shown when updates are received. */ public fun enable() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorV2Handler.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorV2Handler.kt index 540e522888f..8905ecd5cdb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorV2Handler.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/PerfMonitorV2Handler.kt @@ -16,8 +16,7 @@ internal interface PerfMonitorV2Handler { /** [Experimental] Update the V2 Perf Monitor overlay with the given data. */ // FIXME(T233950466): Refactor ReactHostImpl/DevSupport setup to avoid this public API addition public fun unstable_updatePerfMonitor( - eventName: String, - durationMs: Int, + longTaskDuration: Int, responsivenessScore: Int, ttl: Int, ) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt index 28b6616f4a2..7e96b3e251d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt @@ -421,13 +421,12 @@ public class ReactHostImpl( @DoNotStrip private fun unstable_updatePerfMonitor( - eventName: String, - durationMs: Int, + longTaskDuration: Int, responsivenessScore: Int, ttl: Int, ) { if (devSupportManager is PerfMonitorV2Handler) { - devSupportManager.unstable_updatePerfMonitor(eventName, durationMs, responsivenessScore, ttl) + devSupportManager.unstable_updatePerfMonitor(longTaskDuration, responsivenessScore, ttl) } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp index 2cc5554f6f9..fdc8258c1ba 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp @@ -131,7 +131,6 @@ void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate( const PerfMonitorUpdateRequest& request) { if (auto javaReactHostImplStrong = javaReactHostImpl_->get()) { javaReactHostImplStrong->unstable_updatePerfMonitor( - request.activeInteraction.eventName, request.activeInteraction.duration, request.activeInteraction.responsivenessScore, request.activeInteraction.ttl); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h index 6d8a5f54598..2640bfca15f 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h @@ -39,20 +39,13 @@ struct JReactHostImpl : public jni::JavaClass { } void unstable_updatePerfMonitor( - const std::string& interactionName, - uint16_t durationMs, + uint16_t longTaskDuration, jsinspector_modern::InteractionResponsivenessScore responsivenessScore, uint16_t ttl) { - static auto method = - javaClassStatic() - ->getMethod, jint, jint, jint)>( - "unstable_updatePerfMonitor"); + static auto method = javaClassStatic()->getMethod( + "unstable_updatePerfMonitor"); method( - self(), - jni::make_jstring(interactionName), - durationMs, - static_cast(responsivenessScore), - ttl); + self(), longTaskDuration, static_cast(responsivenessScore), ttl); } jni::local_ref> getHostMetadata() const { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.cpp b/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.cpp index 1c6a3af12f8..90a0278db10 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.cpp @@ -15,7 +15,7 @@ namespace facebook::react::jsinspector_modern { namespace { -constexpr uint16_t MIN_DURATION = 10; +constexpr uint16_t MIN_DURATION = 200; constexpr uint16_t DEFAULT_TTL = 4000; constexpr uint16_t BAD_EVENT_TTL = 20000; @@ -39,6 +39,10 @@ void PerfMonitorUpdateHandler::handlePerfMetricsUpdate( auto payload = folly::parseJson(message); if (payload.isObject()) { + if (payload["name"] != "__ReactNative__LongTask") { + return; + } + auto duration = static_cast(payload["duration"].asInt()); if (duration < MIN_DURATION) { @@ -50,36 +54,34 @@ void PerfMonitorUpdateHandler::handlePerfMetricsUpdate( ? BAD_EVENT_TTL : DEFAULT_TTL; - InteractionPayload newInteraction{ - payload["eventName"].asString(), + LongTaskPayload newEvent{ static_cast(payload["startTime"].asInt()), duration, responsivenessScore, ttl}; - if (shouldOverrideLastInteraction(newInteraction)) { - lastInteraction_ = newInteraction; + if (shouldOverrideLastEvent(newEvent)) { + lastEvent_ = newEvent; delegate_.unstable_onPerfMonitorUpdate( - PerfMonitorUpdateRequest{newInteraction}); + PerfMonitorUpdateRequest{newEvent}); } } } -bool PerfMonitorUpdateHandler::shouldOverrideLastInteraction( - const InteractionPayload& newInteraction) { - if (!lastInteraction_) { +bool PerfMonitorUpdateHandler::shouldOverrideLastEvent( + const LongTaskPayload& newEvent) { + if (!lastEvent_) { return true; } // Override if last event has expired if (HighResTimeStamp::now().toDOMHighResTimeStamp() > - lastInteraction_->startTime + lastInteraction_->ttl) { + lastEvent_->startTime + lastEvent_->ttl) { return true; } // Override if same or greater responsiveness score - if (newInteraction.responsivenessScore >= - lastInteraction_->responsivenessScore) { + if (newEvent.responsivenessScore >= lastEvent_->responsivenessScore) { return true; } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.h b/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.h index 8a4ff67ae45..a95a6bde628 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/PerfMonitorV2.h @@ -24,8 +24,7 @@ enum class InteractionResponsivenessScore : int32_t { Poor = 2 }; -struct InteractionPayload { - std::string eventName; +struct LongTaskPayload { uint16_t startTime; uint16_t duration; InteractionResponsivenessScore responsivenessScore; @@ -33,7 +32,7 @@ struct InteractionPayload { }; struct PerfMonitorUpdateRequest { - InteractionPayload activeInteraction; + LongTaskPayload activeInteraction; }; /** @@ -42,7 +41,7 @@ struct PerfMonitorUpdateRequest { */ class PerfMonitorUpdateHandler { public: - PerfMonitorUpdateHandler(HostTargetDelegate& delegate) + explicit PerfMonitorUpdateHandler(HostTargetDelegate& delegate) : delegate_(delegate) {} /** @@ -52,9 +51,9 @@ class PerfMonitorUpdateHandler { private: HostTargetDelegate& delegate_; - std::optional lastInteraction_; + std::optional lastEvent_; - bool shouldOverrideLastInteraction(const InteractionPayload& newInteraction); + bool shouldOverrideLastEvent(const LongTaskPayload& newInteraction); }; } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.cpp b/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.cpp index 1368992385e..01b51d2fc07 100644 --- a/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.cpp @@ -72,4 +72,27 @@ void CdpMetricsReporter::onEventTimingEntry( }); } +void CdpMetricsReporter::onLongTaskEntry( + const PerformanceLongTaskTiming& entry) { + runtimeExecutor_([entry = std::move(entry)](jsi::Runtime& runtime) { + auto global = runtime.global(); + if (!global.hasProperty(runtime, metricsReporterName.data())) { + return; + } + + folly::dynamic jsonPayload = folly::dynamic::object; + jsonPayload["name"] = "__ReactNative__LongTask"; + jsonPayload["duration"] = + static_cast(entry.duration.toDOMHighResTimeStamp()); + jsonPayload["startTime"] = + static_cast(entry.startTime.toDOMHighResTimeStamp()); + + auto jsonString = folly::toJson(jsonPayload); + auto jsiString = jsi::String::createFromUtf8(runtime, jsonString); + auto metricsReporter = + global.getPropertyAsFunction(runtime, metricsReporterName.data()); + metricsReporter.call(runtime, jsiString); + }); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.h b/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.h index 386ded4b8fc..227b22bd344 100644 --- a/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.h +++ b/packages/react-native/ReactCommon/react/performance/cdpmetrics/CdpMetricsReporter.h @@ -28,6 +28,7 @@ class CdpMetricsReporter : public PerformanceEntryReporterEventTimingListener { explicit CdpMetricsReporter(RuntimeExecutor runtimeExecutor); void onEventTimingEntry(const PerformanceEventTiming& entry) override; + void onLongTaskEntry(const PerformanceLongTaskTiming& entry) override; private: const RuntimeExecutor runtimeExecutor_{}; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index d4c6423b231..bdce91ee4b3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -297,6 +297,16 @@ void PerformanceEntryReporter::reportLongTask( } observerRegistry_->queuePerformanceEntry(entry); + + std::vector listenersCopy; + { + std::shared_lock lock(listenersMutex_); + listenersCopy = eventTimingListeners_; + } + + for (auto* listener : listenersCopy) { + listener->onLongTaskEntry(entry); + } } void PerformanceEntryReporter::reportResourceTiming( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporterListeners.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporterListeners.h index b952a5b148a..fb198fc3352 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporterListeners.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporterListeners.h @@ -18,7 +18,9 @@ class PerformanceEntryReporterEventTimingListener { public: virtual ~PerformanceEntryReporterEventTimingListener() = default; - virtual void onEventTimingEntry(const PerformanceEventTiming& entry) = 0; + virtual void onEventTimingEntry(const PerformanceEventTiming& /*entry*/) {} + + virtual void onLongTaskEntry(const PerformanceLongTaskTiming& /*entry*/) {} }; } // namespace facebook::react