Skip to content

Commit 29bc8f1

Browse files
rubennortemeta-codesync[bot]
authored andcommitted
Add performance track for React Native renderer (facebook#56003)
Summary: Pull Request resolved: facebook#56003 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). This is a reland of a previously reverted change. Reviewed By: huntie Differential Revision: D95817614 fbshipit-source-id: 4d6be2732c6412479997a79741ec4043e20971b4
1 parent f71ec1a commit 29bc8f1

File tree

10 files changed

+167
-67
lines changed

10 files changed

+167
-67
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: 101 additions & 64 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,44 +246,56 @@ internal class MountItemDispatcher(
229246
Systrace.TRACE_TAG_REACT,
230247
"MountItemDispatcher::mountViews mountItems to execute",
231248
)
232-
val batchedExecutionStartTime = SystemClock.uptimeMillis()
233-
234-
for (mountItem in items) {
235-
if (ReactNativeFeatureFlags.enableFabricLogs()) {
236-
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
237-
}
238-
239-
val command = mountItem as? DispatchCommandMountItem
240-
if (command != null) {
241-
dispatchViewCommand(command)
242-
continue
243-
}
244249

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-
for (m in items) {
251-
if (m === mountItem) {
252-
// We want to mark the mount item that caused exception
253-
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!")
250+
PerformanceTracer.trace(
251+
"mount",
252+
"Renderer",
253+
"\u269b Native",
254+
{ ->
255+
val batchedExecutionStartTime = SystemClock.uptimeMillis()
256+
257+
for (mountItem in items) {
258+
if (ReactNativeFeatureFlags.enableFabricLogs()) {
259+
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
260+
}
261+
262+
val command = mountItem as? DispatchCommandMountItem
263+
if (command != null) {
264+
dispatchViewCommand(command)
265+
continue
266+
}
267+
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+
for (m in items) {
274+
if (m === mountItem) {
275+
// We want to mark the mount item that caused exception
276+
FLog.e(
277+
TAG,
278+
"dispatchMountItems: mountItem: next mountItem triggered exception!",
279+
)
280+
}
281+
printMountItem(m, "dispatchMountItems: mountItem")
282+
}
283+
284+
if (mountItem.getSurfaceId() != View.NO_ID) {
285+
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
286+
}
287+
288+
if (ReactIgnorableMountingException.isIgnorable(e)) {
289+
ReactSoftExceptionLogger.logSoftException(TAG, e)
290+
} else {
291+
throw e
292+
}
293+
}
254294
}
255-
printMountItem(m, "dispatchMountItems: mountItem")
256-
}
257-
258-
if (mountItem.getSurfaceId() != View.NO_ID) {
259-
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
260-
}
295+
batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
296+
},
297+
)
261298

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

@@ -297,26 +326,34 @@ internal class MountItemDispatcher(
297326
private fun dispatchPreMountItemsImpl(deadline: Long) {
298327
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews")
299328

300-
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
301-
// reentering during dispatchPreMountItems
302-
inDispatch = true
303-
304-
try {
305-
while (true) {
306-
if (System.nanoTime() > deadline) {
307-
break
308-
}
309-
310-
// If list is empty, `poll` will return null, or var will never be set
311-
val preMountItemToDispatch = preMountItems.poll() ?: break
312-
if (ReactNativeFeatureFlags.enableFabricLogs()) {
313-
printMountItem(preMountItemToDispatch, "dispatchPreMountItems")
314-
}
315-
executeOrEnqueue(preMountItemToDispatch)
316-
}
317-
} finally {
318-
inDispatch = false
319-
}
329+
PerformanceTracer.trace(
330+
"premount",
331+
"Renderer",
332+
"\u269b Native",
333+
{ ->
334+
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems
335+
// from
336+
// reentering during dispatchPreMountItems
337+
inDispatch = true
338+
339+
try {
340+
while (true) {
341+
if (System.nanoTime() > deadline) {
342+
break
343+
}
344+
345+
// If list is empty, `poll` will return null, or var will never be set
346+
val preMountItemToDispatch = preMountItems.poll() ?: break
347+
if (ReactNativeFeatureFlags.enableFabricLogs()) {
348+
printMountItem(preMountItemToDispatch, "dispatchPreMountItems")
349+
}
350+
executeOrEnqueue(preMountItemToDispatch)
351+
}
352+
} finally {
353+
inDispatch = false
354+
}
355+
},
356+
)
320357

321358
Systrace.endSection(Systrace.TRACE_TAG_REACT)
322359
}

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
@@ -173,6 +173,7 @@ Pod::Spec.new do |s|
173173
end
174174

175175
s.subspec "mounting" do |ss|
176+
ss.dependency "React-jsinspectortracing"
176177
ss.source_files = podspec_sources("react/renderer/mounting/**/*.{m,mm,cpp,h}", "react/renderer/mounting/**/*.h")
177178
ss.exclude_files = "react/renderer/mounting/tests"
178179
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)