Skip to content

Implement reporting InteractionEntry live metrics to runtime #52839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/react-native/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import PackageDescription
To build React Native, you need to follow these steps:
1. inside the `react-native` root folder, run `yarn install`
2. `cd packages/react-native`
3. `RN_DEP_VERSION=nightly HERMES_VERSION=nightly node scripts/prebuild-ios`
3. `RN_DEP_VERSION=nightly HERMES_VERSION=nightly node scripts/ios-prebuild`
4. `open Package.swift`
5. Build in Xcode.

Expand Down Expand Up @@ -208,6 +208,14 @@ let reactHermes = RNTarget(
]
)

/// React-performancecdpmetrics.podspec
let reactPerformanceCdpMetrics = RNTarget(
name: .reactPerformanceCdpMetrics,
path: "ReactCommon/react/performance/cdpmetrics",
excludedPaths: ["tests"],
dependencies: [.reactNativeDependencies, .reactCxxReact, .jsi, .reactPerformanceTimeline, .reactRuntimeExecutor]
)

/// React-performancetimeline.podspec
let reactPerformanceTimeline = RNTarget(
name: .reactPerformanceTimeline,
Expand Down Expand Up @@ -554,6 +562,7 @@ let targets = [
reactNativeDependencies,
hermesPrebuilt,
reactJsiTooling,
reactPerformanceCdpMetrics,
reactPerformanceTimeline,
reactRuntimeScheduler,
rctTypesafety,
Expand Down Expand Up @@ -724,6 +733,7 @@ extension String {
static let hermesPrebuilt = "hermes-prebuilt"

static let reactJsiTooling = "React-jsitooling"
static let reactPerformanceCdpMetrics = "React-performancecdpmetrics"
static let reactPerformanceTimeline = "React-performancetimeline"
static let reactRuntimeScheduler = "React-runtimescheduler"
static let rctTypesafety = "RCTTypesafety"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ add_react_common_subdir(hermes/inspector-modern)
add_react_common_subdir(react/renderer/runtimescheduler)
add_react_common_subdir(react/debug)
add_react_common_subdir(react/featureflags)
add_react_common_subdir(react/performance/cdpmetrics)
add_react_common_subdir(react/performance/timeline)
add_react_common_subdir(react/renderer/animations)
add_react_common_subdir(react/renderer/attributedstring)
Expand Down Expand Up @@ -191,6 +192,7 @@ add_library(reactnative
$<TARGET_OBJECTS:react_nativemodule_idlecallbacks>
$<TARGET_OBJECTS:react_nativemodule_microtasks>
$<TARGET_OBJECTS:react_newarchdefaults>
$<TARGET_OBJECTS:react_performance_cdpmetrics>
$<TARGET_OBJECTS:react_performance_timeline>
$<TARGET_OBJECTS:react_renderer_animations>
$<TARGET_OBJECTS:react_renderer_attributedstring>
Expand Down Expand Up @@ -279,6 +281,7 @@ target_include_directories(reactnative
$<TARGET_PROPERTY:react_nativemodule_idlecallbacks,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_nativemodule_microtasks,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_newarchdefaults,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_performance_cdpmetrics,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_performance_timeline,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_renderer_animations,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:react_renderer_attributedstring,INTERFACE_INCLUDE_DIRECTORIES>
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/ReactCommon/React-Fabric.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ Pod::Spec.new do |s|
ss.source_files = podspec_sources("react/renderer/scheduler/**/*.{m,mm,cpp,h}", "react/renderer/scheduler/**/*.h")
ss.header_dir = "react/renderer/scheduler"

ss.dependency "React-performancecdpmetrics"
ss.dependency "React-performancetimeline"
ss.dependency "React-Fabric/observers/events"
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# 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.

cmake_minimum_required(VERSION 3.13)
set(CMAKE_VERBOSE_MAKEFILE on)

include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)

file(GLOB react_performance_cdpmetrics_SRC CONFIGURE_DEPENDS *.cpp)
add_library(react_performance_cdpmetrics OBJECT ${react_performance_cdpmetrics_SRC})

target_compile_reactnative_options(react_performance_cdpmetrics PRIVATE)
target_compile_options(react_performance_cdpmetrics PRIVATE -Wpedantic)

target_include_directories(react_performance_cdpmetrics PUBLIC ${REACT_COMMON_DIR})
target_link_libraries(react_performance_cdpmetrics
folly_runtime
jsi
react_performancetimeline
react_timing
runtimeexecutor)
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 "CdpMetricsReporter.h"

#include <folly/dynamic.h>
#include <folly/json.h>
#include <string_view>

namespace facebook::react {

namespace {

constexpr std::string_view metricsReporterName =
"__chromium_devtools_metrics_reporter";

} // namespace

CdpMetricsReporter::CdpMetricsReporter(RuntimeExecutor runtimeExecutor)
: runtimeExecutor_(std::move(runtimeExecutor)) {}

void CdpMetricsReporter::onEventTimingEntry(
const PerformanceEventTiming& entry) {
runtimeExecutor_([entry = std::move(entry)](jsi::Runtime& runtime) {
auto global = runtime.global();
if (!global.hasProperty(runtime, metricsReporterName.data())) {
return;
}

folly::dynamic jsonPayload = folly::dynamic::object;
jsonPayload["eventName"] = entry.name;
jsonPayload["durationMs"] =
static_cast<int>(entry.duration.toDOMHighResTimeStamp());
jsonPayload["startTime"] =
static_cast<int>(entry.startTime.toDOMHighResTimeStamp());
auto jsonString = folly::toJson(jsonPayload);
auto jsiString = jsi::String::createFromUtf8(runtime, jsonString);

auto metricsReporter =
global.getPropertyAsFunction(runtime, metricsReporterName.data());
metricsReporter.call(runtime, jsiString);
});
}

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 <ReactCommon/RuntimeExecutor.h>
#include <jsi/jsi.h>
#include <react/performance/timeline/PerformanceEntry.h>
#include <react/performance/timeline/PerformanceEntryReporterListeners.h>
#include <react/timing/primitives.h>

namespace facebook::react {

/**
* [Experimental] Reports CDP interaction events via the
* "__chromium_devtools_metrics_reporter" runtime binding.
*
* This populates the Interaction to Next Paint (INP) live metric in Chrome
* DevTools and the V2 Perf Monitor. Events are reported immediately and do
* not require an active CDP Tracing session.
*/
class CdpMetricsReporter : public PerformanceEntryReporterEventTimingListener {
public:
explicit CdpMetricsReporter(RuntimeExecutor runtimeExecutor);

void onEventTimingEntry(const PerformanceEventTiming& entry) override;

private:
const RuntimeExecutor runtimeExecutor_{};
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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.

require "json"

package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json")))
version = package['version']

source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end

header_search_paths = []

if ENV['USE_FRAMEWORKS']
header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the cdpmetrics access its own files
end

Pod::Spec.new do |s|
s.name = "React-performancecdpmetrics"
s.version = version
s.summary = "Module for reporting React Native performance live metrics to the debugger"
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = podspec_sources("**/*.{cpp,h}", "**/*.h")
s.header_dir = "react/performance/cdpmetrics"
s.exclude_files = "tests"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(' ')}

if ENV['USE_FRAMEWORKS'] && ReactNativeCoreUtils.build_rncore_from_source()
s.module_name = "React_performancecdpmetrics"
s.header_mappings_dir = "../../.."
end

add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
s.dependency "React-jsi"
s.dependency "React-performancetimeline"
s.dependency "React-timing"

add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ HighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const {
return timeStampProvider_ != nullptr ? timeStampProvider_()
: HighResTimeStamp::now();
}
void PerformanceEntryReporter::addEventTimingListener(
PerformanceEntryReporterEventTimingListener* listener) {
std::unique_lock lock(listenersMutex_);
eventTimingListeners_.push_back(listener);
}

void PerformanceEntryReporter::removeEventTimingListener(
PerformanceEntryReporterEventTimingListener* listener) {
std::unique_lock lock(listenersMutex_);
auto it = std::find(
eventTimingListeners_.begin(), eventTimingListeners_.end(), listener);
if (it != eventTimingListeners_.end()) {
eventTimingListeners_.erase(it);
}
}

std::vector<PerformanceEntryType>
PerformanceEntryReporter::getSupportedEntryTypes() {
Expand Down Expand Up @@ -231,7 +246,7 @@ std::optional<HighResTimeStamp> PerformanceEntryReporter::getMarkTime(
}

void PerformanceEntryReporter::reportEvent(
std::string name,
const std::string& name,
HighResTimeStamp startTime,
HighResDuration duration,
HighResTimeStamp processingStart,
Expand All @@ -246,7 +261,7 @@ void PerformanceEntryReporter::reportEvent(
}

const auto entry = PerformanceEventTiming{
{.name = std::move(name), .startTime = startTime, .duration = duration},
{.name = name, .startTime = startTime, .duration = duration},
processingStart,
processingEnd,
interactionId};
Expand All @@ -258,6 +273,16 @@ void PerformanceEntryReporter::reportEvent(

// TODO(T198982346): Log interaction events to jsinspector_modern
observerRegistry_->queuePerformanceEntry(entry);

std::vector<PerformanceEntryReporterEventTimingListener*> listenersCopy;
{
std::shared_lock lock(listenersMutex_);
listenersCopy = eventTimingListeners_;
}

for (auto* listener : listenersCopy) {
listener->onEventTimingEntry(entry);
}
}

void PerformanceEntryReporter::reportLongTask(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "PerformanceEntryCircularBuffer.h"
#include "PerformanceEntryKeyedBuffer.h"
#include "PerformanceEntryReporterListeners.h"
#include "PerformanceObserverRegistry.h"

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

void addEventTimingListener(
PerformanceEntryReporterEventTimingListener* listener);
void removeEventTimingListener(
PerformanceEntryReporterEventTimingListener* listener);

static std::vector<PerformanceEntryType> getSupportedEntryTypes();

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

void reportEvent(
std::string name,
const std::string& name,
HighResTimeStamp startTime,
HighResDuration duration,
HighResTimeStamp processingStart,
Expand Down Expand Up @@ -133,6 +139,9 @@ class PerformanceEntryReporter {
std::unordered_map<std::string, uint32_t> eventCounts_;

std::function<HighResTimeStamp()> timeStampProvider_ = nullptr;
mutable std::shared_mutex listenersMutex_;
std::vector<PerformanceEntryReporterEventTimingListener*>
eventTimingListeners_{};

const inline PerformanceEntryBuffer& getBuffer(
PerformanceEntryType entryType) const {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 "PerformanceEntry.h"

#include <folly/dynamic.h>
#include <react/timing/primitives.h>

namespace facebook::react {

class PerformanceEntryReporterEventTimingListener {
public:
virtual ~PerformanceEntryReporterEventTimingListener() = default;

virtual void onEventTimingEntry(const PerformanceEventTiming& entry) = 0;
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ target_link_libraries(react_renderer_scheduler
jsi
react_debug
react_featureflags
react_performance_cdpmetrics
react_performance_timeline
react_renderer_componentregistry
react_renderer_core
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Scheduler::Scheduler(
auto performanceEntryReporter = PerformanceEntryReporter::getInstance();
performanceEntryReporter_ = performanceEntryReporter;

if (ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
ReactNativeFeatureFlags::cdpInteractionMetricsEnabled()) {
cdpMetricsReporter_.emplace(CdpMetricsReporter{runtimeExecutor_});
performanceEntryReporter_->addEventTimingListener(&*cdpMetricsReporter_);
}

eventPerformanceLogger_ =
std::make_shared<EventPerformanceLogger>(performanceEntryReporter_);

Expand Down Expand Up @@ -164,6 +170,10 @@ Scheduler::~Scheduler() {
uiManager_->setDelegate(nullptr);
uiManager_->setAnimationDelegate(nullptr);

if (cdpMetricsReporter_) {
performanceEntryReporter_->removeEventTimingListener(&*cdpMetricsReporter_);
}

// Then, let's verify that the requirement was satisfied.
auto surfaceIds = std::vector<SurfaceId>{};
uiManager_->getShadowTreeRegistry().enumerate(
Expand Down
Loading
Loading