Skip to content

Commit 2768c84

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 fbshipit-source-id: 991f17a4cc69f574917750053a5da31bbc6dc0d5
1 parent be6f3c6 commit 2768c84

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
@@ -146,11 +146,53 @@ class HostCommandSender {
146146
std::unique_ptr<ILocalConnection> connection_;
147147
};
148148

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

@@ -229,6 +271,19 @@ void HostTarget::sendCommand(HostCommand command) {
229271
});
230272
}
231273

274+
void HostTarget::installPerfMetricsBinding() {
275+
perfMetricsBinding_ = std::make_unique<HostRuntimeBinding>(
276+
*this, // Used immediately
277+
"__chromium_devtools_metrics_reporter",
278+
[this](const std::string& message) {
279+
auto payload = folly::parseJson(message);
280+
HostTargetDelegate::PerfMonitorUpdateRequest request{
281+
.interactionName = payload["eventName"].asString(),
282+
.durationMs = static_cast<uint16_t>(payload["duration"].asInt())};
283+
delegate_.unstable_onPerfMonitorUpdate(request);
284+
});
285+
}
286+
232287
HostTargetController::HostTargetController(HostTarget& target)
233288
: target_(target) {}
234289

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class HostTargetSession;
3838
class HostAgent;
3939
class HostTracingAgent;
4040
class HostCommandSender;
41+
class HostRuntimeBinding;
4142
class HostTarget;
4243
class HostTargetTraceRecording;
4344

@@ -97,6 +98,11 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
9798
}
9899
};
99100

101+
struct PerfMonitorUpdateRequest {
102+
std::string interactionName;
103+
uint16_t durationMs;
104+
};
105+
100106
virtual ~HostTargetDelegate() override;
101107

102108
/**
@@ -125,6 +131,13 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
125131
virtual void onSetPausedInDebuggerMessage(
126132
const OverlaySetPausedInDebuggerMessageRequest& request) = 0;
127133

134+
/**
135+
* [Experimental] Called when the runtime has new data for the V2 Perf
136+
* Monitor overlay. This is called on the inspector thread.
137+
*/
138+
virtual void unstable_onPerfMonitorUpdate(
139+
const PerfMonitorUpdateRequest& /*request*/) {}
140+
128141
/**
129142
* Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP
130143
* request. Platform implementations should override this to perform a
@@ -296,6 +309,7 @@ class JSINSPECTOR_EXPORT HostTarget
296309
std::shared_ptr<ExecutionContextManager> executionContextManager_;
297310
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
298311
std::unique_ptr<HostCommandSender> commandSender_;
312+
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
299313

300314
/**
301315
* Current pending trace recording, which encapsulates the configuration of
@@ -313,6 +327,13 @@ class JSINSPECTOR_EXPORT HostTarget
313327
return currentInstance_ != nullptr;
314328
}
315329

330+
/**
331+
* Install a runtime binding subscribing to the Interaction to Next Paint
332+
* (INP) live metric, which we broadcast to the V2 Perf Monitor overlay
333+
* via \ref HostTargetDelegate::unstable_onPerfMonitorUpdate.
334+
*/
335+
void installPerfMetricsBinding();
336+
316337
// Necessary to allow HostAgent to access HostTarget's internals in a
317338
// controlled way (i.e. only HostTargetController gets friend access, while
318339
// 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)