Skip to content

Commit d2f9b2c

Browse files
huntiefacebook-github-bot
authored andcommitted
Implement local connection for perf metrics in HostTarget (#52838)
Summary: Pull Request resolved: #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
1 parent 664f7c0 commit d2f9b2c

8 files changed

Lines changed: 121 additions & 0 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ void JReactHostInspectorTarget::onSetPausedInDebuggerMessage(
127127
}
128128
}
129129

130+
void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate(
131+
const PerfMonitorUpdateRequest& /* unused */) {}
132+
130133
void JReactHostInspectorTarget::loadNetworkResource(
131134
const jsinspector_modern::LoadNetworkResourceRequest& params,
132135
jsinspector_modern::ScopedExecutor<

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class JReactHostInspectorTarget
8484
void onReload(const PageReloadRequest& request) override;
8585
void onSetPausedInDebuggerMessage(
8686
const OverlaySetPausedInDebuggerMessageRequest&) override;
87+
void unstable_onPerfMonitorUpdate(
88+
const PerfMonitorUpdateRequest& /* unused */) override;
8789
void loadNetworkResource(
8890
const jsinspector_modern::LoadNetworkResourceRequest& params,
8991
jsinspector_modern::ScopedExecutor<

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,53 @@ class HostCommandSender {
145145
std::unique_ptr<ILocalConnection> connection_;
146146
};
147147

148+
/**
149+
* Enables the caller to install and subscribe to a named CDP runtime binding
150+
* on the HostTarget via a callback. Note: Per CDP spec, this does not need to
151+
* check if the `Runtime` domain is enabled.
152+
*/
153+
class HostRuntimeBinding {
154+
public:
155+
explicit HostRuntimeBinding(
156+
HostTarget& target,
157+
std::string name,
158+
std::function<void(std::string)> callback)
159+
: connection_(target.connect(std::make_unique<CallbackRemoteConnection>(
160+
[callback = std::move(callback)](const std::string& message) {
161+
auto parsedMessage = folly::parseJson(message);
162+
163+
// Ignore initial Runtime.addBinding response
164+
if (parsedMessage["id"] == 0 &&
165+
parsedMessage["result"].isObject() &&
166+
parsedMessage["result"].empty()) {
167+
return;
168+
}
169+
170+
// Assert that we only intercept bindingCalled responses
171+
assert(
172+
parsedMessage["method"].asString() ==
173+
"Runtime.bindingCalled");
174+
callback(parsedMessage["params"]["payload"].asString());
175+
}))) {
176+
// Install runtime binding
177+
connection_->sendMessage(cdp::jsonRequest(
178+
0,
179+
"Runtime.addBinding",
180+
folly::dynamic::object("name", std::move(name))));
181+
}
182+
183+
private:
184+
std::unique_ptr<ILocalConnection> connection_;
185+
};
186+
148187
std::shared_ptr<HostTarget> HostTarget::create(
149188
HostTargetDelegate& delegate,
150189
VoidExecutor executor) {
151190
std::shared_ptr<HostTarget> hostTarget{new HostTarget(delegate)};
152191
hostTarget->setExecutor(std::move(executor));
192+
if (InspectorFlags::getInstance().getPerfMonitorV2Enabled()) {
193+
hostTarget->installPerfMetricsBinding();
194+
}
153195
return hostTarget;
154196
}
155197

@@ -212,6 +254,19 @@ void HostTarget::sendCommand(HostCommand command) {
212254
});
213255
}
214256

257+
void HostTarget::installPerfMetricsBinding() {
258+
perfMetricsBinding_ = std::make_unique<HostRuntimeBinding>(
259+
*this, // Used immediately
260+
"__chromium_devtools_metrics_reporter",
261+
[this](const std::string& message) {
262+
auto payload = folly::parseJson(message);
263+
HostTargetDelegate::PerfMonitorUpdateRequest request{
264+
.interactionName = payload["eventName"].asString(),
265+
.durationMs = static_cast<uint16_t>(payload["duration"].asInt())};
266+
delegate_.unstable_onPerfMonitorUpdate(request);
267+
});
268+
}
269+
215270
HostTargetController::HostTargetController(HostTarget& target)
216271
: target_(target) {}
217272

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ namespace facebook::react::jsinspector_modern {
3535
class HostTargetSession;
3636
class HostAgent;
3737
class HostCommandSender;
38+
class HostRuntimeBinding;
3839
class HostTarget;
3940

4041
struct HostTargetMetadata {
@@ -93,6 +94,11 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
9394
}
9495
};
9596

