Skip to content

Commit 0b1def9

Browse files
rubennortemeta-codesync[bot]
authored andcommitted
Add performance track for React Native renderer
Summary: Changelog: [General][Added] Add new custom track for React Native Renderer operations in React Native DevTools performance traces This diff adds performance tracking instrumentation to the React Native renderer to provide visibility into rendering operations in React Native Developer Tools traces. On Android (Kotlin), the MountItemDispatcher is updated to wrap key mounting operations (view commands, premount, and mount) with PerformanceTracer.trace calls. These traces appear on the "Renderer" track within the "⚛ Native" track group. On C++ (shared renderer), the ShadowTree::tryCommit method is instrumented with PerformanceTracerSection to track commit and layout operations. The commit trace includes metadata about the source of the commit (e.g., React). Reviewed By: NickGerleman Differential Revision: D97766334 fbshipit-source-id: 71ced2b6e43b6f07509ba5eebcc79cf745778000
1 parent 6860ec7 commit 0b1def9

File tree

10 files changed

+165
-65
lines changed

10 files changed

+165
-65
lines changed

packages/react-native/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ let reactFabric = RNTarget(
463463
"components/virtualview",
464464
"components/root/tests",
465465
],
466-
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga],
466+
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga, .reactJsInspectorTracing],
467467
sources: ["animated", "animationbackend", "animations", "attributedstring", "core", "componentregistry", "componentregistry/native", "components/root", "components/view", "components/view/platform/cxx", "components/scrollview", "components/scrollview/platform/cxx", "components/scrollview/platform/ios", "components/legacyviewmanagerinterop", "components/legacyviewmanagerinterop/platform/ios", "dom", "scheduler", "mounting", "observers/events", "observers/intersection", "observers/mutation", "telemetry", "consistency", "leakchecker", "uimanager", "uimanager/consistency", "viewtransition"]
468468
)
469469

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3114,6 +3114,7 @@ public final class com/facebook/react/soloader/OpenSourceMergedSoMapping : com/f
31143114
public final fun libreact_devsupportjni_so ()I
31153115
public final fun libreact_featureflagsjni_so ()I
31163116
public final fun libreact_newarchdefaults_so ()I
3117+
public final fun libreact_tracingjni_so ()I
31173118
public final fun libreactnative_so ()I
31183119
public final fun libreactnativeblob_so ()I
31193120
public final fun libreactnativejni_common_so ()I

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountItemDispatcher.kt

Lines changed: 99 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.facebook.react.fabric.FabricUIManager
2121
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem
2222
import com.facebook.react.fabric.mounting.mountitems.MountItem
2323
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
24+
import com.facebook.react.internal.tracing.PerformanceTracer
2425
import com.facebook.systrace.Systrace
2526
import java.util.Queue
2627
import java.util.concurrent.ConcurrentLinkedQueue
@@ -201,9 +202,16 @@ internal class MountItemDispatcher(
201202
"MountItemDispatcher::mountViews viewCommandMountItems",
202203
)
203204

204-
for (command in commands) {
205-
dispatchViewCommand(command)
206-
}
205+
PerformanceTracer.trace(
206+
"view commands",
207+
"Renderer",
208+
"\u269b Native",
209+
{ ->
210+
for (command in commands) {
211+
dispatchViewCommand(command)
212+
}
213+
},
214+
)
207215

208216
Systrace.endSection(Systrace.TRACE_TAG_REACT)
209217
}
@@ -215,12 +223,21 @@ internal class MountItemDispatcher(
215223
Systrace.TRACE_TAG_REACT,
216224
"MountItemDispatcher::mountViews preMountItems",
217225
)
218-
for (preMountItem in preMountItems) {
219-
if (ReactNativeFeatureFlags.enableFabricLogs()) {
220-
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem")
221-
}
222-
executeOrEnqueue(preMountItem)
223-
}
226+
227+
PerformanceTracer.trace(
228+
"premount",
229+
"Renderer",
230+
"\u269b Native",
231+
{ ->
232+
for (preMountItem in preMountItems) {
233+
if (ReactNativeFeatureFlags.enableFabricLogs()) {
234+
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem")
235+
}
236+
executeOrEnqueue(preMountItem)
237+
}
238+
},
239+
)
240+
224241
Systrace.endSection(Systrace.TRACE_TAG_REACT)
225242
}
226243

