Skip to content

Commit 6877a61

Browse files
huntiefacebook-github-bot
authored andcommitted
Implement reporting InteractionEntry live metrics to runtime (facebook#52839)
Summary: **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** Adds and configures a `CdpMetricsReporter` class to report `InteractionEntry` live metrics over CDP via the `"__chromium_devtools_metrics_reporter"` runtime binding. **Notes** - Introduces a new `react/performance/cdpmetrics` package, and a listener API on `PerformanceEntryReporter` (both to avoid a `jni` dependency in `react/performance/timeline`). Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D78904748
1 parent 869a976 commit 6877a61

File tree

14 files changed

+333
-79
lines changed

14 files changed

+333
-79
lines changed

packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ add_react_common_subdir(hermes/inspector-modern)
8181
add_react_common_subdir(react/renderer/runtimescheduler)
8282
add_react_common_subdir(react/debug)
8383
add_react_common_subdir(react/featureflags)
84+
add_react_common_subdir(react/performance/cdpmetrics)
8485
add_react_common_subdir(react/performance/timeline)
8586
add_react_common_subdir(react/renderer/animations)
8687
add_react_common_subdir(react/renderer/attributedstring)
@@ -191,6 +192,7 @@ add_library(reactnative
191192
$<TARGET_OBJECTS:react_nativemodule_idlecallbacks>
192193
$<TARGET_OBJECTS:react_nativemodule_microtasks>
193194
$<TARGET_OBJECTS:react_newarchdefaults>
195+
$<TARGET_OBJECTS:react_performance_cdpmetrics>
194196
$<TARGET_OBJECTS:react_performance_timeline>
195197
$<TARGET_OBJECTS:react_renderer_animations>
196198
$<TARGET_OBJECTS:react_renderer_attributedstring>
@@ -279,6 +281,7 @@ target_include_directories(reactnative
279281
$<TARGET_PROPERTY:react_nativemodule_idlecallbacks,INTERFACE_INCLUDE_DIRECTORIES>
280282
$<TARGET_PROPERTY:react_nativemodule_microtasks,INTERFACE_INCLUDE_DIRECTORIES>
281283
$<TARGET_PROPERTY:react_newarchdefaults,INTERFACE_INCLUDE_DIRECTORIES>
284+
$<TARGET_PROPERTY:react_performance_cdpmetrics,INTERFACE_INCLUDE_DIRECTORIES>
282285
$<TARGET_PROPERTY:react_performance_timeline,INTERFACE_INCLUDE_DIRECTORIES>
283286
$<TARGET_PROPERTY:react_renderer_animations,INTERFACE_INCLUDE_DIRECTORIES>
284287
$<TARGET_PROPERTY:react_renderer_attributedstring,INTERFACE_INCLUDE_DIRECTORIES>

packages/react-native/ReactCommon/React-Fabric.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ Pod::Spec.new do |s|
149149
ss.source_files = podspec_sources("react/renderer/scheduler/**/*.{m,mm,cpp,h}", "react/renderer/scheduler/**/*.h")
150150
ss.header_dir = "react/renderer/scheduler"
151151

152+
ss.dependency "React-performancecdpmetrics"
152153
ss.dependency "React-performancetimeline"
153154
ss.dependency "React-Fabric/observers/events"
154155
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
cmake_minimum_required(VERSION 3.13)
7+
set(CMAKE_VERBOSE_MAKEFILE on)
8+
9+
include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
10+
11+
file(GLOB react_performance_cdpmetrics_SRC CONFIGURE_DEPENDS *.cpp)
12+
add_library(react_performance_cdpmetrics OBJECT ${react_performance_cdpmetrics_SRC})
13+
14+
target_compile_reactnative_options(react_performance_cdpmetrics PRIVATE)
15+
target_compile_options(react_performance_cdpmetrics PRIVATE -Wpedantic)
16+
17+
target_include_directories(react_performance_cdpmetrics PUBLIC ${REACT_COMMON_DIR})
18+
target_link_libraries(react_performance_cdpmetrics
19+
jsi
20+
react_runtimeexecutor
21+
react_performancetimeline
22+
react_timing
23+
folly_runtime)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "CdpMetricsReporter.h"
9+
10+
#include <folly/dynamic.h>
11+
#include <folly/json.h>
12+
#include <string_view>
13+
14+
namespace facebook::react {
15+
16+
namespace {
17+
18+
constexpr std::string_view metricsReporterName =
19+
"__chromium_devtools_metrics_reporter";
20+
21+
} // namespace
22+
23+
CdpMetricsReporter::CdpMetricsReporter(RuntimeExecutor runtimeExecutor)
24+
: runtimeExecutor_(std::move(runtimeExecutor)) {}
25+
26+
void CdpMetricsReporter::onEventTimingEntry(
27+
const PerformanceEventTiming& entry) {
28+
runtimeExecutor_([entry = std::move(entry)](jsi::Runtime& runtime) {
29+
auto global = runtime.global();
30+
if (!global.hasProperty(runtime, metricsReporterName.data())) {
31+
return;
32+
}
33+
34+
folly::dynamic jsonPayload = folly::dynamic::object;
35+
jsonPayload["eventName"] = entry.name;
36+
jsonPayload["durationMs"] =
37+
static_cast<int>(entry.duration.toDOMHighResTimeStamp());
38+
jsonPayload["startTime"] =
39+
static_cast<int>(entry.startTime.toDOMHighResTimeStamp());
40+
auto jsonString = folly::toJson(jsonPayload);
41+
auto jsiString = jsi::String::createFromUtf8(runtime, jsonString);
42+
43+
auto metricsReporter =
44+
global.getPropertyAsFunction(runtime, metricsReporterName.data());
45+
metricsReporter.call(runtime, jsiString);
46+
});
47+
}
48+
49+
} // namespace facebook::react
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <ReactCommon/RuntimeExecutor.h>
11+
#include <jsi/jsi.h>
12+
#include <react/performance/timeline/PerformanceEntry.h>
13+
#include <react/performance/timeline/PerformanceEntryReporterListeners.h>
14+
#include <react/timing/primitives.h>
15+
16+
namespace facebook::react {
17+
18+
/**
19+
* [Experimental] Reports CDP interaction events via the
20+
* "__chromium_devtools_metrics_reporter" runtime binding.
21+
*
22+
* This populates the Interaction to Next Paint (INP) live metric in Chrome
23+
* DevTools and the V2 Perf Monitor. Events are reported immediately and do
24+
* not require an active CDP Tracing session.
25+
*/
26+
class CdpMetricsReporter : public PerformanceEntryReporterEventTimingListener {
27+
public:
28+
explicit CdpMetricsReporter(RuntimeExecutor runtimeExecutor);
29+
30+
void onEventTimingEntry(const PerformanceEventTiming& entry) override;
31+
32+
private:
33+
const RuntimeExecutor runtimeExecutor_{};
34+
};
35+
36+
} // namespace facebook::react
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
require "json"
7+
8+
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
9+
version = package['version']
10+
11+
source = { :git => 'https://github.com/facebook/react-native.git' }
12+
if version == '1000.0.0'
13+
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
14+
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
15+
else
16+
source[:tag] = "v#{version}"
17+
end
18+
19+
header_search_paths = []
20+
21+
if ENV['USE_FRAMEWORKS']
22+
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the cdpmetrics access its own files
23+
end
24+
25+
Pod::Spec.new do |s|
26+
s.name = "React-performancecdpmetrics"
27+
s.version = version
28+
s.summary = "Module for reporting React Native performance live metrics to the debugger"
29+
s.homepage = "https://reactnative.dev/"
30+
s.license = package["license"]
31+
s.author = "Meta Platforms, Inc. and its affiliates"
32+
s.platforms = min_supported_versions
33+
s.source = source
34+
s.source_files = podspec_sources("**/*.{cpp,h}", "**/*.h")
35+
s.header_dir = "react/performance/cdpmetrics"
36+
s.exclude_files = "tests"
37+
s.pod_target_xcconfig = {
38+
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
39+
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')}
40+
41+
if ENV['USE_FRAMEWORKS'] && ReactNativeCoreUtils.build_rncore_from_source()
42+
s.module_name = "React_performancecdpmetrics"
43+
s.header_mappings_dir = "../../.."
44+
end
45+
46+
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
47+
s.dependency "React-jsi"
48+
s.dependency "React-performancetimeline"
49+
s.dependency "React-timing"
50+
51+
add_rn_third_party_dependencies(s)
52+
add_rncore_dependency(s)
53+
end

packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ HighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const {
9292
return timeStampProvider_ != nullptr ? timeStampProvider_()
9393
: HighResTimeStamp::now();
9494
}
95+
void PerformanceEntryReporter::addEventTimingListener(
96+
PerformanceEntryReporterEventTimingListener* listener) {
97+
std::unique_lock lock(listenersMutex_);
98+
eventTimingListeners_.push_back(listener);
99+
}
100+
101+
void PerformanceEntryReporter::removeEventTimingListener(
102+
PerformanceEntryReporterEventTimingListener* listener) {
103+
std::unique_lock lock(listenersMutex_);
104+
auto it = std::find(
105+
eventTimingListeners_.begin(), eventTimingListeners_.end(), listener);
106+
if (it != eventTimingListeners_.end()) {
107+
eventTimingListeners_.erase(it);
108+
}
109+
}
95110

96111
std::vector<PerformanceEntryType>
97112
PerformanceEntryReporter::getSupportedEntryTypes() {
@@ -231,7 +246,7 @@ std::optional<HighResTimeStamp> PerformanceEntryReporter::getMarkTime(
231246
}
232247

233248
void PerformanceEntryReporter::reportEvent(
234-
std::string name,
249+
const std::string& name,
235250
HighResTimeStamp startTime,
236251
HighResDuration duration,
237252
HighResTimeStamp processingStart,
@@ -246,7 +261,7 @@ void PerformanceEntryReporter::reportEvent(
246261
}
247262

248263
const auto entry = PerformanceEventTiming{
249-
{.name = std::move(name), .startTime = startTime, .duration = duration},
264+
{.name = name, .startTime = startTime, .duration = duration},
250265
processingStart,
251266
processingEnd,
252267
interactionId};
@@ -258,6 +273,16 @@ void PerformanceEntryReporter::reportEvent(
258273

259274
// TODO(T198982346): Log interaction events to jsinspector_modern
260275
observerRegistry_->queuePerformanceEntry(entry);
276+
277+
std::vector<PerformanceEntryReporterEventTimingListener*> listenersCopy;
278+
{
279+
std::shared_lock lock(listenersMutex_);
280+
listenersCopy = eventTimingListeners_;
281+
}
282+
283+
for (auto* listener : listenersCopy) {
284+
listener->onEventTimingEntry(entry);
285+
}
261286
}
262287

263288
void PerformanceEntryReporter::reportLongTask(

packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include "PerformanceEntryCircularBuffer.h"
1111
#include "PerformanceEntryKeyedBuffer.h"
12+
#include "PerformanceEntryReporterListeners.h"
1213
#include "PerformanceObserverRegistry.h"
1314

1415
#include <folly/dynamic.h>
@@ -73,6 +74,11 @@ class PerformanceEntryReporter {
7374
timeStampProvider_ = std::move(provider);
7475
}
7576

77+
void addEventTimingListener(
78+
PerformanceEntryReporterEventTimingListener* listener);
79+
void removeEventTimingListener(
80+
PerformanceEntryReporterEventTimingListener* listener);
81+
7682
static std::vector<PerformanceEntryType> getSupportedEntryTypes();
7783

7884
uint32_t getDroppedEntriesCount(PerformanceEntryType type) const noexcept;
@@ -100,7 +106,7 @@ class PerformanceEntryReporter {
100106
UserTimingDetailProvider&& detailProvider = nullptr);
101107

102108
void reportEvent(
103-
std::string name,
109+
const std::string& name,
104110
HighResTimeStamp startTime,
105111
HighResDuration duration,
106112
HighResTimeStamp processingStart,
@@ -133,6 +139,9 @@ class PerformanceEntryReporter {
133139
std::unordered_map<std::string, uint32_t> eventCounts_;
134140

135141
std::function<HighResTimeStamp()> timeStampProvider_ = nullptr;
142+
mutable std::shared_mutex listenersMutex_;
143+
std::vector<PerformanceEntryReporterEventTimingListener*>
144+
eventTimingListeners_{};
136145

137146
const inline PerformanceEntryBuffer& getBuffer(
138147
PerformanceEntryType entryType) const {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include "PerformanceEntry.h"
11+
12+
#include <folly/dynamic.h>
13+
#include <react/timing/primitives.h>
14+
15+
namespace facebook::react {
16+
17+
class PerformanceEntryReporterEventTimingListener {
18+
public:
19+
virtual ~PerformanceEntryReporterEventTimingListener() = default;
20+
21+
virtual void onEventTimingEntry(const PerformanceEventTiming& entry) = 0;
22+
};
23+
24+
} // namespace facebook::react

packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ target_link_libraries(react_renderer_scheduler
1919
jsi
2020
react_debug
2121
react_featureflags
22+
react_performance_cdpmetrics
2223
react_performance_timeline
2324
react_renderer_componentregistry
2425
react_renderer_core

0 commit comments

Comments
 (0)