Implement local connection for perf metrics in HostTarget (#52838)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52838

**Context**

Experimental V2 Performance Monitor prototype, beginning by bringing the [Interaction to Next Paint (INP)](https://web.dev/articles/inp) metric to React Native.

**This diff**

Wires up a client/subscriber for the `"__chromium_devtools_metrics_reporter"` runtime binding (to which we emit live metrics events since D78904748). This will be used to unpack these performance updates to send to the host platform.

- Creates a new `HostRuntimeBinding` helper, which establishes a local/private CDP session.
- Conditionally installs our perf metrics runtime binding in `HostTarget` when `perfMonitorV2Enabled` is set.
- Wires up a new `onPerfMonitorUpdate` event on `HostTargetDelegate` (unimplemented until the next diff).

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D78904766

fbshipit-source-id: 991f17a4cc69f574917750053a5da31bbc6dc0d5
This commit is contained in:
Alex Hunt
2025-08-06 16:05:28 -07:00
committed by Facebook GitHub Bot
parent be6f3c6f77
commit 2768c84445
8 changed files with 121 additions and 0 deletions
@@ -127,6 +127,9 @@ void JReactHostInspectorTarget::onSetPausedInDebuggerMessage(
}
}
void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& /* unused */) {}
void JReactHostInspectorTarget::loadNetworkResource(
const jsinspector_modern::LoadNetworkResourceRequest& params,
jsinspector_modern::ScopedExecutor<
@@ -84,6 +84,8 @@ class JReactHostInspectorTarget
void onReload(const PageReloadRequest& request) override;
void onSetPausedInDebuggerMessage(
const OverlaySetPausedInDebuggerMessageRequest&) override;
void unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& /* unused */) override;
void loadNetworkResource(
const jsinspector_modern::LoadNetworkResourceRequest& params,
jsinspector_modern::ScopedExecutor<
@@ -146,11 +146,53 @@ class HostCommandSender {
std::unique_ptr<ILocalConnection> connection_;
};
/**
* Enables the caller to install and subscribe to a named CDP runtime binding
* on the HostTarget via a callback. Note: Per CDP spec, this does not need to
* check if the `Runtime` domain is enabled.
*/
class HostRuntimeBinding {
public:
explicit HostRuntimeBinding(
HostTarget& target,
std::string name,
std::function<void(std::string)> callback)
: connection_(target.connect(std::make_unique<CallbackRemoteConnection>(
[callback = std::move(callback)](const std::string& message) {
auto parsedMessage = folly::parseJson(message);
// Ignore initial Runtime.addBinding response
if (parsedMessage["id"] == 0 &&
parsedMessage["result"].isObject() &&
parsedMessage["result"].empty()) {
return;
}
// Assert that we only intercept bindingCalled responses
assert(
parsedMessage["method"].asString() ==
"Runtime.bindingCalled");
callback(parsedMessage["params"]["payload"].asString());
}))) {
// Install runtime binding
connection_->sendMessage(cdp::jsonRequest(
0,
"Runtime.addBinding",
folly::dynamic::object("name", std::move(name))));
}
private:
std::unique_ptr<ILocalConnection> connection_;
};
std::shared_ptr<HostTarget> HostTarget::create(
HostTargetDelegate& delegate,
VoidExecutor executor) {
std::shared_ptr<HostTarget> hostTarget{new HostTarget(delegate)};
hostTarget->setExecutor(std::move(executor));
if (InspectorFlags::getInstance().getPerfMonitorV2Enabled()) {
hostTarget->installPerfMetricsBinding();
}
return hostTarget;
}
@@ -229,6 +271,19 @@ void HostTarget::sendCommand(HostCommand command) {
});
}
void HostTarget::installPerfMetricsBinding() {
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);
});
}
HostTargetController::HostTargetController(HostTarget& target)
: target_(target) {}
@@ -38,6 +38,7 @@ class HostTargetSession;
class HostAgent;
class HostTracingAgent;
class HostCommandSender;
class HostRuntimeBinding;
class HostTarget;
class HostTargetTraceRecording;
@@ -97,6 +98,11 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
}
};
struct PerfMonitorUpdateRequest {
std::string interactionName;
uint16_t durationMs;
};
virtual ~HostTargetDelegate() override;
/**
@@ -125,6 +131,13 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
virtual void onSetPausedInDebuggerMessage(
const OverlaySetPausedInDebuggerMessageRequest& request) = 0;
/**
* [Experimental] Called when the runtime has new data for the V2 Perf
* Monitor overlay. This is called on the inspector thread.
*/
virtual void unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& /*request*/) {}
/**
* Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP
* request. Platform implementations should override this to perform a
@@ -296,6 +309,7 @@ class JSINSPECTOR_EXPORT HostTarget
std::shared_ptr<ExecutionContextManager> executionContextManager_;
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
std::unique_ptr<HostCommandSender> commandSender_;
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
/**
* Current pending trace recording, which encapsulates the configuration of
@@ -313,6 +327,13 @@ class JSINSPECTOR_EXPORT HostTarget
return currentInstance_ != nullptr;
}
/**
* Install a runtime binding subscribing to the Interaction to Next Paint
* (INP) live metric, which we broadcast to the V2 Perf Monitor overlay
* via \ref HostTargetDelegate::unstable_onPerfMonitorUpdate.
*/
void installPerfMetricsBinding();
// Necessary to allow HostAgent to access HostTarget's internals in a
// controlled way (i.e. only HostTargetController gets friend access, while
// HostAgent itself doesn't).
@@ -33,6 +33,10 @@ bool InspectorFlags::getNetworkInspectionEnabled() const {
return loadFlagsAndAssertUnchanged().networkInspectionEnabled;
}
bool InspectorFlags::getPerfMonitorV2Enabled() const {
return loadFlagsAndAssertUnchanged().perfMonitorV2Enabled;
}
void InspectorFlags::dangerouslyResetFlags() {
*this = InspectorFlags{};
}
@@ -59,6 +63,9 @@ const InspectorFlags::Values& InspectorFlags::loadFlagsAndAssertUnchanged()
.networkInspectionEnabled =
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(),
.perfMonitorV2Enabled =
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
ReactNativeFeatureFlags::perfMonitorV2Enabled(),
};
if (cachedValues_.has_value() && !inconsistentFlagsStateLogged_) {
@@ -35,6 +35,11 @@ class InspectorFlags {
*/
bool getNetworkInspectionEnabled() const;
/**
* Flag determining if the V2 in-app Performance Monitor is enabled.
*/
bool getPerfMonitorV2Enabled() const;
/**
* Forcibly disable the main `getFuseboxEnabled()` flag. This should ONLY be
* used by `ReactInstanceIntegrationTest`.
@@ -52,6 +57,7 @@ class InspectorFlags {
bool fuseboxEnabled;
bool isProfilingBuild;
bool networkInspectionEnabled;
bool perfMonitorV2Enabled;
bool operator==(const Values&) const = default;
};
@@ -24,6 +24,14 @@ void CallbackLocalConnection::disconnect() {
handler_ = nullptr;
}
CallbackRemoteConnection::CallbackRemoteConnection(
std::function<void(std::string)> handler)
: handler_(std::move(handler)) {}
void CallbackRemoteConnection::onMessage(std::string message) {
handler_(std::move(message));
}
RAIIRemoteConnection::RAIIRemoteConnection(
std::unique_ptr<IRemoteConnection> remote)
: remote_(std::move(remote)) {}
@@ -33,6 +33,25 @@ class CallbackLocalConnection : public ILocalConnection {
std::function<void(std::string)> handler_;
};
/**
* Wraps a callback function in IRemoteConnection.
*/
class CallbackRemoteConnection : public IRemoteConnection {
public:
/**
* Creates a new Connection that uses the given callback to receive messages
* from the backend.
*/
explicit CallbackRemoteConnection(std::function<void(std::string)> handler);
void onMessage(std::string message) override;
void onDisconnect() override {}
private:
std::function<void(std::string)> handler_;
};
/**
* Wraps an IRemoteConnection in a simpler interface that calls `onDisconnect`
* implicitly upon destruction.