mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Implement Perf Monitor event scoring and display timeout (#53169)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53169 Improves the experimental Perf Monitor UI sufficient for the initial MVP. - Sets minimum duration threshold to display an event to 10ms. - Impelements [responsiveness scoring](https://web.dev/articles/inp#good-score) linked to UI colour and display timeout. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D79359131 fbshipit-source-id: f08d2b595e885342d841c3a02b9e02456502c926
This commit is contained in:
committed by
Facebook GitHub Bot
parent
defefb19e3
commit
df748ba083
@@ -2031,7 +2031,7 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/
|
||||
public fun startInspector ()V
|
||||
public fun stopInspector ()V
|
||||
public fun toggleElementInspector ()V
|
||||
public fun unstable_updatePerfMonitor (Ljava/lang/String;I)V
|
||||
public fun unstable_updatePerfMonitor (Ljava/lang/String;III)V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/devsupport/DevSupportManagerBase$CallbackWithBundleLoader {
|
||||
|
||||
+6
-2
@@ -927,10 +927,14 @@ public abstract class DevSupportManagerBase(
|
||||
}
|
||||
|
||||
override fun unstable_updatePerfMonitor(
|
||||
interactionName: String,
|
||||
eventName: String,
|
||||
durationMs: Int,
|
||||
responsivenessScore: Int,
|
||||
ttl: Int,
|
||||
) {
|
||||
perfMonitorOverlayManager?.update(interactionName, durationMs)
|
||||
perfMonitorOverlayManager?.update(
|
||||
PerfMonitorOverlayManager.PerfMonitorUpdateData(
|
||||
eventName, durationMs, responsivenessScore, ttl))
|
||||
}
|
||||
|
||||
override fun setAdditionalOptionForPackager(name: String, value: String) {
|
||||
|
||||
+30
-3
@@ -13,6 +13,8 @@ import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.Gravity
|
||||
import android.view.Window
|
||||
import android.view.WindowManager
|
||||
@@ -39,6 +41,8 @@ internal class PerfMonitorOverlayViewManager(
|
||||
private var buttonDialog: Dialog? = null
|
||||
private var interactionNameLabel: TextView? = null
|
||||
private var durationLabel: TextView? = null
|
||||
private var ttl: Int = 0
|
||||
private var hideAfterTimeoutHandler: Handler? = null
|
||||
|
||||
override fun enable() {
|
||||
UiThreadUtil.runOnUiThread {
|
||||
@@ -63,14 +67,27 @@ internal class PerfMonitorOverlayViewManager(
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(interactionName: String, durationMs: Int) {
|
||||
override fun update(data: PerfMonitorOverlayManager.PerfMonitorUpdateData) {
|
||||
UiThreadUtil.runOnUiThread {
|
||||
ensureInitialized()
|
||||
interactionNameLabel?.text = interactionName
|
||||
durationLabel?.text = String.format(Locale.US, "%d ms", durationMs)
|
||||
interactionNameLabel?.text = data.eventName
|
||||
durationLabel?.text = String.format(Locale.US, "%d ms", data.durationMs)
|
||||
durationLabel?.setTextColor(getDurationHighlightColor(data.responsivenessScore))
|
||||
hasInteractionData = true
|
||||
this.ttl = data.ttl
|
||||
|
||||
hideAfterTimeoutHandler?.removeCallbacksAndMessages(null)
|
||||
|
||||
if (enabled) {
|
||||
showOverlay()
|
||||
|
||||
// Schedule hiding overlay after ttl milliseconds
|
||||
if (ttl > 0) {
|
||||
if (hideAfterTimeoutHandler == null) {
|
||||
hideAfterTimeoutHandler = Handler(Looper.getMainLooper())
|
||||
}
|
||||
hideAfterTimeoutHandler?.postDelayed({ hideOverlay() }, ttl.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,10 +245,20 @@ internal class PerfMonitorOverlayViewManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDurationHighlightColor(responsivenessScore: Int): Int {
|
||||
return when (responsivenessScore) {
|
||||
3 -> COLOR_TEXT_RED
|
||||
2 -> COLOR_TEXT_YELLOW
|
||||
else -> COLOR_TEXT_GREEN
|
||||
}
|
||||
}
|
||||
|
||||
private fun dpToPx(dp: Float): Float = PixelUtil.toPixelFromDIP(dp)
|
||||
|
||||
companion object {
|
||||
private val COLOR_TEXT_GREEN = Color.parseColor("#4AEB2F")
|
||||
private val COLOR_TEXT_YELLOW = Color.parseColor("#FFAA00")
|
||||
private val COLOR_TEXT_RED = Color.parseColor("#FF0000")
|
||||
private val COLOR_OVERLAY_BORDER = Color.parseColor("#6C6C6C")
|
||||
private val TEXT_SIZE_PRIMARY = 13f
|
||||
private val TEXT_SIZE_ACCESSORY = 9f
|
||||
|
||||
+8
-4
@@ -9,6 +9,13 @@ package com.facebook.react.devsupport.interfaces
|
||||
|
||||
/** [Experimental] Interface to manage the V2 Perf Monitor overlay. */
|
||||
internal interface PerfMonitorOverlayManager {
|
||||
data class PerfMonitorUpdateData(
|
||||
val eventName: String,
|
||||
val durationMs: Int,
|
||||
val responsivenessScore: Int,
|
||||
val ttl: Int
|
||||
)
|
||||
|
||||
/** Enable the Perf Monitor overlay. Will be shown when updates are received. */
|
||||
public fun enable()
|
||||
|
||||
@@ -19,8 +26,5 @@ internal interface PerfMonitorOverlayManager {
|
||||
public fun reset()
|
||||
|
||||
/** Update the state of the Perf Monitor overlay. */
|
||||
public fun update(
|
||||
interactionName: String,
|
||||
durationMs: Int,
|
||||
)
|
||||
public fun update(data: PerfMonitorUpdateData)
|
||||
}
|
||||
|
||||
+3
-1
@@ -16,7 +16,9 @@ internal interface PerfMonitorV2Handler {
|
||||
/** [Experimental] Update the V2 Perf Monitor overlay with the given data. */
|
||||
// FIXME(T233950466): Refactor ReactHostImpl/DevSupport setup to avoid this public API addition
|
||||
public fun unstable_updatePerfMonitor(
|
||||
interactionName: String,
|
||||
eventName: String,
|
||||
durationMs: Int,
|
||||
responsivenessScore: Int,
|
||||
ttl: Int,
|
||||
)
|
||||
}
|
||||
|
||||
+7
-2
@@ -420,9 +420,14 @@ public class ReactHostImpl(
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
private fun unstable_updatePerfMonitor(interactionName: String, durationMs: Int) {
|
||||
private fun unstable_updatePerfMonitor(
|
||||
eventName: String,
|
||||
durationMs: Int,
|
||||
responsivenessScore: Int,
|
||||
ttl: Int
|
||||
) {
|
||||
if (devSupportManager is PerfMonitorV2Handler) {
|
||||
devSupportManager.unstable_updatePerfMonitor(interactionName, durationMs)
|
||||
devSupportManager.unstable_updatePerfMonitor(eventName, durationMs, responsivenessScore, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -131,7 +131,10 @@ void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate(
|
||||
const PerfMonitorUpdateRequest& request) {
|
||||
if (auto javaReactHostImplStrong = javaReactHostImpl_->get()) {
|
||||
javaReactHostImplStrong->unstable_updatePerfMonitor(
|
||||
request.interactionName, request.durationMs);
|
||||
request.activeInteraction.eventName,
|
||||
request.activeInteraction.duration,
|
||||
request.activeInteraction.responsivenessScore,
|
||||
request.activeInteraction.ttl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-5
@@ -40,11 +40,19 @@ struct JReactHostImpl : public jni::JavaClass<JReactHostImpl> {
|
||||
|
||||
void unstable_updatePerfMonitor(
|
||||
const std::string& interactionName,
|
||||
uint16_t durationMs) {
|
||||
uint16_t durationMs,
|
||||
jsinspector_modern::InteractionResponsivenessScore responsivenessScore,
|
||||
uint16_t ttl) {
|
||||
static auto method =
|
||||
javaClassStatic()->getMethod<void(jni::local_ref<jni::JString>, jint)>(
|
||||
"unstable_updatePerfMonitor");
|
||||
method(self(), jni::make_jstring(interactionName), durationMs);
|
||||
javaClassStatic()
|
||||
->getMethod<void(jni::local_ref<jni::JString>, jint, jint, jint)>(
|
||||
"unstable_updatePerfMonitor");
|
||||
method(
|
||||
self(),
|
||||
jni::make_jstring(interactionName),
|
||||
durationMs,
|
||||
static_cast<jint>(responsivenessScore),
|
||||
ttl);
|
||||
}
|
||||
|
||||
jni::local_ref<jni::JMap<jstring, jstring>> getHostMetadata() const {
|
||||
@@ -94,7 +102,7 @@ class JReactHostInspectorTarget
|
||||
void onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest& request) override;
|
||||
void unstable_onPerfMonitorUpdate(
|
||||
const PerfMonitorUpdateRequest& request) override;
|
||||
const jsinspector_modern::PerfMonitorUpdateRequest& request) override;
|
||||
void loadNetworkResource(
|
||||
const jsinspector_modern::LoadNetworkResourceRequest& params,
|
||||
jsinspector_modern::ScopedExecutor<
|
||||
|
||||
@@ -272,15 +272,13 @@ void HostTarget::sendCommand(HostCommand command) {
|
||||
}
|
||||
|
||||
void HostTarget::installPerfMetricsBinding() {
|
||||
perfMonitorUpdateHandler_ =
|
||||
std::make_unique<PerfMonitorUpdateHandler>(delegate_);
|
||||
perfMetricsBinding_ = std::make_unique<HostRuntimeBinding>(
|
||||
*this, // Used immediately
|
||||
"__chromium_devtools_metrics_reporter",
|
||||
[this](const std::string& message) {
|
||||
auto payload = folly::parseJson(message);
|
||||
HostTargetDelegate::PerfMonitorUpdateRequest request{
|
||||
.interactionName = payload["eventName"].asString(),
|
||||
.durationMs = static_cast<uint16_t>(payload["duration"].asInt())};
|
||||
delegate_.unstable_onPerfMonitorUpdate(request);
|
||||
perfMonitorUpdateHandler_->handlePerfMetricsUpdate(message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "InspectorInterfaces.h"
|
||||
#include "InstanceTarget.h"
|
||||
#include "NetworkIOAgent.h"
|
||||
#include "PerfMonitorV2.h"
|
||||
#include "ScopedExecutor.h"
|
||||
#include "WeakList.h"
|
||||
|
||||
@@ -98,11 +99,6 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
|
||||
}
|
||||
};
|
||||
|
||||
struct PerfMonitorUpdateRequest {
|
||||
std::string interactionName;
|
||||
uint16_t durationMs;
|
||||
};
|
||||
|
||||
virtual ~HostTargetDelegate() override;
|
||||
|
||||
/**
|
||||
@@ -309,6 +305,7 @@ class JSINSPECTOR_EXPORT HostTarget
|
||||
std::shared_ptr<ExecutionContextManager> executionContextManager_;
|
||||
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
|
||||
std::unique_ptr<HostCommandSender> commandSender_;
|
||||
std::unique_ptr<PerfMonitorUpdateHandler> perfMonitorUpdateHandler_;
|
||||
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "PerfMonitorV2.h"
|
||||
#include "HostTarget.h"
|
||||
|
||||
#include <folly/json.h>
|
||||
#include <react/timing/primitives.h>
|
||||
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t MIN_DURATION = 10;
|
||||
constexpr uint16_t DEFAULT_TTL = 4000;
|
||||
constexpr uint16_t BAD_EVENT_TTL = 20000;
|
||||
|
||||
InteractionResponsivenessScore getInteractionScore(uint16_t duration) {
|
||||
constexpr uint16_t GOOD_THRESHOLD = 200;
|
||||
constexpr uint16_t NEEDS_IMPROVEMENT_THRESHOLD = 500;
|
||||
|
||||
if (duration < GOOD_THRESHOLD) {
|
||||
return InteractionResponsivenessScore::Good;
|
||||
} else if (duration < NEEDS_IMPROVEMENT_THRESHOLD) {
|
||||
return InteractionResponsivenessScore::NeedsImprovement;
|
||||
} else {
|
||||
return InteractionResponsivenessScore::Poor;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void PerfMonitorUpdateHandler::handlePerfMetricsUpdate(
|
||||
const std::string& message) {
|
||||
auto payload = folly::parseJson(message);
|
||||
|
||||
if (payload.isObject()) {
|
||||
auto duration = static_cast<uint16_t>(payload["duration"].asInt());
|
||||
|
||||
if (duration < MIN_DURATION) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto responsivenessScore = getInteractionScore(duration);
|
||||
auto ttl = responsivenessScore == InteractionResponsivenessScore::Poor
|
||||
? BAD_EVENT_TTL
|
||||
: DEFAULT_TTL;
|
||||
|
||||
InteractionPayload newInteraction{
|
||||
payload["eventName"].asString(),
|
||||
static_cast<uint16_t>(payload["startTime"].asInt()),
|
||||
duration,
|
||||
responsivenessScore,
|
||||
ttl};
|
||||
|
||||
if (shouldOverrideLastInteraction(newInteraction)) {
|
||||
lastInteraction_ = newInteraction;
|
||||
delegate_.unstable_onPerfMonitorUpdate(
|
||||
PerfMonitorUpdateRequest{newInteraction});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PerfMonitorUpdateHandler::shouldOverrideLastInteraction(
|
||||
const InteractionPayload& newInteraction) {
|
||||
if (!lastInteraction_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Override if last event has expired
|
||||
if (HighResTimeStamp::now().toDOMHighResTimeStamp() >
|
||||
lastInteraction_->startTime + lastInteraction_->ttl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Override if same or greater responsiveness score
|
||||
if (newInteraction.responsivenessScore >=
|
||||
lastInteraction_->responsivenessScore) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
class HostTargetDelegate;
|
||||
|
||||
/**
|
||||
* See https://web.dev/articles/inp#good-score.
|
||||
*/
|
||||
enum class InteractionResponsivenessScore : int32_t {
|
||||
Good = 0,
|
||||
NeedsImprovement = 1,
|
||||
Poor = 2
|
||||
};
|
||||
|
||||
struct InteractionPayload {
|
||||
std::string eventName;
|
||||
uint16_t startTime;
|
||||
uint16_t duration;
|
||||
InteractionResponsivenessScore responsivenessScore;
|
||||
uint16_t ttl;
|
||||
};
|
||||
|
||||
struct PerfMonitorUpdateRequest {
|
||||
InteractionPayload activeInteraction;
|
||||
};
|
||||
|
||||
/**
|
||||
* [Experimental] Utility to handle performance metrics updates received from
|
||||
* the runtime and forward update events to the V2 Perf Monitor UI.
|
||||
*/
|
||||
class PerfMonitorUpdateHandler {
|
||||
public:
|
||||
PerfMonitorUpdateHandler(HostTargetDelegate& delegate)
|
||||
: delegate_(delegate) {}
|
||||
|
||||
/**
|
||||
* Handle a new "__chromium_devtools_metrics_reporter" message.
|
||||
*/
|
||||
void handlePerfMetricsUpdate(const std::string& message);
|
||||
|
||||
private:
|
||||
HostTargetDelegate& delegate_;
|
||||
std::optional<InteractionPayload> lastInteraction_;
|
||||
|
||||
bool shouldOverrideLastInteraction(const InteractionPayload& newInteraction);
|
||||
};
|
||||
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
Reference in New Issue
Block a user