Skip to content

Commit 3dee76d

Browse files
huntiemeta-codesync[bot]
authored andcommitted
Add Performance Issues to Perf Monitor (2/2) (#54266)
Summary: Pull Request resolved: #54266 Introduces the concept of **Performance Issues**, an experimental performance signals concept for React Native. **Design** Performance Issues are an **experimental** user space API via the User Timings `detail` object. ``` performance.measure({ start, end, detail: { devtools: { ... }, rnPerfIssue: { name: 'React: Cascading Update', severity: 'warning', // 'info' | 'warning' | 'error', description: 'A cascading update is a update that is triggered by a previous update. This can lead to performance issues and should be avoided.', learnMoreUrl: 'https://react.dev/reference/dev-tools/react-performance-tracks#cascading-updates', } } }); ``` When `rnPerfIssue` is present, we eagerly report an the event over CDP, regardless of an active performance trace, via the `"__react_native_perf_issues_reporter"` runtime binding. **This diff** - Updates the V2 Perf Monitor UI (Android) to display Performance Issues as a count. - (UI parts of this diff, including `HostTarget::installPerfIssuesBinding` are reinstated from the previous INP design, removed in D82208400). Changelog: [Internal] Differential Revision: D85448199
1 parent 722df12 commit 3dee76d

File tree

12 files changed

+178
-7
lines changed

12 files changed

+178
-7
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ internal class PerfMonitorOverlayManager(
2222

2323
private var view: PerfMonitorOverlayView? = null
2424
private var tracingState: TracingState = TracingState.ENABLEDINCDPMODE
25+
private var perfIssueCount: Int = 0
2526

2627
/** Enable the Perf Monitor overlay. */
2728
fun enable() {
@@ -72,8 +73,19 @@ internal class PerfMonitorOverlayManager(
7273

7374
override fun onRecordingStateChanged(state: TracingState) {
7475
tracingState = state
76+
if (state != TracingState.DISABLED) {
77+
perfIssueCount = 0
78+
}
7579
UiThreadUtil.runOnUiThread {
7680
view?.updateRecordingState(state)
81+
view?.updatePerfIssueCount(perfIssueCount)
82+
view?.show()
83+
}
84+
}
85+
86+
override fun onPerfIssueAdded(name: String) {
87+
UiThreadUtil.runOnUiThread {
88+
view?.updatePerfIssueCount(++perfIssueCount)
7789
view?.show()
7890
}
7991
}

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

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ internal class PerfMonitorOverlayView(
3030
private val onButtonPress: () -> Unit,
3131
) {
3232
private val dialog: Dialog
33+
private lateinit var statusIndicator: TextView
3334
private lateinit var statusLabel: TextView
3435
private lateinit var tooltipLabel: TextView
35-
private lateinit var statusIndicator: TextView
36+
private lateinit var issuesContainer: LinearLayout
37+
private lateinit var issueCountLabel: TextView
3638

3739
init {
3840
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
@@ -70,6 +72,11 @@ internal class PerfMonitorOverlayView(
7072
dialog.show()
7173
}
7274

75+
fun updatePerfIssueCount(count: Int) {
76+
issueCountLabel.text = count.toString()
77+
issuesContainer.visibility = if (count == 0) LinearLayout.GONE else LinearLayout.VISIBLE
78+
}
79+
7380
private fun createToolbarDialog(): Dialog {
7481
statusIndicator =
7582
TextView(context).apply {
@@ -85,11 +92,7 @@ internal class PerfMonitorOverlayView(
8592
val textContainer =
8693
LinearLayout(context).apply {
8794
orientation = LinearLayout.VERTICAL
88-
layoutParams =
89-
LinearLayout.LayoutParams(
90-
LinearLayout.LayoutParams.WRAP_CONTENT,
91-
LinearLayout.LayoutParams.WRAP_CONTENT,
92-
)
95+
setPadding(dpToPx(2f).toInt(), 0, 0, 0)
9396
}
9497
statusLabel =
9598
TextView(context).apply {
@@ -106,10 +109,35 @@ internal class PerfMonitorOverlayView(
106109
textContainer.addView(statusLabel)
107110
textContainer.addView(tooltipLabel)
108111

112+
issuesContainer =
113+
LinearLayout(context).apply {
114+
setPadding(dpToPx(8f).toInt(), 0, 0, 0)
115+
visibility = LinearLayout.GONE
116+
}
117+
issueCountLabel =
118+
TextView(context).apply {
119+
textSize = TEXT_SIZE_PRIMARY
120+
setTextColor(Color.WHITE)
121+
typeface = TYPEFACE_BOLD
122+
val alertDrawable =
123+
context.getDrawable(android.R.drawable.ic_dialog_alert)?.apply {
124+
setBounds(
125+
0,
126+
1,
127+
dpToPx(TEXT_SIZE_PRIMARY).toInt(),
128+
dpToPx(TEXT_SIZE_PRIMARY).toInt() + 1,
129+
)
130+
}
131+
setCompoundDrawables(alertDrawable, null, null, null)
132+
compoundDrawablePadding = dpToPx(6f).toInt()
133+
}
134+
issuesContainer.addView(issueCountLabel)
135+
109136
val containerLayout = createInnerLayout()
110137
containerLayout.setOnClickListener { onButtonPress() }
111138
containerLayout.addView(statusIndicator)
112139
containerLayout.addView(textContainer)
140+
containerLayout.addView(issuesContainer)
113141

114142
val dialog =
115143
createAnchoredDialog(dpToPx(12f), dpToPx(12f)).apply { setContentView(containerLayout) }
@@ -175,7 +203,7 @@ internal class PerfMonitorOverlayView(
175203
showDividers = LinearLayout.SHOW_DIVIDER_MIDDLE
176204
dividerDrawable =
177205
object : ColorDrawable(Color.TRANSPARENT) {
178-
override fun getIntrinsicWidth(): Int = dpToPx(8f).toInt()
206+
override fun getIntrinsicWidth(): Int = dpToPx(10f).toInt()
179207
}
180208
}
181209
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ import com.facebook.react.devsupport.interfaces.TracingState
1212
internal interface PerfMonitorUpdateListener {
1313
/** Called when the recording state of the background performance trace has changed. */
1414
fun onRecordingStateChanged(state: TracingState)
15+
16+
/** Called when a new Performance Issue is added. */
17+
fun onPerfIssueAdded(name: String)
1518
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostInspectorTarget.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ internal class ReactHostInspectorTarget(reactHostImpl: ReactHostImpl) :
7474
}
7575
}
7676

77+
fun handleNativePerfIssueAdded(
78+
name: String,
79+
) {
80+
perfMonitorListeners.forEach { listener -> listener.onPerfIssueAdded(name) }
81+
}
82+
7783
override fun close() {
7884
mHybridData.resetNative()
7985
}

packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ void JReactHostInspectorTarget::onSetPausedInDebuggerMessage(
120120
}
121121
}
122122

123+
void JReactHostInspectorTarget::unstable_onPerfIssueAdded(
124+
const PerfIssuePayload& issue) {
125+
static auto method = javaClassStatic()->getMethod<void(local_ref<jstring>)>(
126+
"handleNativePerfIssueAdded");
127+
method(jobj_, make_jstring(issue.name));
128+
}
129+
123130
void JReactHostInspectorTarget::loadNetworkResource(
124131
const jsinspector_modern::LoadNetworkResourceRequest& params,
125132
jsinspector_modern::ScopedExecutor<

packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/JReactHostInspectorTarget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class JReactHostInspectorTarget : public jni::HybridClass<JReactHostInspectorTar
103103
jsinspector_modern::HostTargetMetadata getMetadata() override;
104104
void onReload(const PageReloadRequest &request) override;
105105
void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest &request) override;
106+
void unstable_onPerfIssueAdded(const jsinspector_modern::PerfIssuePayload &issue) override;
106107
void loadNetworkResource(
107108
const jsinspector_modern::LoadNetworkResourceRequest &params,
108109
jsinspector_modern::ScopedExecutor<jsinspector_modern::NetworkRequestListener> executor) override;

packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,9 @@ std::shared_ptr<HostTarget> HostTarget::create(
206206
VoidExecutor executor) {
207207
std::shared_ptr<HostTarget> hostTarget{new HostTarget(delegate)};
208208
hostTarget->setExecutor(std::move(executor));
209+
if (InspectorFlags::getInstance().getPerfIssuesEnabled()) {
210+
hostTarget->installPerfIssuesBinding();
211+
}
209212
return hostTarget;
210213
}
211214

@@ -289,6 +292,17 @@ void HostTarget::sendCommand(HostCommand command) {
289292
});
290293
}
291294

295+
void HostTarget::installPerfIssuesBinding() {
296+
perfMonitorUpdateHandler_ =
297+
std::make_unique<PerfMonitorUpdateHandler>(delegate_);
298+
perfMetricsBinding_ = std::make_unique<HostRuntimeBinding>(
299+
*this, // Used immediately
300+
"__react_native_perf_issues_reporter",
301+
[this](const std::string& message) {
302+
perfMonitorUpdateHandler_->handlePerfIssueAdded(message);
303+
});
304+
}
305+
292306
HostTargetController::HostTargetController(HostTarget& target)
293307
: target_(target) {}
294308

packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "InspectorInterfaces.h"
1313
#include "InstanceTarget.h"
1414
#include "NetworkIOAgent.h"
15+
#include "PerfMonitorV2.h"
1516
#include "ScopedExecutor.h"
1617
#include "WeakList.h"
1718

@@ -126,6 +127,12 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
126127
*/
127128
virtual void onSetPausedInDebuggerMessage(const OverlaySetPausedInDebuggerMessageRequest &request) = 0;
128129

130+
/**
131+
* [Experimental] Called when the runtime has new data for the V2 Perf
132+
* Monitor overlay. This is called on the inspector thread.
133+
*/
134+
virtual void unstable_onPerfIssueAdded(const PerfIssuePayload & /*issue*/) {}
135+
129136
/**
130137
* Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP
131138
* request. Platform implementations should override this to perform a
@@ -166,6 +173,13 @@ class HostTargetController final {
166173

167174
bool hasInstance() const;
168175

176+
/**
177+
* [Experimental] Install a runtime binding subscribing to new Performance
178+
* Issues, which we broadcast to the V2 Perf Monitor overlay via
179+
* \ref HostTargetDelegate::unstable_onPerfIssueAdded.
180+
*/
181+
void installPerfIssuesBinding();
182+
169183
/**
170184
* Increments the target's pause overlay counter. The counter represents the
171185
* exact number of Agents that have (concurrently) requested the pause
@@ -325,6 +339,7 @@ class JSINSPECTOR_EXPORT HostTarget : public EnableExecutorFromThis<HostTarget>
325339
std::shared_ptr<ExecutionContextManager> executionContextManager_;
326340
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
327341
std::unique_ptr<HostCommandSender> commandSender_;
342+
std::unique_ptr<PerfMonitorUpdateHandler> perfMonitorUpdateHandler_;
328343
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
329344

330345
/**
@@ -345,6 +360,13 @@ class JSINSPECTOR_EXPORT HostTarget : public EnableExecutorFromThis<HostTarget>
345360
return currentInstance_ != nullptr;
346361
}
347362

363+
/**
364+
* [Experimental] Install a runtime binding subscribing to new Peformance
365+
* Issues, which we broadcast to the V2 Perf Monitor overlay via
366+
* \ref HostTargetDelegate::unstable_onPerfMonitorUpdate.
367+
*/
368+
void installPerfIssuesBinding();
369+
348370
// Necessary to allow HostAgent to access HostTarget's internals in a
349371
// controlled way (i.e. only HostTargetController gets friend access, while
350372
// HostAgent itself doesn't).

packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ bool InspectorFlags::getNetworkInspectionEnabled() const {
3333
return loadFlagsAndAssertUnchanged().networkInspectionEnabled;
3434
}
3535

36+
bool InspectorFlags::getPerfIssuesEnabled() const {
37+
return loadFlagsAndAssertUnchanged().perfIssuesEnabled;
38+
}
39+
3640
void InspectorFlags::dangerouslyResetFlags() {
3741
*this = InspectorFlags{};
3842
}
@@ -59,6 +63,7 @@ const InspectorFlags::Values& InspectorFlags::loadFlagsAndAssertUnchanged()
5963
.networkInspectionEnabled =
6064
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
6165
ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(),
66+
.perfIssuesEnabled = ReactNativeFeatureFlags::perfIssuesEnabled(),
6267
};
6368

6469
if (cachedValues_.has_value() && !inconsistentFlagsStateLogged_) {

packages/react-native/ReactCommon/jsinspector-modern/InspectorFlags.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class InspectorFlags {
3535
*/
3636
bool getNetworkInspectionEnabled() const;
3737

38+
/**
39+
* Flag determining if the V2 in-app Performance Monitor is enabled.
40+
*/
41+
bool getPerfIssuesEnabled() const;
42+
3843
/**
3944
* Forcibly disable the main `getFuseboxEnabled()` flag. This should ONLY be
4045
* used by `ReactInstanceIntegrationTest`.
@@ -52,6 +57,7 @@ class InspectorFlags {
5257
bool fuseboxEnabled;
5358
bool isProfilingBuild;
5459
bool networkInspectionEnabled;
60+
bool perfIssuesEnabled;
5561
bool operator==(const Values &) const = default;
5662
};
5763

0 commit comments

Comments
 (0)