97+
struct PerfMonitorUpdateRequest {
98+
std::string interactionName;
99+
uint16_t durationMs;
100+
};
101+
96102
virtual ~HostTargetDelegate() override;
97103

98104
/**
@@ -121,6 +127,13 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
121127
virtual void onSetPausedInDebuggerMessage(
122128
const OverlaySetPausedInDebuggerMessageRequest& request) = 0;
123129

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_onPerfMonitorUpdate(
135+
const PerfMonitorUpdateRequest& /*request*/) {}
136+
124137
/**
125138
* Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP
126139
* request. Platform implementations should override this to perform a
@@ -256,6 +269,7 @@ class JSINSPECTOR_EXPORT HostTarget
256269
std::shared_ptr<ExecutionContextManager> executionContextManager_;
257270
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
258271
std::unique_ptr<HostCommandSender> commandSender_;
272+
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
259273

260274
inline HostTargetDelegate& getDelegate() {
261275
return delegate_;
@@ -265,6 +279,13 @@ class JSINSPECTOR_EXPORT HostTarget
265279
return currentInstance_ != nullptr;
266280
}
267281

282+
/**
283+
* Install a runtime binding subscribing to the Interaction to Next Paint
284+
* (INP) live metric, which we broadcast to the V2 Perf Monitor overlay
285+
* via \ref HostTargetDelegate::unstable_onPerfMonitorUpdate.
286+
*/
287+
void installPerfMetricsBinding();
288+
268289
// Necessary to allow HostAgent to access HostTarget's internals in a
269290
// controlled way (i.e. only HostTargetController gets friend access, while
270291
// HostAgent itself doesn't).

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

Lines changed: 7 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::getPerfMonitorV2Enabled() const {
37+
return loadFlagsAndAssertUnchanged().perfMonitorV2Enabled;
38+
}
39+
3640
void InspectorFlags::dangerouslyResetFlags() {
3741
*this = InspectorFlags{};
3842
}
@@ -59,6 +63,9 @@ const InspectorFlags::Values& InspectorFlags::loadFlagsAndAssertUnchanged()
5963
.networkInspectionEnabled =
6064
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
6165
ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(),
66+
.perfMonitorV2Enabled =
67+
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
68+
ReactNativeFeatureFlags::perfMonitorV2Enabled(),
6269
};
6370

6471
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 getPerfMonitorV2Enabled() 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 perfMonitorV2Enabled;
5561
bool operator==(const Values&) const = default;
5662
};
5763

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ void CallbackLocalConnection::disconnect() {
2424
handler_ = nullptr;
2525
}
2626

27+
CallbackRemoteConnection::CallbackRemoteConnection(
28+
std::function<void(std::string)> handler)
29+
: handler_(std::move(handler)) {}
30+
31+
void CallbackRemoteConnection::onMessage(std::string message) {
32+
handler_(std::move(message));
33+
}
34+
2735
RAIIRemoteConnection::RAIIRemoteConnection(
2836
std::unique_ptr<IRemoteConnection> remote)
2937
: remote_(std::move(remote)) {}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ class CallbackLocalConnection : public ILocalConnection {
3333
std::function<void(std::string)> handler_;
3434
};
3535

36+
/**
37+
* Wraps a callback function in IRemoteConnection.
38+
*/
39+
class CallbackRemoteConnection : public IRemoteConnection {
40+
public:
41+
/**
42+
* Creates a new Connection that uses the given callback to receive messages
43+
* from the backend.
44+
*/
45+
explicit CallbackRemoteConnection(std::function<void(std::string)> handler);
46+
47+
void onMessage(std::string message) override;
48+
49+
void onDisconnect() override {}
50+
51+
private:
52+
std::function<void(std::string)> handler_;
53+
};
54+
3655
/**
3756
* Wraps an IRemoteConnection in a simpler interface that calls `onDisconnect`
3857
* implicitly upon destruction.

0 commit comments

Comments
 (0)