From b68329f7ca67484ed37d459eec4a74f30fd54846 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 29 Oct 2025 15:35:35 -0700 Subject: [PATCH] Implement Kotlin API for PerformanceTracer in RNDT (#54316) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/54316 Changelog: [internal] This defines an internal class in Kotlin to access `PerformanceTracer` methods, necessary for platform-specific integrations on Android. Reviewed By: sbuggay Differential Revision: D85689126 fbshipit-source-id: 84cb675984b90c677c3a322b191e24cc6b58c74f --- .../internal/tracing/PerformanceTracer.kt | 109 ++++++++ .../src/main/jni/react/tracing/OnLoad.cpp | 16 ++ .../tracing/PerformanceTracerCxxInterop.cpp | 241 ++++++++++++++++++ .../tracing/PerformanceTracerCxxInterop.h | 63 +++++ 4 files changed, 429 insertions(+) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/tracing/OnLoad.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.cpp create mode 100644 packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.h diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt new file mode 100644 index 00000000000..dad904b4b1d --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt @@ -0,0 +1,109 @@ +/* + * 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. + */ + +package com.facebook.react.internal.tracing + +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.bridge.ReadableNativeMap +import com.facebook.soloader.SoLoader + +/** + * JNI interface to access PerformanceTracer methods from React Native Inspector Modern. + * + * This provides access to reportMark and reportMeasure methods which are used for performance + * tracing in the React Native DevTools. + */ +@DoNotStrip +public object PerformanceTracer { + init { + SoLoader.loadLibrary("react_performancetracerjni") + } + + /** Callback interface for tracing state changes. */ + @DoNotStrip + public interface TracingStateCallback { + /** + * Called when tracing state changes. + * + * @param isTracing true if tracing has started, false if tracing has stopped + */ + @DoNotStrip public fun onTracingStateChanged(isTracing: Boolean) + } + + /** + * Report a Performance.mark() event - a labelled timestamp. + * + * @param name The name/label of the mark + * @param timestampNanos The timestamp in nanoseconds (monotonic time) + * @param detail Optional map with additional detail (pass null if not needed) + */ + @DoNotStrip + @JvmStatic + public external fun reportMark(name: String, timestampNanos: Long, detail: ReadableNativeMap?) + + /** + * Report a Performance.measure() event - a labelled duration. + * + * @param name The name/label of the measure + * @param startTimestampNanos The start timestamp in nanoseconds (monotonic time) + * @param durationNanos The duration in nanoseconds + * @param detail Optional map with additional detail (pass null if not needed) + */ + @DoNotStrip + @JvmStatic + public external fun reportMeasure( + name: String, + startTimestampNanos: Long, + durationNanos: Long, + detail: ReadableNativeMap?, + ) + + /** + * Report a TimeStamp Trace Event - a labelled entry on Performance timeline. + * + * @param name The name/label of the timestamp + * @param startTimeNanos Start timestamp in nanoseconds (monotonic time) + * @param endTimeNanos End timestamp in nanoseconds (monotonic time) + * @param trackName Optional track name for organizing the timestamp + * @param trackGroup Optional track group for organizing the timestamp + * @param color Optional color name (e.g., "primary", "secondary", "error", "warning") + */ + @DoNotStrip + @JvmStatic + public external fun reportTimeStamp( + name: String, + startTimeNanos: Long, + endTimeNanos: Long, + trackName: String?, + trackGroup: String?, + color: String?, + ) + + /** + * Check if the tracer is currently tracing. + * + * @return true if tracing is active, false otherwise + */ + @DoNotStrip @JvmStatic public external fun isTracing(): Boolean + + /** + * Subscribe to tracing state changes (start/stop events). + * + * @param callback The callback to invoke when tracing starts or stops + * @return A subscription ID that can be used to unsubscribe + */ + @DoNotStrip + @JvmStatic + public external fun subscribeToTracingStateChanges(callback: TracingStateCallback): Int + + /** + * Unsubscribe from tracing state changes. + * + * @param subscriptionId The subscription ID returned from subscribeToTracingStateChanges + */ + @DoNotStrip @JvmStatic public external fun unsubscribeFromTracingStateChanges(subscriptionId: Int) +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/tracing/OnLoad.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/tracing/OnLoad.cpp new file mode 100644 index 00000000000..1eb4a04ee80 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/tracing/OnLoad.cpp @@ -0,0 +1,16 @@ +/* + * 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 "PerformanceTracerCxxInterop.h" + +#include + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*unused*/) { + return facebook::jni::initialize(vm, [] { + facebook::react::PerformanceTracerCxxInterop::registerNatives(); + }); +} diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.cpp new file mode 100644 index 00000000000..6d2c3359073 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.cpp @@ -0,0 +1,241 @@ +/* + * 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 "PerformanceTracerCxxInterop.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace facebook::react { + +void TracingStateCallback::onTracingStateChanged(bool isTracing) const { + static const auto method = + javaClassStatic()->getMethod("onTracingStateChanged"); + method(self(), static_cast(isTracing)); +} + +namespace { + +class TracingCallbackRegistry { + public: + static TracingCallbackRegistry& getInstance() { + static TracingCallbackRegistry instance; + return instance; + } + + void registerCallback( + uint32_t subscriptionId, + jni::global_ref callback) { + std::lock_guard lock(mutex_); + callbacks_[subscriptionId] = std::move(callback); + } + + void unregisterCallback(uint32_t subscriptionId) { + std::lock_guard lock(mutex_); + callbacks_.erase(subscriptionId); + } + + jni::global_ref getCallback(uint32_t subscriptionId) { + std::lock_guard lock(mutex_); + auto it = callbacks_.find(subscriptionId); + if (it != callbacks_.end()) { + return it->second; + } + return nullptr; + } + + private: + std::mutex mutex_; + std::map> callbacks_; +}; + +} // namespace + +void PerformanceTracerCxxInterop::reportMark( + jni::alias_ref /*jthis*/, + jni::alias_ref name, + jlong timestampNanos, + jni::alias_ref detail) { + auto& tracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); + + if (!tracer.isTracing()) { + return; + } + + std::string nameStr = name->toStdString(); + HighResTimeStamp timestamp = HighResTimeStamp::fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point( + std::chrono::nanoseconds(timestampNanos))); + + folly::dynamic detailDynamic = nullptr; + if (detail) { + detailDynamic = detail->cthis()->consume(); + } + + tracer.reportMark(nameStr, timestamp, std::move(detailDynamic)); +} + +void PerformanceTracerCxxInterop::reportMeasure( + jni::alias_ref /*jthis*/, + jni::alias_ref name, + jlong startTimestampNanos, + jlong durationNanos, + jni::alias_ref detail) { + auto& tracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); + + if (!tracer.isTracing()) { + return; + } + + std::string nameStr = name->toStdString(); + HighResTimeStamp startTimestamp = + HighResTimeStamp::fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point( + std::chrono::nanoseconds(startTimestampNanos))); + HighResDuration duration = HighResDuration::fromNanoseconds(durationNanos); + + folly::dynamic detailDynamic = nullptr; + if (detail) { + detailDynamic = detail->cthis()->consume(); + } + + tracer.reportMeasure( + nameStr, startTimestamp, duration, std::move(detailDynamic)); +} + +void PerformanceTracerCxxInterop::reportTimeStamp( + jni::alias_ref /*jthis*/, + jni::alias_ref name, + jlong startTimeNanos, + jlong endTimeNanos, + jni::alias_ref trackName, + jni::alias_ref trackGroup, + jni::alias_ref color) { + auto& tracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); + + if (!tracer.isTracing()) { + return; + } + + std::string nameStr = name->toStdString(); + + // Convert start timestamp + HighResTimeStamp startTimestamp = + HighResTimeStamp::fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point( + std::chrono::nanoseconds(startTimeNanos))); + + // Convert end timestamp + HighResTimeStamp endTimestamp = + HighResTimeStamp::fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point( + std::chrono::nanoseconds(endTimeNanos))); + + // Handle optional string parameters + std::optional trackNameOpt = std::nullopt; + if (trackName) { + trackNameOpt = trackName->toStdString(); + } + + std::optional trackGroupOpt = std::nullopt; + if (trackGroup) { + trackGroupOpt = trackGroup->toStdString(); + } + + // Handle optional color parameter + std::optional colorOpt = + std::nullopt; + if (color) { + colorOpt = jsinspector_modern::tracing::getConsoleTimeStampColorFromString( + color->toStdString()); + } + + tracer.reportTimeStamp( + nameStr, + startTimestamp, + endTimestamp, + trackNameOpt, + trackGroupOpt, + colorOpt); +} + +jboolean PerformanceTracerCxxInterop::isTracing( + jni::alias_ref /*jthis*/) { + return static_cast( + jsinspector_modern::tracing::PerformanceTracer::getInstance() + .isTracing()); +} + +jint PerformanceTracerCxxInterop::subscribeToTracingStateChanges( + jni::alias_ref /*jthis*/, + jni::alias_ref callback) { + auto globalCallback = jni::make_global(callback); + + auto& tracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); + + auto callbackWrapper = std::make_shared(0); + + uint32_t subscriptionId = + tracer.subscribeToTracingStateChanges([callbackWrapper](bool isTracing) { + auto callback = TracingCallbackRegistry::getInstance().getCallback( + *callbackWrapper); + if (callback) { + try { + jni::ThreadScope::WithClassLoader([&] { + auto callbackClass = callback->getClass(); + auto onTracingStateChangedMethod = + callbackClass->getMethod( + "onTracingStateChanged"); + onTracingStateChangedMethod( + callback, static_cast(isTracing)); + }); + } catch (const std::exception& e) { + } + } + }); + + *callbackWrapper = subscriptionId; + TracingCallbackRegistry::getInstance().registerCallback( + subscriptionId, globalCallback); + + return static_cast(subscriptionId); +} + +void PerformanceTracerCxxInterop::unsubscribeFromTracingStateChanges( + jni::alias_ref /*jthis*/, + jint subscriptionId) { + auto& tracer = jsinspector_modern::tracing::PerformanceTracer::getInstance(); + tracer.unsubscribeFromTracingStateChanges( + static_cast(subscriptionId)); + TracingCallbackRegistry::getInstance().unregisterCallback( + static_cast(subscriptionId)); +} + +void PerformanceTracerCxxInterop::registerNatives() { + javaClassLocal()->registerNatives( + {makeNativeMethod("reportMark", PerformanceTracerCxxInterop::reportMark), + makeNativeMethod( + "reportMeasure", PerformanceTracerCxxInterop::reportMeasure), + makeNativeMethod( + "reportTimeStamp", PerformanceTracerCxxInterop::reportTimeStamp), + makeNativeMethod("isTracing", PerformanceTracerCxxInterop::isTracing), + makeNativeMethod( + "subscribeToTracingStateChanges", + PerformanceTracerCxxInterop::subscribeToTracingStateChanges), + makeNativeMethod( + "unsubscribeFromTracingStateChanges", + PerformanceTracerCxxInterop::unsubscribeFromTracingStateChanges)}); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.h new file mode 100644 index 00000000000..d4592d96367 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/tracing/PerformanceTracerCxxInterop.h @@ -0,0 +1,63 @@ +/* + * 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 +#include +#include + +namespace facebook::react { + +class TracingStateCallback : public facebook::jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = + "Lcom/facebook/react/internal/tracing/PerformanceTracer$TracingStateCallback;"; + + void onTracingStateChanged(bool isTracing) const; +}; + +class PerformanceTracerCxxInterop : public facebook::jni::JavaClass { + public: + static constexpr auto kJavaDescriptor = "Lcom/facebook/react/internal/tracing/PerformanceTracer;"; + + static void reportMark( + facebook::jni::alias_ref jthis, + facebook::jni::alias_ref name, + jlong timestampNanos, + facebook::jni::alias_ref detail); + + static void reportMeasure( + facebook::jni::alias_ref jthis, + facebook::jni::alias_ref name, + jlong startTimestampNanos, + jlong durationNanos, + facebook::jni::alias_ref detail); + + static void reportTimeStamp( + facebook::jni::alias_ref jthis, + facebook::jni::alias_ref name, + jlong startTimeNanos, + jlong endTimeNanos, + facebook::jni::alias_ref trackName, + facebook::jni::alias_ref trackGroup, + facebook::jni::alias_ref color); + + static jboolean isTracing(facebook::jni::alias_ref jthis); + + static jint subscribeToTracingStateChanges( + facebook::jni::alias_ref jthis, + facebook::jni::alias_ref callback); + + static void unsubscribeFromTracingStateChanges( + facebook::jni::alias_ref jthis, + jint subscriptionId); + + static void registerNatives(); +}; + +} // namespace facebook::react