@@ -229,46 +246,58 @@ internal class MountItemDispatcher(
229246
Systrace.TRACE_TAG_REACT,
230247
"MountItemDispatcher::mountViews mountItems to execute",
231248
)
232-
val batchedExecutionStartTime = SystemClock.uptimeMillis()
233249

234-
for (mountItem in items) {
235-
if (ReactNativeFeatureFlags.enableFabricLogs()) {
236-
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
237-
}
250+
PerformanceTracer.trace(
251+
"mount",
252+
"Renderer",
253+
"\u269b Native",
254+
{ ->
255+
val batchedExecutionStartTime = SystemClock.uptimeMillis()
238256

239-
val command = mountItem as? DispatchCommandMountItem
240-
if (command != null) {
241-
dispatchViewCommand(command)
242-
continue
243-
}
257+
for (mountItem in items) {
258+
if (ReactNativeFeatureFlags.enableFabricLogs()) {
259+
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
260+
}
244261

245-
try {
246-
executeOrEnqueue(mountItem)
247-
} catch (e: Throwable) {
248-
// If there's an exception, we want to log diagnostics in prod and rethrow.
249-
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e)
250-
if (ReactNativeFeatureFlags.enableFabricLogs()) {
251-
for (m in items) {
252-
if (m === mountItem) {
253-
// We want to mark the mount item that caused exception
254-
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!")
262+
val command = mountItem as? DispatchCommandMountItem
263+
if (command != null) {
264+
dispatchViewCommand(command)
265+
continue
255266
}
256-
printMountItem(m, "dispatchMountItems: mountItem")
257-
}
258-
}
259267

260-
if (mountItem.getSurfaceId() != View.NO_ID) {
261-
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
262-
}
268+
try {
269+
executeOrEnqueue(mountItem)
270+
} catch (e: Throwable) {
271+
// If there's an exception, we want to log diagnostics in prod and rethrow.
272+
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e)
273+
if (ReactNativeFeatureFlags.enableFabricLogs()) {
274+
for (m in items) {
275+
if (m === mountItem) {
276+
// We want to mark the mount item that caused exception
277+
FLog.e(
278+
TAG,
279+
"dispatchMountItems: mountItem: next mountItem triggered exception!",
280+
)
281+
}
282+
printMountItem(m, "dispatchMountItems: mountItem")
283+
}
284+
}
285+
286+
if (mountItem.getSurfaceId() != View.NO_ID) {
287+
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
288+
}
289+
290+
if (ReactIgnorableMountingException.isIgnorable(e)) {
291+
ReactSoftExceptionLogger.logSoftException(TAG, e)
292+
} else {
293+
throw e
294+
}
295+
}
296+
}
297+
batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
298+
},
299+
)
263300

264-
if (ReactIgnorableMountingException.isIgnorable(e)) {
265-
ReactSoftExceptionLogger.logSoftException(TAG, e)
266-
} else {
267-
throw e
268-
}
269-
}
270-
}
271-
batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
272301
Systrace.endSection(Systrace.TRACE_TAG_REACT)
273302
}
274303

