Skip to content

Commit b3fe48b

Browse files
huntiehannojg
authored andcommitted
Report PerformanceResourceTiming events (facebook#51025)
Summary: Pull Request resolved: facebook#51025 (Sparsely) wires up reporting of Network events to the Web Performance subsystem. Our plan is to report to the Web Performance APIs (lightweight timing metadata, here) for all build flavours, and report to CDP (more costly full metadata/previews) in dev/profiling builds. **Notes** - Introduces `PerformanceEntryReporter::unstable_reportResourceTiming` — this will become "stable" when further network events/fields are fully hydrated on Android and iOS. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D73922341 fbshipit-source-id: bcfc03c3d8a9a286ae72ba00a3313602fb2adea8
1 parent 6bfd829 commit b3fe48b

File tree

10 files changed

+684
-71
lines changed

10 files changed

+684
-71
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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_ANDROID_DIR}/src/main/jni/first-party/jni-lib-merge/SoMerging-utils.cmake)
10+
11+
add_compile_options(
12+
-fexceptions
13+
-std=c++20
14+
-Wall
15+
-Wpedantic)
16+
17+
file(GLOB jsinspector_network_SRC CONFIGURE_DEPENDS *.cpp)
18+
19+
add_library(jsinspector_network OBJECT ${jsinspector_network_SRC})
20+
target_merge_so(jsinspector_network)
21+
22+
target_include_directories(jsinspector_network PUBLIC ${REACT_COMMON_DIR})
23+
24+
target_link_libraries(jsinspector_network
25+
folly_runtime
26+
jsinspector_cdp
27+
react_performance_timeline
28+
react_timing)
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
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 "NetworkReporter.h"
9+
10+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
11+
#include "CdpNetwork.h"
12+
#endif
13+
14+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
15+
#include <folly/dynamic.h>
16+
#include <jsinspector-modern/cdp/CdpJson.h>
17+
#endif
18+
#include <react/featureflags/ReactNativeFeatureFlags.h>
19+
#include <react/performance/timeline/PerformanceEntryReporter.h>
20+
21+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
22+
#include <chrono>
23+
#endif
24+
#include <stdexcept>
25+
26+
namespace facebook::react::jsinspector_modern {
27+
28+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
29+
namespace {
30+
31+
/**
32+
* Get the current Unix timestamp in seconds (µs precision, CDP format).
33+
*/
34+
double getCurrentUnixTimestampSeconds() {
35+
auto now = std::chrono::system_clock::now().time_since_epoch();
36+
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now).count();
37+
auto micros =
38+
std::chrono::duration_cast<std::chrono::microseconds>(now).count() %
39+
1000000;
40+
41+
return static_cast<double>(seconds) +
42+
(static_cast<double>(micros) / 1000000.0);
43+
}
44+
45+
} // namespace
46+
#endif
47+
48+
NetworkReporter& NetworkReporter::getInstance() {
49+
static NetworkReporter instance;
50+
return instance;
51+
}
52+
53+
void NetworkReporter::setFrontendChannel(FrontendChannel frontendChannel) {
54+
frontendChannel_ = std::move(frontendChannel);
55+
}
56+
57+
bool NetworkReporter::enableDebugging() {
58+
if (debuggingEnabled_.load(std::memory_order_acquire)) {
59+
return false;
60+
}
61+
62+
debuggingEnabled_.store(true, std::memory_order_release);
63+
return true;
64+
}
65+
66+
bool NetworkReporter::disableDebugging() {
67+
if (!debuggingEnabled_.load(std::memory_order_acquire)) {
68+
return false;
69+
}
70+
71+
debuggingEnabled_.store(false, std::memory_order_release);
72+
return true;
73+
}
74+
75+
void NetworkReporter::reportRequestStart(
76+
const std::string& requestId,
77+
const RequestInfo& requestInfo,
78+
int encodedDataLength,
79+
const std::optional<ResponseInfo>& redirectResponse) {
80+
if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
81+
double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp();
82+
83+
// All builds: Annotate PerformanceResourceTiming metadata
84+
{
85+
std::lock_guard<std::mutex> lock(perfTimingsMutex_);
86+
perfTimingsBuffer_.emplace(
87+
requestId,
88+
ResourceTimingData{
89+
.url = requestInfo.url,
90+
.fetchStart = now,
91+
.requestStart = now,
92+
});
93+
}
94+
}
95+
96+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
97+
// Debug build: CDP event handling
98+
if (!isDebuggingEnabledNoSync()) {
99+
return;
100+
}
101+
102+
double timestamp = getCurrentUnixTimestampSeconds();
103+
auto request = cdp::network::Request::fromInputParams(requestInfo);
104+
auto params = cdp::network::RequestWillBeSentParams{
105+
.requestId = requestId,
106+
.loaderId = "",
107+
.documentURL = "mobile",
108+
.request = std::move(request),
109+
// NOTE: Both timestamp and wallTime use the same unit, however wallTime
110+
// is relative to an "arbitrary epoch". In our implementation, use the
111+
// Unix epoch for both.
112+
.timestamp = timestamp,
113+
.wallTime = timestamp,
114+
.initiator = folly::dynamic::object("type", "script"),
115+
.redirectHasExtraInfo = redirectResponse.has_value(),
116+
};
117+
118+
if (redirectResponse.has_value()) {
119+
params.redirectResponse = cdp::network::Response::fromInputParams(
120+
redirectResponse.value(), encodedDataLength);
121+
}
122+
123+
frontendChannel_(
124+
cdp::jsonNotification("Network.requestWillBeSent", params.toDynamic()));
125+
#endif
126+
}
127+
128+
void NetworkReporter::reportConnectionTiming(const std::string& requestId) {
129+
if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
130+
double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp();
131+
132+
// All builds: Annotate PerformanceResourceTiming metadata
133+
{
134+
std::lock_guard<std::mutex> lock(perfTimingsMutex_);
135+
auto it = perfTimingsBuffer_.find(requestId);
136+
if (it != perfTimingsBuffer_.end()) {
137+
it->second.connectStart = now;
138+
}
139+
}
140+
}
141+
142+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
143+
// Debug build: CDP event handling
144+
if (!isDebuggingEnabledNoSync()) {
145+
return;
146+
}
147+
148+
// TODO(T218236597)
149+
throw std::runtime_error("Not implemented");
150+
#endif
151+
}
152+
153+
void NetworkReporter::reportRequestFailed(
154+
const std::string& /*requestId*/) const {
155+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
156+
// Debug build: CDP event handling
157+
if (!isDebuggingEnabledNoSync()) {
158+
return;
159+
}
160+
161+
// TODO(T218236855)
162+
throw std::runtime_error("Not implemented");
163+
#endif
164+
}
165+
166+
void NetworkReporter::reportResponseStart(
167+
const std::string& requestId,
168+
const ResponseInfo& responseInfo,
169+
int encodedDataLength) {
170+
if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
171+
double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp();
172+
173+
// All builds: Annotate PerformanceResourceTiming metadata
174+
{
175+
std::lock_guard<std::mutex> lock(perfTimingsMutex_);
176+
auto it = perfTimingsBuffer_.find(requestId);
177+
if (it != perfTimingsBuffer_.end()) {
178+
it->second.responseStart = now;
179+
it->second.responseStatus = responseInfo.statusCode;
180+
}
181+
}
182+
}
183+
184+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
185+
// Debug build: CDP event handling
186+
if (!isDebuggingEnabledNoSync()) {
187+
return;
188+
}
189+
190+
auto response =
191+
cdp::network::Response::fromInputParams(responseInfo, encodedDataLength);
192+
auto params = cdp::network::ResponseReceivedParams{
193+
.requestId = requestId,
194+
.loaderId = "",
195+
.timestamp = getCurrentUnixTimestampSeconds(),
196+
.type = cdp::network::resourceTypeFromMimeType(response.mimeType),
197+
.response = response,
198+
.hasExtraInfo = false,
199+
};
200+
201+
frontendChannel_(
202+
cdp::jsonNotification("Network.responseReceived", params.toDynamic()));
203+
#endif
204+
}
205+
206+
void NetworkReporter::reportDataReceived(const std::string& requestId) {
207+
if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
208+
double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp();
209+
210+
// All builds: Annotate PerformanceResourceTiming metadata
211+
{
212+
std::lock_guard<std::mutex> lock(perfTimingsMutex_);
213+
auto it = perfTimingsBuffer_.find(requestId);
214+
if (it != perfTimingsBuffer_.end()) {
215+
it->second.connectEnd = now;
216+
it->second.responseStart = now;
217+
}
218+
}
219+
}
220+
221+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
222+
// Debug build: CDP event handling
223+
if (!isDebuggingEnabledNoSync()) {
224+
return;
225+
}
226+
227+
// TODO(T218236266)
228+
throw std::runtime_error("Not implemented");
229+
#endif
230+
}
231+
232+
void NetworkReporter::reportResponseEnd(
233+
const std::string& requestId,
234+
int encodedDataLength) {
235+
if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
236+
double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp();
237+
238+
// All builds: Report PerformanceResourceTiming event
239+
{
240+
std::lock_guard<std::mutex> lock(perfTimingsMutex_);
241+
auto it = perfTimingsBuffer_.find(requestId);
242+
if (it != perfTimingsBuffer_.end()) {
243+
auto& eventData = it->second;
244+
PerformanceEntryReporter::getInstance()->reportResourceTiming(
245+
eventData.url,
246+
eventData.fetchStart,
247+
eventData.requestStart,
248+
eventData.connectStart.value_or(now),
249+
eventData.connectEnd.value_or(now),
250+
eventData.responseStart.value_or(now),
251+
now,
252+
eventData.responseStatus);
253+
perfTimingsBuffer_.erase(requestId);
254+
}
255+
}
256+
}
257+
258+
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
259+
// Debug build: CDP event handling
260+
if (!isDebuggingEnabledNoSync()) {
261+
return;
262+
}
263+
264+
auto params = cdp::network::LoadingFinishedParams{
265+
.requestId = requestId,
266+
.timestamp = getCurrentUnixTimestampSeconds(),
267+
.encodedDataLength = encodedDataLength,
268+
};
269+
270+
frontendChannel_(
271+
cdp::jsonNotification("Network.loadingFinished", params.toDynamic()));
272+
#endif
273+
}
274+
275+
} // namespace facebook::react::jsinspector_modern

0 commit comments

Comments
 (0)