Skip to content

Commit 829bd61

Browse files
sbuggaymeta-codesync[bot]
authored andcommitted
Add FrameTiming module (#54362)
Summary: Pull Request resolved: #54362 Adds a new frame timing hook that leverages [OnFrameMetricsAvailableListener](https://developer.android.com/reference/android/view/Window.OnFrameMetricsAvailableListener). No support for idle or dropped frames yet, just basic frame timing and naively reporting a "commit" when the frame is over. Will continue iterating and mapping events between browser/native. {F1983178153} Changelog: [Internal] Reviewed By: hoxyq, huntie Differential Revision: D85999774 fbshipit-source-id: 39f659671ce227439b3ab2959e306b911a4d6df2
1 parent b7012ba commit 829bd61

File tree

11 files changed

+197
-1
lines changed

11 files changed

+197
-1
lines changed

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3186,6 +3186,7 @@ public final class com/facebook/react/soloader/OpenSourceMergedSoMapping : com/f
31863186
public final fun libreact_devsupportjni_so ()I
31873187
public final fun libreact_featureflagsjni_so ()I
31883188
public final fun libreact_newarchdefaults_so ()I
3189+
public final fun libreact_performancetracerjni_so ()I
31893190
public final fun libreactnative_so ()I
31903191
public final fun libreactnativeblob_so ()I
31913192
public final fun libreactnativejni_common_so ()I

packages/react-native/ReactAndroid/proguard-rules.pro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,6 @@
7373
-keep public class com.facebook.imageutils.** {
7474
public *;
7575
}
76+
77+
# devsupport - keep classes that are referenced via JNI or reflection
78+
-keep class com.facebook.react.devsupport.** { *; }

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import com.facebook.react.devsupport.perfmonitor.PerfMonitorDevHelper
7272
import com.facebook.react.devsupport.perfmonitor.PerfMonitorOverlayManager
7373
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
7474
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags
75+
import com.facebook.react.internal.tracing.PerformanceTracer
7576
import com.facebook.react.modules.core.RCTNativeAppEventEmitter
7677
import com.facebook.react.modules.debug.interfaces.DeveloperSettings
7778
import com.facebook.react.packagerconnection.RequestHandler
@@ -211,6 +212,8 @@ public abstract class DevSupportManagerBase(
211212
private var perfMonitorOverlayManager: PerfMonitorOverlayManager? = null
212213
private var perfMonitorInitialized = false
213214
private var tracingStateProvider: TracingStateProvider? = null
215+
private var tracingStateSubscriptionId: Int? = null
216+
private var frameTiming: FrameTiming? = null
214217

215218
public override var keyboardShortcutsEnabled: Boolean = true
216219
public override var devMenuEnabled: Boolean = true
@@ -969,12 +972,37 @@ public abstract class DevSupportManagerBase(
969972
isPackagerConnected = true
970973
perfMonitorOverlayManager?.enable()
971974
perfMonitorOverlayManager?.startBackgroundTrace()
975+
976+
// Subscribe to tracing state changes
977+
tracingStateSubscriptionId =
978+
PerformanceTracer.subscribeToTracingStateChanges(
979+
object : PerformanceTracer.TracingStateCallback {
980+
override fun onTracingStateChanged(isTracing: Boolean) {
981+
if (isTracing) {
982+
if (frameTiming == null) {
983+
currentActivity?.window?.let { window ->
984+
frameTiming = FrameTiming(window)
985+
}
986+
}
987+
frameTiming?.startMonitoring()
988+
} else {
989+
frameTiming?.stopMonitoring()
990+
}
991+
}
992+
}
993+
)
972994
}
973995

974996
override fun onPackagerDisconnected() {
975997
isPackagerConnected = false
976998
perfMonitorOverlayManager?.disable()
977999
perfMonitorOverlayManager?.stopBackgroundTrace()
1000+
1001+
// Unsubscribe from tracing state changes
1002+
tracingStateSubscriptionId?.let { subscriptionId ->
1003+
PerformanceTracer.unsubscribeFromTracingStateChanges(subscriptionId)
1004+
tracingStateSubscriptionId = null
1005+
}
9781006
}
9791007

9801008
override fun onPackagerReloadCommand() {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.devsupport
9+
10+
import android.os.Build
11+
import android.os.Handler
12+
import android.os.Looper
13+
import android.view.FrameMetrics
14+
import android.view.Window
15+
import com.facebook.soloader.SoLoader
16+
17+
internal class FrameTiming(private val window: Window) {
18+
init {
19+
SoLoader.loadLibrary("react_devsupportjni")
20+
}
21+
22+
private var frameCounter: Int = 0
23+
24+
private val frameMetricsListener =
25+
Window.OnFrameMetricsAvailableListener { _, frameMetrics, dropCount ->
26+
val metrics = FrameMetrics(frameMetrics)
27+
28+
val paintStartTime = metrics.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP)
29+
val totalDuration = metrics.getMetric(FrameMetrics.TOTAL_DURATION)
30+
31+
val currentFrame = frameCounter++
32+
reportFrameTiming(
33+
frame = currentFrame,
34+
paintStartNanos = paintStartTime,
35+
paintEndNanos = paintStartTime + totalDuration,
36+
)
37+
}
38+
39+
companion object {
40+
@JvmStatic
41+
private external fun reportFrameTiming(frame: Int, paintStartNanos: Long, paintEndNanos: Long)
42+
}
43+
44+
private val handler = Handler(Looper.getMainLooper())
45+
46+
internal fun startMonitoring() {
47+
frameCounter = 0
48+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
49+
return
50+
}
51+
window.addOnFrameMetricsAvailableListener(frameMetricsListener, handler)
52+
}
53+
54+
internal fun stopMonitoring() {
55+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
56+
return
57+
}
58+
window.removeOnFrameMetricsAvailableListener(frameMetricsListener)
59+
}
60+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/soloader/OpenSourceMergedSoMapping.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
2828
"react_devsupportjni",
2929
"react_featureflagsjni",
3030
"react_newarchdefaults",
31+
"react_performancetracerjni",
3132
"reactnativeblob",
3233
"reactnativejni",
3334
"reactnativejni_common",
@@ -57,6 +58,7 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
5758
"react_devsupportjni" -> libreact_devsupportjni_so()
5859
"react_featureflagsjni" -> libreact_featureflagsjni_so()
5960
"react_newarchdefaults" -> libreact_newarchdefaults_so()
61+
"react_performancetracerjni" -> libreact_performancetracerjni_so()
6062
"reactnative" -> libreactnative_so()
6163
"reactnativeblob" -> libreactnativeblob_so()
6264
"reactnativejni" -> libreactnativejni_so()
@@ -88,6 +90,8 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
8890

8991
public external fun libreact_newarchdefaults_so(): Int
9092

93+
public external fun libreact_performancetracerjni_so(): Int
94+
9195
public external fun libreactnative_so(): Int
9296

9397
public external fun libreactnativeblob_so(): Int

packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ add_react_android_subdir(src/main/jni/react/runtime/cxxreactpackage)
153153
add_react_android_subdir(src/main/jni/react/runtime/jni)
154154
add_react_android_subdir(src/main/jni/react/runtime/hermes/jni)
155155
add_react_android_subdir(src/main/jni/react/devsupport)
156+
add_react_android_subdir(src/main/jni/react/tracing)
156157

157158
# SoMerging Utils
158159
include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake)
@@ -197,6 +198,7 @@ add_library(reactnative
197198
$<TARGET_OBJECTS:react_newarchdefaults>
198199
$<TARGET_OBJECTS:react_performance_cdpmetrics>
199200
$<TARGET_OBJECTS:react_performance_timeline>
201+
$<TARGET_OBJECTS:react_performancetracerjni>
200202
$<TARGET_OBJECTS:react_renderer_animations>
201203
$<TARGET_OBJECTS:react_renderer_attributedstring>
202204
$<TARGET_OBJECTS:react_renderer_componentregistry>
@@ -288,6 +290,7 @@ target_include_directories(reactnative
288290
$<TARGET_PROPERTY:react_newarchdefaults,INTERFACE_INCLUDE_DIRECTORIES>
289291
$<TARGET_PROPERTY:react_performance_cdpmetrics,INTERFACE_INCLUDE_DIRECTORIES>
290292
$<TARGET_PROPERTY:react_performance_timeline,INTERFACE_INCLUDE_DIRECTORIES>
293+
$<TARGET_PROPERTY:react_performancetracerjni,INTERFACE_INCLUDE_DIRECTORIES>
291294
$<TARGET_PROPERTY:react_renderer_animations,INTERFACE_INCLUDE_DIRECTORIES>
292295
$<TARGET_PROPERTY:react_renderer_attributedstring,INTERFACE_INCLUDE_DIRECTORIES>
293296
$<TARGET_PROPERTY:react_renderer_componentregistry,INTERFACE_INCLUDE_DIRECTORIES>

packages/react-native/ReactAndroid/src/main/jni/react/devsupport/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ target_include_directories(react_devsupportjni PUBLIC .)
2020
target_link_libraries(react_devsupportjni
2121
fbjni
2222
jsinspector
23-
react_networking)
23+
jsinspector_tracing
24+
react_networking
25+
react_timing)
2426

2527
target_compile_reactnative_options(react_devsupportjni PRIVATE)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "JFrameTiming.h"
9+
10+
#include <jsinspector-modern/tracing/PerformanceTracer.h>
11+
#include <react/timing/primitives.h>
12+
13+
namespace facebook::react::jsinspector_modern {
14+
15+
void JFrameTiming::reportFrameTiming(
16+
jni::alias_ref<jclass> /*unused*/,
17+
jint frameNumber,
18+
jlong paintStartNanos,
19+
jlong paintEndNanos) {
20+
auto& performanceTracer = tracing::PerformanceTracer::getInstance();
21+
22+
auto startTime = HighResTimeStamp::fromDOMHighResTimeStamp(
23+
static_cast<double>(paintStartNanos) / 1e6);
24+
auto endTime = HighResTimeStamp::fromDOMHighResTimeStamp(
25+
static_cast<double>(paintEndNanos) / 1e6);
26+
27+
performanceTracer.reportFrameTiming(frameNumber, startTime, endTime);
28+
}
29+
30+
void JFrameTiming::registerNatives() {
31+
javaClassLocal()->registerNatives({
32+
makeNativeMethod("reportFrameTiming", JFrameTiming::reportFrameTiming),
33+
});
34+
}
35+
36+
} // namespace facebook::react::jsinspector_modern
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <fbjni/fbjni.h>
11+
12+
namespace facebook::react::jsinspector_modern {
13+
14+
/**
15+
* JNI wrapper for reporting frame timing to PerformanceTracer.
16+
*/
17+
class JFrameTiming : public jni::JavaClass<JFrameTiming> {
18+
public:
19+
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/devsupport/FrameTiming;";
20+
21+
static void
22+
reportFrameTiming(jni::alias_ref<jclass> /*unused*/, jint frame, jlong paintStartNanos, jlong paintEndNanos);
23+
24+
static void registerNatives();
25+
26+
private:
27+
JFrameTiming() = delete;
28+
};
29+
30+
} // namespace facebook::react::jsinspector_modern

packages/react-native/ReactAndroid/src/main/jni/react/devsupport/OnLoad.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "JCxxInspectorPackagerConnection.h"
99
#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h"
10+
#include "JFrameTiming.h"
1011
#include "JInspectorFlags.h"
1112
#include "JInspectorNetworkReporter.h"
1213

@@ -18,6 +19,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*unused*/) {
1819
registerNatives();
1920
facebook::react::jsinspector_modern::
2021
JCxxInspectorPackagerConnectionWebSocketDelegate::registerNatives();
22+
facebook::react::jsinspector_modern::JFrameTiming::registerNatives();
2123
facebook::react::jsinspector_modern::JInspectorFlags::registerNatives();
2224
facebook::react::jsinspector_modern::JInspectorNetworkReporter::
2325
registerNatives();

0 commit comments

Comments
 (0)