Skip to content

Commit ec38728

Browse files
huntiefacebook-github-bot
authored andcommitted
Report PerformanceResourceTiming events (#51025)
Summary: Pull Request resolved: #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 6b304bc commit ec38728

File tree

10 files changed

+183
-21
lines changed

10 files changed

+183
-21
lines changed

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)