@@ -299,26 +328,34 @@ internal class MountItemDispatcher(
299328
private fun dispatchPreMountItemsImpl(deadline: Long) {
300329
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews")
301330

302-
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
303-
// reentering during dispatchPreMountItems
304-
inDispatch = true
305-
306-
try {
307-
while (true) {
308-
if (System.nanoTime() > deadline) {
309-
break
310-
}
331+
PerformanceTracer.trace(
332+
"premount",
333+
"Renderer",
334+
"\u269b Native",
335+
{ ->
336+
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems
337+
// from
338+
// reentering during dispatchPreMountItems
339+
inDispatch = true
340+
341+
try {
342+
while (true) {
343+
if (System.nanoTime() > deadline) {
344+
break
345+
}
311346

312-
// If list is empty, `poll` will return null, or var will never be set
313-
val preMountItemToDispatch = preMountItems.poll() ?: break
314-
if (ReactNativeFeatureFlags.enableFabricLogs()) {
315-
printMountItem(preMountItemToDispatch, "dispatchPreMountItems")
316-
}
317-
executeOrEnqueue(preMountItemToDispatch)
318-
}
319-
} finally {
320-
inDispatch = false
321-
}
347+
// If list is empty, `poll` will return null, or var will never be set
348+
val preMountItemToDispatch = preMountItems.poll() ?: break
349+
if (ReactNativeFeatureFlags.enableFabricLogs()) {
350+
printMountItem(preMountItemToDispatch, "dispatchPreMountItems")
351+
}
352+
executeOrEnqueue(preMountItemToDispatch)
353+
}
354+
} finally {
355+
inDispatch = false
356+
}
357+
},
358+
)
322359

323360
Systrace.endSection(Systrace.TRACE_TAG_REACT)
324361
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/tracing/PerformanceTracer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import com.facebook.soloader.SoLoader
2020
@DoNotStrip
2121
public object PerformanceTracer {
2222
init {
23-
SoLoader.loadLibrary("react_performancetracerjni")
23+
SoLoader.loadLibrary("react_tracingjni")
2424
}
2525

2626
public fun <T> trace(name: String, block: () -> T): T {

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_tracingjni",
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_tracingjni" -> libreact_tracingjni_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_tracingjni_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
@@ -159,6 +159,7 @@ add_react_android_subdir(src/main/jni/react/hermes/instrumentation/)
159159
add_react_android_subdir(src/main/jni/react/runtime/cxxreactpackage)
160160
add_react_android_subdir(src/main/jni/react/runtime/jni)
161161
add_react_android_subdir(src/main/jni/react/runtime/hermes/jni)
162+
add_react_android_subdir(src/main/jni/react/tracing)
162163
add_react_android_subdir(src/main/jni/react/devsupport)
163164

164165
# SoMerging Utils
@@ -231,6 +232,7 @@ add_library(reactnative
231232
$<TARGET_OBJECTS:react_renderer_uimanager>
232233
$<TARGET_OBJECTS:react_renderer_uimanager_consistency>
233234
$<TARGET_OBJECTS:react_renderer_viewtransition>
235+
$<TARGET_OBJECTS:react_tracingjni>
234236
$<TARGET_OBJECTS:react_utils>
235237
$<TARGET_OBJECTS:reactnativeblob>
236238
$<TARGET_OBJECTS:reactnativejni>
@@ -331,6 +333,7 @@ target_include_directories(reactnative
331333
$<TARGET_PROPERTY:react_renderer_uimanager,INTERFACE_INCLUDE_DIRECTORIES>
332334
$<TARGET_PROPERTY:react_renderer_uimanager_consistency,INTERFACE_INCLUDE_DIRECTORIES>
333335
$<TARGET_PROPERTY:react_renderer_viewtransition,INTERFACE_INCLUDE_DIRECTORIES>
336+
$<TARGET_PROPERTY:react_tracingjni,INTERFACE_INCLUDE_DIRECTORIES>
334337
$<TARGET_PROPERTY:react_utils,INTERFACE_INCLUDE_DIRECTORIES>
335338
$<TARGET_PROPERTY:reactnativeblob,INTERFACE_INCLUDE_DIRECTORIES>
336339
$<TARGET_PROPERTY:reactnativejni,INTERFACE_INCLUDE_DIRECTORIES>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
cmake_minimum_required(VERSION 3.13)
7+
set(CMAKE_VERBOSE_MAKEFILE on)
8+
9+
include(${REACT_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake)
10+
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
11+
12+
file(GLOB react_tracingjni_SRC CONFIGURE_DEPENDS *.cpp)
13+
14+
add_library(react_tracingjni OBJECT ${react_tracingjni_SRC})
15+
16+
target_merge_so(react_tracingjni)
17+
18+
target_include_directories(react_tracingjni PUBLIC .)
19+
20+
target_link_libraries(react_tracingjni
21+
fbjni
22+
folly_runtime
23+
jsinspector
24+
jsinspector_tracing
25+
react_timing
26+
reactnativejni)
27+
28+
target_compile_reactnative_options(react_tracingjni PRIVATE)

packages/react-native/ReactCommon/React-Fabric.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ Pod::Spec.new do |s|
174174
end
175175

176176
s.subspec "mounting" do |ss|
177+
ss.dependency "React-jsinspectortracing"
177178
ss.source_files = podspec_sources("react/renderer/mounting/**/*.{m,mm,cpp,h}", "react/renderer/mounting/**/*.h")
178179
ss.exclude_files = "react/renderer/mounting/tests"
179180
ss.header_dir = "react/renderer/mounting"

packages/react-native/ReactCommon/react/renderer/mounting/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ target_link_libraries(react_renderer_mounting
2222
glog
2323
glog_init
2424
jsi
25+
jsinspector_tracing
2526
react_debug
2627
react_renderer_core
2728
react_renderer_debug

packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "ShadowTree.h"
99

1010
#include <cxxreact/TraceSection.h>
11+
#include <jsinspector-modern/tracing/PerformanceTracerSection.h>
1112
#include <react/debug/react_native_assert.h>
1213
#include <react/renderer/components/root/RootComponentDescriptor.h>
1314
#include <react/renderer/components/view/ViewShadowNode.h>
@@ -24,6 +25,19 @@ namespace facebook::react {
2425

2526
namespace {
2627
const int MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING = 3;
28+
29+
std::string getShadowTreeCommitSourceName(ShadowTreeCommitSource source) {
30+
switch (source) {
31+
case ShadowTreeCommitSource::Unknown:
32+
return "Unknown";
33+
case ShadowTreeCommitSource::React:
34+
return "React";
35+
case ShadowTreeCommitSource::AnimationEndSync:
36+
return "AnimationEndSync";
37+
case ShadowTreeCommitSource::ReactRevisionMerge:
38+
return "ReactRevisionMerge";
39+
}
40+
}
2741
} // namespace
2842

2943
using CommitStatus = ShadowTree::CommitStatus;
@@ -316,6 +330,13 @@ CommitStatus ShadowTree::tryCommit(
316330
const ShadowTreeCommitTransaction& transaction,
317331
const CommitOptions& commitOptions) const {
318332
TraceSection s("ShadowTree::commit");
333+
jsinspector_modern::tracing::PerformanceTracerSection s1(
334+
"commit",
335+
"Renderer",
336+
"\u269b Native",
337+
nullptr,
338+
"source",
339+
getShadowTreeCommitSourceName(commitOptions.source));
319340

320341
auto isReactBranch = ReactNativeFeatureFlags::enableFabricCommitBranching() &&
321342
commitOptions.source == CommitSource::React;
@@ -382,7 +403,11 @@ CommitStatus ShadowTree::tryCommit(
382403

383404
telemetry.willLayout();
384405
telemetry.setAsThreadLocal();
385-
newRootShadowNode->layoutIfNeeded(&affectedLayoutableNodes);
406+
{
407+
jsinspector_modern::tracing::PerformanceTracerSection s2(
408+
"layout", "Renderer", "\u269b Native");
409+
newRootShadowNode->layoutIfNeeded(&affectedLayoutableNodes);
410+
}
386411
telemetry.unsetAsThreadLocal();
387412
telemetry.didLayout(static_cast<int>(affectedLayoutableNodes.size()));
388413

0 commit comments

Comments
 (0)