From 41cd4a93785065695584c12fa0d94014e52020fe Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Tue, 6 May 2025 06:09:24 -0700 Subject: [PATCH] Report PerformanceResourceTiming events Summary: NOTE: Resubmission of D73922341, fixing a dependency cycle (T223145455). (Sparsely) wires up reporting of Network events to the Web Performance subsystem. Changelog: [Internal] Differential Revision: D74245441 --- .../jsinspector-modern/network/CMakeLists.txt | 3 +- .../network/NetworkReporter.cpp | 96 +++++++++++++++++-- .../network/NetworkReporter.h | 35 +++++-- .../network/React-jsinspectornetwork.podspec | 3 + .../react/performance/timeline/CMakeLists.txt | 1 + .../performance/timeline/PerformanceEntry.h | 4 +- .../timeline/PerformanceEntryReporter.cpp | 35 +++++++ .../timeline/PerformanceEntryReporter.h | 20 +++- .../internals/RawPerformanceEntry.js | 2 + packages/rn-tester/Podfile.lock | 5 +- 10 files changed, 183 insertions(+), 21 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt b/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt index 1749a26b3e0d..162debbcf27f 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/CMakeLists.txt @@ -24,4 +24,5 @@ target_include_directories(jsinspector_network PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(jsinspector_network folly_runtime jsinspector_cdp -) + react_performance_timeline + react_timing) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp index 44e49417ecee..1eb6e19f20f6 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp @@ -15,6 +15,8 @@ #include #include #endif +#include +#include #ifdef REACT_NATIVE_DEBUGGER_ENABLED #include @@ -27,7 +29,7 @@ namespace facebook::react::jsinspector_modern { namespace { /** - * Get the current Unix timestamp in seconds (µs precision). + * Get the current Unix timestamp in seconds (µs precision, CDP format). */ double getCurrentUnixTimestampSeconds() { auto now = std::chrono::system_clock::now().time_since_epoch(); @@ -74,7 +76,23 @@ void NetworkReporter::reportRequestStart( const std::string& requestId, const RequestInfo& requestInfo, int encodedDataLength, - const std::optional& redirectResponse) const { + const std::optional& redirectResponse) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + perfTimingsBuffer_.emplace( + requestId, + ResourceTimingData{ + .url = requestInfo.url, + .fetchStart = now, + .requestStart = now, + }); + } + } + #ifdef REACT_NATIVE_DEBUGGER_ENABLED // Debug build: CDP event handling if (!isDebuggingEnabledNoSync()) { @@ -107,8 +125,20 @@ void NetworkReporter::reportRequestStart( #endif } -void NetworkReporter::reportConnectionTiming( - const std::string& /*requestId*/) const { +void NetworkReporter::reportConnectionTiming(const std::string& requestId) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.connectStart = now; + } + } + } + #ifdef REACT_NATIVE_DEBUGGER_ENABLED // Debug build: CDP event handling if (!isDebuggingEnabledNoSync()) { @@ -136,7 +166,21 @@ void NetworkReporter::reportRequestFailed( void NetworkReporter::reportResponseStart( const std::string& requestId, const ResponseInfo& responseInfo, - int encodedDataLength) const { + int encodedDataLength) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.responseStart = now; + it->second.responseStatus = responseInfo.statusCode; + } + } + } + #ifdef REACT_NATIVE_DEBUGGER_ENABLED // Debug build: CDP event handling if (!isDebuggingEnabledNoSync()) { @@ -159,8 +203,21 @@ void NetworkReporter::reportResponseStart( #endif } -void NetworkReporter::reportDataReceived( - const std::string& /*requestId*/) const { +void NetworkReporter::reportDataReceived(const std::string& requestId) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Annotate PerformanceResourceTiming metadata + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + it->second.connectEnd = now; + it->second.responseStart = now; + } + } + } + #ifdef REACT_NATIVE_DEBUGGER_ENABLED // Debug build: CDP event handling if (!isDebuggingEnabledNoSync()) { @@ -174,7 +231,30 @@ void NetworkReporter::reportDataReceived( void NetworkReporter::reportResponseEnd( const std::string& requestId, - int encodedDataLength) const { + int encodedDataLength) { + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + + // All builds: Report PerformanceResourceTiming event + { + std::lock_guard lock(perfTimingsMutex_); + auto it = perfTimingsBuffer_.find(requestId); + if (it != perfTimingsBuffer_.end()) { + auto& eventData = it->second; + PerformanceEntryReporter::getInstance()->reportResourceTiming( + eventData.url, + eventData.fetchStart, + eventData.requestStart, + eventData.connectStart.value_or(now), + eventData.connectEnd.value_or(now), + eventData.responseStart.value_or(now), + now, + eventData.responseStatus); + perfTimingsBuffer_.erase(requestId); + } + } + } + #ifdef REACT_NATIVE_DEBUGGER_ENABLED // Debug build: CDP event handling if (!isDebuggingEnabledNoSync()) { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h index c6d281e15c75..4227deca3df0 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h @@ -9,8 +9,11 @@ #include "NetworkTypes.h" +#include + #include #include +#include #include namespace facebook::react::jsinspector_modern { @@ -22,6 +25,24 @@ namespace facebook::react::jsinspector_modern { */ using FrontendChannel = std::function; +/** + * Container for static network event metadata aligning with the + * `PerformanceResourceTiming` interface. + * + * This is a lightweight type stored in `perfTimingsBuffer_` and used for + * reporting complete events to the Web Performance subsystem. Not used for CDP + * reporting. + */ +struct ResourceTimingData { + std::string url; + DOMHighResTimeStamp fetchStart; + DOMHighResTimeStamp requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; + std::optional responseStatus; +}; + /** * [Experimental] An interface for reporting network events to the modern * debugger server and Web Performance APIs. @@ -67,7 +88,7 @@ class NetworkReporter { const std::string& requestId, const RequestInfo& requestInfo, int encodedDataLength, - const std::optional& redirectResponse) const; + const std::optional& redirectResponse); /** * Report detailed timing info, such as DNS lookup, when a request has @@ -79,7 +100,7 @@ class NetworkReporter { * * https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-connectstart */ - void reportConnectionTiming(const std::string& requestId) const; + void reportConnectionTiming(const std::string& requestId); /** * Report when a network request has failed. @@ -100,14 +121,14 @@ class NetworkReporter { void reportResponseStart( const std::string& requestId, const ResponseInfo& responseInfo, - int encodedDataLength) const; + int encodedDataLength); /** * Report when additional chunks of the response body have been received. * * Corresponds to `Network.dataReceived` in CDP. */ - void reportDataReceived(const std::string& requestId) const; + void reportDataReceived(const std::string& requestId); /** * Report when a network request is complete and we are no longer receiving @@ -118,8 +139,7 @@ class NetworkReporter { * * https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-responseend */ - void reportResponseEnd(const std::string& requestId, int encodedDataLength) - const; + void reportResponseEnd(const std::string& requestId, int encodedDataLength); private: FrontendChannel frontendChannel_; @@ -134,6 +154,9 @@ class NetworkReporter { inline bool isDebuggingEnabledNoSync() const { return debuggingEnabled_.load(std::memory_order_relaxed); } + + std::unordered_map perfTimingsBuffer_{}; + std::mutex perfTimingsMutex_; }; } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec index eb2b513439a7..b4c4a2b545f2 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec @@ -47,6 +47,9 @@ Pod::Spec.new do |s| end add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp') + add_dependency(s, "React-featureflags") + s.dependency "React-performancetimeline" + s.dependency "React-timing" add_rn_third_party_dependencies(s) end diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index c507e4fd449e..4de6a14a5098 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -18,4 +18,5 @@ target_include_directories(react_performance_timeline PUBLIC ${REACT_COMMON_DIR} target_link_libraries(react_performance_timeline jsinspector_tracing reactperflogger + react_featureflags react_timing) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index 45a7044934c7..e794902f85dc 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -57,8 +57,8 @@ struct PerformanceResourceTiming : AbstractPerformanceEntry { static constexpr PerformanceEntryType entryType = PerformanceEntryType::RESOURCE; /** Aligns with `startTime`. */ - std::optional fetchStart; - std::optional requestStart; + DOMHighResTimeStamp fetchStart; + DOMHighResTimeStamp requestStart; std::optional connectStart; std::optional connectEnd; std::optional responseStart; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 8273a89d429a..2d0ba22aec3f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -30,6 +30,10 @@ std::vector getSupportedEntryTypesInternal() { PerformanceEntryType::LONGTASK, }; + if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { + supportedEntryTypes.emplace_back(PerformanceEntryType::RESOURCE); + } + return supportedEntryTypes; } @@ -289,6 +293,37 @@ void PerformanceEntryReporter::reportLongTask( observerRegistry_->queuePerformanceEntry(entry); } +PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( + const std::string& url, + DOMHighResTimeStamp fetchStart, + DOMHighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + DOMHighResTimeStamp responseStart, + DOMHighResTimeStamp responseEnd, + const std::optional& responseStatus) { + const auto entry = PerformanceResourceTiming{ + {.name = url, .startTime = fetchStart}, + fetchStart, + requestStart, + connectStart, + connectEnd, + responseStart, + responseEnd, + responseStatus, + }; + + // Add to buffers & notify observers + { + std::unique_lock lock(buffersMutex_); + resourceTimingBuffer_.add(entry); + } + + observerRegistry_->queuePerformanceEntry(entry); + + return entry; +} + void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { auto& performanceTracer = jsinspector_modern::PerformanceTracer::getInstance(); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index a8a75fbfcd6b..fba99695551f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -21,8 +21,11 @@ namespace facebook::react { +// Aligned with maxBufferSize implemented by browsers +// https://w3c.github.io/timing-entrytypes-registry/#registry constexpr size_t EVENT_BUFFER_SIZE = 150; constexpr size_t LONG_TASK_BUFFER_SIZE = 200; +constexpr size_t RESOURCE_TIMING_BUFFER_SIZE = 250; constexpr DOMHighResTimeStamp LONG_TASK_DURATION_THRESHOLD_MS = 50.0; @@ -101,15 +104,26 @@ class PerformanceEntryReporter { void reportLongTask(double startTime, double duration); + PerformanceResourceTiming reportResourceTiming( + const std::string& url, + DOMHighResTimeStamp fetchStart, + DOMHighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + DOMHighResTimeStamp responseStart, + DOMHighResTimeStamp responseEnd, + const std::optional& responseStatus); + private: std::unique_ptr observerRegistry_; mutable std::shared_mutex buffersMutex_; PerformanceEntryCircularBuffer eventBuffer_{EVENT_BUFFER_SIZE}; PerformanceEntryCircularBuffer longTaskBuffer_{LONG_TASK_BUFFER_SIZE}; + PerformanceEntryCircularBuffer resourceTimingBuffer_{ + RESOURCE_TIMING_BUFFER_SIZE}; PerformanceEntryKeyedBuffer markBuffer_; PerformanceEntryKeyedBuffer measureBuffer_; - PerformanceEntryKeyedBuffer resourceBuffer_; std::unordered_map eventCounts_; @@ -129,7 +143,7 @@ class PerformanceEntryReporter { case PerformanceEntryType::LONGTASK: return longTaskBuffer_; case PerformanceEntryType::RESOURCE: - return resourceBuffer_; + return resourceTimingBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } @@ -147,7 +161,7 @@ class PerformanceEntryReporter { case PerformanceEntryType::LONGTASK: return longTaskBuffer_; case PerformanceEntryType::RESOURCE: - return resourceBuffer_; + return resourceTimingBuffer_; case PerformanceEntryType::_NEXT: throw std::logic_error("Cannot get buffer for _NEXT entry type"); } diff --git a/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js b/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js index 99a0cbb4bfb9..6a635c0a37a9 100644 --- a/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/internals/RawPerformanceEntry.js @@ -77,6 +77,8 @@ export function rawToPerformanceEntryType( return 'event'; case RawPerformanceEntryTypeValues.LONGTASK: return 'longtask'; + case RawPerformanceEntryTypeValues.RESOURCE: + return 'resource'; default: throw new TypeError( `rawToPerformanceEntryType: unexpected performance entry type received: ${type}`, diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 9bc4825c0e1f..03c0606ae220 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -1706,7 +1706,10 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric + - React-featureflags - React-jsinspectorcdp + - React-performancetimeline + - React-timing - SocketRocket - React-jsinspectortracing (1000.0.0): - boost @@ -2599,7 +2602,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 569425f7cd2c3e005a17e5211843e541c11d6916 React-jsinspector: 885e8180e898f07e4d7df29e2681a89e69d736d3 React-jsinspectorcdp: 5fb266e5f23d3a2819ba848e9d4d0b6b00f95934 - React-jsinspectornetwork: 207422b56a7918e83c94c207570849f83ab9052a + React-jsinspectornetwork: 1655a81f3fe14789df41e063bd56dd130cc3562a React-jsinspectortracing: 80e9418ac67630c76f15ef06534087037a822330 React-jsitooling: 0c28fbc10441f8b63f4c6bf443cb36416500ce2b React-jsitracing: ce443686f52538d1033ce7db1e7d643e866262f0