Skip to content

Commit 12627f1

Browse files
huntiefacebook-github-bot
authored andcommitted
Report PerformanceResourceTiming events (react#51126)
Summary: Pull Request resolved: react#51126 NOTE: Resubmission of D73922341, fixing a dependency cycle (T223145455). (Sparsely) wires up reporting of Network events to the Web Performance subsystem. Changelog: [Internal] Reviewed By: rubennorte Differential Revision: D74245441 fbshipit-source-id: ce8f47dfb3f5cd415f51287d7fcc6a048336018e
1 parent f922563 commit 12627f1

10 files changed

Lines changed: 183 additions & 21 deletions

File tree

packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ target_include_directories(jsinspector_network PUBLIC ${REACT_COMMON_DIR})
2424
target_link_libraries(jsinspector_network
2525
folly_runtime
2626
jsinspector_cdp
27-
)
27+
react_performance_timeline
28+
react_timing)

packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include <folly/dynamic.h>
1616
#include <jsinspector-modern/cdp/CdpJson.h>
1717
#endif
18+
#include <react/featureflags/ReactNativeFeatureFlags.h>
19+
#include <react/performance/timeline/PerformanceEntryReporter.h>
1820

1921
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
2022
#include <chrono>
@@ -27,7 +29,7 @@ namespace facebook::react::jsinspector_modern {
2729
namespace {
2830

2931
/**
30-
* Get the current Unix timestamp in seconds (µs precision).
32+
* Get the current Unix timestamp in seconds (µs precision, CDP format).
3133
*/
3234
double getCurrentUnixTimestampSeconds() {
3335
auto now = std::chrono::system_clock::now().time_since_epoch();
@@ -74,7 +76,23 @@ void NetworkReporter::reportRequestStart(
7476
const std::string& requestId,
7577
const RequestInfo& requestInfo,
7678
int encodedDataLength,
77-
const std::optional<ResponseInfo>& redirectResponse) const {
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+
7896
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
7997
// Debug build: CDP event handling
8098
if (!isDebuggingEnabledNoSync()) {
@@ -107,8 +125,20 @@ void NetworkReporter::reportRequestStart(
107125
#endif
108126
}
109127

110-
void NetworkReporter::reportConnectionTiming(
111-
const std::string& /*requestId*/) const {
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+
112142
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
113143
// Debug build: CDP event handling
114144
if (!isDebuggingEnabledNoSync()) {
@@ -136,7 +166,21 @@ void NetworkReporter::reportRequestFailed(
136166
void NetworkReporter::reportResponseStart(
137167
const std::string& requestId,
138168
const ResponseInfo& responseInfo,
139-
int encodedDataLength) const {
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+
140184
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
141185
// Debug build: CDP event handling
142186
if (!isDebuggingEnabledNoSync()) {
@@ -159,8 +203,21 @@ void NetworkReporter::reportResponseStart(
159203
#endif
160204
}
161205

162-
void NetworkReporter::reportDataReceived(
163-
const std::string& /*requestId*/) const {
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+
164221
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
165222
// Debug build: CDP event handling
166223
if (!isDebuggingEnabledNoSync()) {
@@ -174,7 +231,30 @@ void NetworkReporter::reportDataReceived(
174231

175232
void NetworkReporter::reportResponseEnd(
176233
const std::string& requestId,
177-
int encodedDataLength) const {
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+
178258
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
179259
// Debug build: CDP event handling
180260
if (!isDebuggingEnabledNoSync()) {

packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99

1010
#include "NetworkTypes.h"
1111

12+
#include <react/timing/primitives.h>
13+
1214
#include <atomic>
1315
#include <functional>
16+
#include <mutex>
1417
#include <string>
1518

1619
namespace facebook::react::jsinspector_modern {
@@ -22,6 +25,24 @@ namespace facebook::react::jsinspector_modern {
2225
*/
2326
using FrontendChannel = std::function<void(std::string_view messageJson)>;
2427

28+
/**
29+
* Container for static network event metadata aligning with the
30+
* `PerformanceResourceTiming` interface.
31+
*
32+
* This is a lightweight type stored in `perfTimingsBuffer_` and used for
33+
* reporting complete events to the Web Performance subsystem. Not used for CDP
34+
* reporting.
35+
*/
36+
struct ResourceTimingData {
37+
std::string url;
38+
DOMHighResTimeStamp fetchStart;
39+
DOMHighResTimeStamp requestStart;
40+
std::optional<DOMHighResTimeStamp> connectStart;
41+
std::optional<DOMHighResTimeStamp> connectEnd;
42+
std::optional<DOMHighResTimeStamp> responseStart;
43+
std::optional<int> responseStatus;
44+
};
45+
2546
/**
2647
* [Experimental] An interface for reporting network events to the modern
2748
* debugger server and Web Performance APIs.
@@ -67,7 +88,7 @@ class NetworkReporter {
6788
const std::string& requestId,
6889
const RequestInfo& requestInfo,
6990
int encodedDataLength,
70-
const std::optional<ResponseInfo>& redirectResponse) const;
91+
const std::optional<ResponseInfo>& redirectResponse);
7192

7293
/**
7394
* Report detailed timing info, such as DNS lookup, when a request has
@@ -79,7 +100,7 @@ class NetworkReporter {
79100
*
80101
* https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-connectstart
81102
*/
82-
void reportConnectionTiming(const std::string& requestId) const;
103+
void reportConnectionTiming(const std::string& requestId);
83104

84105
/**
85106
* Report when a network request has failed.
@@ -100,14 +121,14 @@ class NetworkReporter {
100121
void reportResponseStart(
101122
const std::string& requestId,
102123
const ResponseInfo& responseInfo,
103-
int encodedDataLength) const;
124+
int encodedDataLength);
104125

105126
/**
106127
* Report when additional chunks of the response body have been received.
107128
*
108129
* Corresponds to `Network.dataReceived` in CDP.
109130
*/
110-
void reportDataReceived(const std::string& requestId) const;
131+
void reportDataReceived(const std::string& requestId);
111132

112133
/**
113134
* Report when a network request is complete and we are no longer receiving
@@ -118,8 +139,7 @@ class NetworkReporter {
118139
*
119140
* https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responseend
120141
*/
121-
void reportResponseEnd(const std::string& requestId, int encodedDataLength)
122-
const;
142+
void reportResponseEnd(const std::string& requestId, int encodedDataLength);
123143

124144
private:
125145
FrontendChannel frontendChannel_;
@@ -134,6 +154,9 @@ class NetworkReporter {
134154
inline bool isDebuggingEnabledNoSync() const {
135155
return debuggingEnabled_.load(std::memory_order_relaxed);
136156
}
157+
158+
std::unordered_map<std::string, ResourceTimingData> perfTimingsBuffer_{};
159+
std::mutex perfTimingsMutex_;
137160
};
138161

139162
} // namespace facebook::react::jsinspector_modern

packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ Pod::Spec.new do |s|
4747
end
4848

4949
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
50+
add_dependency(s, "React-featureflags")
51+
s.dependency "React-performancetimeline"
52+
s.dependency "React-timing"
5053

5154
add_rn_third_party_dependencies(s)
5255
end

packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ target_include_directories(react_performance_timeline PUBLIC ${REACT_COMMON_DIR}
1818
target_link_libraries(react_performance_timeline
1919
jsinspector_tracing
2020
reactperflogger
21+
react_featureflags
2122
react_timing)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ struct PerformanceResourceTiming : AbstractPerformanceEntry {
5757
static constexpr PerformanceEntryType entryType =
5858
PerformanceEntryType::RESOURCE;
5959
/** Aligns with `startTime`. */
60-
std::optional<DOMHighResTimeStamp> fetchStart;
61-
std::optional<DOMHighResTimeStamp> requestStart;
60+
DOMHighResTimeStamp fetchStart;
61+
DOMHighResTimeStamp requestStart;
6262
std::optional<DOMHighResTimeStamp> connectStart;
6363
std::optional<DOMHighResTimeStamp> connectEnd;
6464
std::optional<DOMHighResTimeStamp> responseStart;

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ std::vector<PerformanceEntryType> getSupportedEntryTypesInternal() {
3030
PerformanceEntryType::LONGTASK,
3131
};
3232

33+
if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
34+
supportedEntryTypes.emplace_back(PerformanceEntryType::RESOURCE);
35+
}
36+
3337
return supportedEntryTypes;
3438
}
3539

@@ -289,6 +293,37 @@ void PerformanceEntryReporter::reportLongTask(
289293
observerRegistry_->queuePerformanceEntry(entry);
290294
}
291295

296+
PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming(
297+
const std::string& url,
298+
DOMHighResTimeStamp fetchStart,
299+
DOMHighResTimeStamp requestStart,
300+
std::optional<DOMHighResTimeStamp> connectStart,
301+
std::optional<DOMHighResTimeStamp> connectEnd,
302+
DOMHighResTimeStamp responseStart,
303+
DOMHighResTimeStamp responseEnd,
304+
const std::optional<int>& responseStatus) {
305+
const auto entry = PerformanceResourceTiming{
306+
{.name = url, .startTime = fetchStart},
307+
fetchStart,
308+
requestStart,
309+
connectStart,
310+
connectEnd,
311+
responseStart,
312+
responseEnd,
313+
responseStatus,
314+
};
315+
316+
// Add to buffers & notify observers
317+
{
318+
std::unique_lock lock(buffersMutex_);
319+
resourceTimingBuffer_.add(entry);
320+
}
321+
322+
observerRegistry_->queuePerformanceEntry(entry);
323+
324+
return entry;
325+
}
326+
292327
void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const {
293328
auto& performanceTracer =
294329
jsinspector_modern::PerformanceTracer::getInstance();

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@
2121

2222
namespace facebook::react {
2323

24+
// Aligned with maxBufferSize implemented by browsers
25+
// https://w3c.github.io/timing-entrytypes-registry/#registry
2426
constexpr size_t EVENT_BUFFER_SIZE = 150;
2527
constexpr size_t LONG_TASK_BUFFER_SIZE = 200;
28+
constexpr size_t RESOURCE_TIMING_BUFFER_SIZE = 250;
2629

2730
constexpr DOMHighResTimeStamp LONG_TASK_DURATION_THRESHOLD_MS = 50.0;
2831

@@ -101,15 +104,26 @@ class PerformanceEntryReporter {
101104

102105
void reportLongTask(double startTime, double duration);
103106

107+
PerformanceResourceTiming reportResourceTiming(
108+
const std::string& url,
109+
DOMHighResTimeStamp fetchStart,
110+
DOMHighResTimeStamp requestStart,
111+
std::optional<DOMHighResTimeStamp> connectStart,
112+
std::optional<DOMHighResTimeStamp> connectEnd,
113+
DOMHighResTimeStamp responseStart,
114+
DOMHighResTimeStamp responseEnd,
115+
const std::optional<int>& responseStatus);
116+
104117
private:
105118
std::unique_ptr<PerformanceObserverRegistry> observerRegistry_;
106119

107120
mutable std::shared_mutex buffersMutex_;
108121
PerformanceEntryCircularBuffer eventBuffer_{EVENT_BUFFER_SIZE};
109122
PerformanceEntryCircularBuffer longTaskBuffer_{LONG_TASK_BUFFER_SIZE};
123+
PerformanceEntryCircularBuffer resourceTimingBuffer_{
124+
RESOURCE_TIMING_BUFFER_SIZE};
110125
PerformanceEntryKeyedBuffer markBuffer_;
111126
PerformanceEntryKeyedBuffer measureBuffer_;
112-
PerformanceEntryKeyedBuffer resourceBuffer_;
113127

114128
std::unordered_map<std::string, uint32_t> eventCounts_;
115129

@@ -129,7 +143,7 @@ class PerformanceEntryReporter {
129143
case PerformanceEntryType::LONGTASK:
130144
return longTaskBuffer_;
131145
case PerformanceEntryType::RESOURCE:
132-
return resourceBuffer_;
146+
return resourceTimingBuffer_;
133147
case PerformanceEntryType::_NEXT:
134148
throw std::logic_error("Cannot get buffer for _NEXT entry type");
135149
}
@@ -147,7 +161,7 @@ class PerformanceEntryReporter {
147161
case PerformanceEntryType::LONGTASK:
148162
return longTaskBuffer_;
149163
case PerformanceEntryType::RESOURCE:
150-
return resourceBuffer_;
164+
return resourceTimingBuffer_;
151165
case PerformanceEntryType::_NEXT:
152166
throw std::logic_error("Cannot get buffer for _NEXT entry type");
153167
}

packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ export function rawToPerformanceEntryType(
7777
return 'event';
7878
case RawPerformanceEntryTypeValues.LONGTASK:
7979
return 'longtask';
80+
case RawPerformanceEntryTypeValues.RESOURCE:
81+
return 'resource';
8082
default:
8183
throw new TypeError(
8284
`rawToPerformanceEntryType: unexpected performance entry type received: ${type}`,

0 commit comments

Comments
 (0)