Skip to content

Commit 9130df6

Browse files
rubennortemeta-codesync[bot]
authored andcommitted
Add performance track for React Native renderer (#55936)
Summary: Pull Request resolved: #55936 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: sammy-SC Differential Revision: D88863291 fbshipit-source-id: 6401d7e45b4649c848825c47f6a214e7c387d34e
1 parent 4f3f536 commit 9130df6

5 files changed

Lines changed: 130 additions & 66 deletions

File tree

packages/react-native/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ let reactFabric = RNTarget(
456456
"components/virtualview",
457457
"components/root/tests",
458458
],
459-
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga],
459+
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga, .reactJsInspectorTracing],
460460
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", "telemetry", "consistency", "leakchecker", "uimanager", "uimanager/consistency", "viewtransition"]
461461
)
462462

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+
"⚛ 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+
"⚛ 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+
"⚛ 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+
"⚛ 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/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+
"⚛ 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", "⚛ Native");
409+
newRootShadowNode->layoutIfNeeded(&affectedLayoutableNodes);
410+
}
386411
telemetry.unsetAsThreadLocal();
387412
telemetry.didLayout(static_cast<int>(affectedLayoutableNodes.size()));
388413

0 commit comments

Comments
 (0)