From dba8dbdeeb9bee5fe0922c24e687fe3647420ab7 Mon Sep 17 00:00:00 2001 From: Alex Hunt Date: Mon, 15 Sep 2025 07:45:32 -0700 Subject: [PATCH] Implement support for Network trace events (#53761) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53761 Updates `NetworkReporter` and `PerformanceEntryReporter` to populate (minimal) `"ResourceSendRequest"` and `"ResourceFinished"` events when a CDP performance trace is active. This allows the Chrome DevTools Performance panel to display the "Network" track. **Notes** - The trace events that Chrome requires need extra fields which aren't present on `PerformanceResourceTiming`, hence the new + optional `devtoolsRequestId`, `requestMethod`, `resourceType` params. We only populate these in debug builds. **Limitations** - We emit a *complete trace event set* within `reportResourceTiming`, implementing basic initial support in the Performance panel Network track. This means 1/ either all/no events are sent for a given request (rather than incrementally), 2/ we aren't yet handling failed/cancelled requests in this pipeline. Changelog: [Internal] Reviewed By: hoxyq Differential Revision: D82212362 fbshipit-source-id: 4c6d5d2510cc98ddc819a2778222b835411295c8 --- .../tracing/PerformanceTracer.cpp | 111 ++++++++++++++++++ .../tracing/PerformanceTracer.h | 53 ++++++++- .../jsinspector-modern/tracing/TraceEvent.h | 6 + .../tracing/TraceEventSerializer.cpp | 3 + .../react/networking/NetworkReporter.cpp | 17 ++- .../react/networking/NetworkReporter.h | 2 + .../timeline/PerformanceEntryReporter.cpp | 34 +++++- .../timeline/PerformanceEntryReporter.h | 10 +- 8 files changed, 231 insertions(+), 5 deletions(-) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index 80db2a1dce1..2aed5b2e751 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -232,6 +232,50 @@ void PerformanceTracer::reportEventLoopMicrotasks( }); } +void PerformanceTracer::reportResourceTiming( + const std::string& requestId, + const std::string& url, + HighResTimeStamp fetchStart, + HighResTimeStamp responseStart, + HighResTimeStamp responseEnd, + int statusCode, + const std::string& requestMethod, + const std::string& resourceType) { + if (!tracingAtomic_) { + return; + } + + std::lock_guard lock(mutex_); + if (!tracingAtomic_) { + return; + } + + enqueueEvent(PerformanceTracerResourceWillSendRequest{ + .requestId = requestId, + .start = fetchStart, + .threadId = getCurrentThreadId(), + }); + enqueueEvent(PerformanceTracerResourceSendRequest{ + .requestId = requestId, + .url = url, + .start = fetchStart, + .requestMethod = requestMethod, + .resourceType = resourceType, + .threadId = getCurrentThreadId(), + }); + enqueueEvent(PerformanceTracerResourceReceiveResponse{ + .requestId = requestId, + .start = responseStart, + .statusCode = statusCode, + .threadId = getCurrentThreadId(), + }); + enqueueEvent(PerformanceTracerResourceFinish{ + .requestId = requestId, + .start = responseEnd, + .threadId = getCurrentThreadId(), + }); +} + /* static */ TraceEvent PerformanceTracer::constructRuntimeProfileTraceEvent( RuntimeProfileId profileId, ProcessId processId, @@ -505,6 +549,73 @@ void PerformanceTracer::enqueueTraceEventsFromPerformanceTracerEvent( .args = folly::dynamic::object("data", std::move(data)), }); }, + [&](PerformanceTracerResourceWillSendRequest&& event) { + folly::dynamic data = + folly::dynamic::object("requestId", std::move(event.requestId)); + + events.emplace_back(TraceEvent{ + .name = "ResourceWillSendRequest", + .cat = "devtools.timeline", + .ph = 'I', + .ts = event.start, + .pid = processId_, + .s = 't', + .tid = event.threadId, + .args = folly::dynamic::object("data", std::move(data)), + }); + }, + [&](PerformanceTracerResourceSendRequest&& event) { + folly::dynamic data = + folly::dynamic::object("initiator", folly::dynamic::object())( + "renderBlocking", "non_blocking")( + "requestId", std::move(event.requestId))( + "requestMethod", std::move(event.requestMethod))( + "resourceType", std::move(event.resourceType))( + "url", std::move(event.url)); + + events.emplace_back(TraceEvent{ + .name = "ResourceSendRequest", + .cat = "devtools.timeline", + .ph = 'I', + .ts = event.start, + .pid = processId_, + .s = 't', + .tid = event.threadId, + .args = folly::dynamic::object("data", std::move(data)), + }); + }, + [&](PerformanceTracerResourceReceiveResponse&& event) { + folly::dynamic data = folly::dynamic::object("protocol", "h2")( + "requestId", std::move(event.requestId))( + "statusCode", event.statusCode)( + "timing", folly::dynamic::object()); + + events.emplace_back(TraceEvent{ + .name = "ResourceReceiveResponse", + .cat = "devtools.timeline", + .ph = 'I', + .ts = event.start, + .pid = processId_, + .s = 't', + .tid = event.threadId, + .args = folly::dynamic::object("data", std::move(data)), + }); + }, + [&](PerformanceTracerResourceFinish&& event) { + folly::dynamic data = folly::dynamic::object("didFail", false)( + "requestId", std::move(event.requestId)); + + events.emplace_back(TraceEvent{ + .name = "ResourceFinish", + .cat = "devtools.timeline", + .ph = 'I', + .ts = event.start, + .pid = processId_, + .s = 't', + .tid = event.threadId, + .args = folly::dynamic::object("data", std::move(data)), + }); + }, }, std::move(event)); } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index c1baaf50819..60faff48a0b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -108,6 +108,21 @@ class PerformanceTracer { */ void reportEventLoopMicrotasks(HighResTimeStamp start, HighResTimeStamp end); + /** + * Record a "ResourceSendRequest"/"ResourceFinish" event pair - a labelled + * duration in the Performance timeline Network track. If not currently + * tracing, this is a no-op. + */ + void reportResourceTiming( + const std::string& requestId, + const std::string& url, + HighResTimeStamp fetchStart, + HighResTimeStamp responseStart, + HighResTimeStamp responseEnd, + int statusCode, + const std::string& requestMethod, + const std::string& resourceType); + /** * Creates "Profile" Trace Event. * @@ -181,12 +196,48 @@ class PerformanceTracer { HighResTimeStamp createdAt = HighResTimeStamp::now(); }; + struct PerformanceTracerResourceWillSendRequest { + std::string requestId; + HighResTimeStamp start; + ThreadId threadId; + HighResTimeStamp createdAt = HighResTimeStamp::now(); + }; + + struct PerformanceTracerResourceSendRequest { + std::string requestId; + std::string url; + HighResTimeStamp start; + std::string requestMethod; + std::string resourceType; + ThreadId threadId; + HighResTimeStamp createdAt = HighResTimeStamp::now(); + }; + + struct PerformanceTracerResourceFinish { + std::string requestId; + HighResTimeStamp start; + ThreadId threadId; + HighResTimeStamp createdAt = HighResTimeStamp::now(); + }; + + struct PerformanceTracerResourceReceiveResponse { + std::string requestId; + HighResTimeStamp start; + int statusCode; + ThreadId threadId; + HighResTimeStamp createdAt = HighResTimeStamp::now(); + }; + using PerformanceTracerEvent = std::variant< PerformanceTracerEventTimeStamp, PerformanceTracerEventEventLoopTask, PerformanceTracerEventEventLoopMicrotask, PerformanceTracerEventMark, - PerformanceTracerEventMeasure>; + PerformanceTracerEventMeasure, + PerformanceTracerResourceWillSendRequest, + PerformanceTracerResourceSendRequest, + PerformanceTracerResourceReceiveResponse, + PerformanceTracerResourceFinish>; #pragma mark - Private fields and methods diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h index d784e98d1b5..ec95ef048f2 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h @@ -55,6 +55,12 @@ struct TraceEvent { /** The ID for the process that output this event. */ ProcessId pid; + /** + * The scope of the event, either global (g), process (p), or thread (t). + * Only applicable to instant events ("ph": "i"). + */ + std::optional s; + /** The ID for the thread that output this event. */ ThreadId tid; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.cpp index 36eb612c9fd..f8181cf3902 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventSerializer.cpp @@ -26,6 +26,9 @@ namespace facebook::react::jsinspector_modern::tracing { result["ph"] = std::string(1, event.ph); result["ts"] = highResTimeStampToTracingClockTimeStamp(event.ts); result["pid"] = event.pid; + if (event.s.has_value()) { + result["s"] = std::string(1, event.s.value()); + } result["tid"] = event.tid; result["args"] = std::move(event.args); if (event.dur.has_value()) { diff --git a/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp b/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp index 7ea6572f5b0..16951912db4 100644 --- a/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp +++ b/packages/react-native/ReactCommon/react/networking/NetworkReporter.cpp @@ -8,7 +8,9 @@ #include "NetworkReporter.h" #ifdef REACT_NATIVE_DEBUGGER_ENABLED -#include "jsinspector-modern/network/NetworkHandler.h" +#include +#include +#include #endif #include #include @@ -43,6 +45,7 @@ void NetworkReporter::reportRequestStart( requestId, ResourceTimingData{ .url = requestInfo.url, + .requestMethod = requestInfo.httpMethod, .fetchStart = now, .requestStart = now, }); @@ -108,6 +111,13 @@ void NetworkReporter::reportResponseStart( it->second.connectEnd = now; it->second.responseStart = now; it->second.responseStatus = responseInfo.statusCode; +#ifdef REACT_NATIVE_DEBUGGER_ENABLED + // Debug build: Compute additional fields to send in CDP trace events + it->second.resourceType = + jsinspector_modern::cdp::network::resourceTypeFromMimeType( + jsinspector_modern::mimeTypeFromHeaders( + responseInfo.headers.value_or(Headers{}))); +#endif } } } @@ -155,7 +165,10 @@ void NetworkReporter::reportResponseEnd( eventData.connectEnd.value_or(now), eventData.responseStart.value_or(now), now, - eventData.responseStatus); + eventData.responseStatus, + requestId, + eventData.requestMethod, + eventData.resourceType); perfTimingsBuffer_.erase(requestId); } } diff --git a/packages/react-native/ReactCommon/react/networking/NetworkReporter.h b/packages/react-native/ReactCommon/react/networking/NetworkReporter.h index 3e5c2c227ab..a3441771707 100644 --- a/packages/react-native/ReactCommon/react/networking/NetworkReporter.h +++ b/packages/react-native/ReactCommon/react/networking/NetworkReporter.h @@ -28,6 +28,8 @@ namespace facebook::react { */ struct ResourceTimingData { std::string url; + std::string requestMethod; + std::optional resourceType; HighResTimeStamp fetchStart; HighResTimeStamp requestStart; std::optional connectStart; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index d4c6423b231..117f966dbbc 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -307,7 +307,10 @@ void PerformanceEntryReporter::reportResourceTiming( std::optional connectEnd, HighResTimeStamp responseStart, HighResTimeStamp responseEnd, - const std::optional& responseStatus) { + const std::optional& responseStatus, + const std::optional& devtoolsRequestId, + const std::optional& requestMethod, + const std::optional& resourceType) { const auto entry = PerformanceResourceTiming{ {.name = url, .startTime = fetchStart}, fetchStart, @@ -319,6 +322,8 @@ void PerformanceEntryReporter::reportResourceTiming( responseStatus, }; + traceResourceTiming(entry, devtoolsRequestId, requestMethod, resourceType); + // Add to buffers & notify observers { std::unique_lock lock(buffersMutex_); @@ -370,4 +375,31 @@ void PerformanceEntryReporter::traceMeasure( } } +void PerformanceEntryReporter::traceResourceTiming( + const PerformanceResourceTiming& entry, + const std::optional& devtoolsRequestId, + const std::optional& requestMethod, + const std::optional& resourceType) const { + if (!entry.responseStart.has_value() || !entry.responseEnd.has_value() || + !entry.responseStatus.has_value() || !devtoolsRequestId.has_value() || + !requestMethod.has_value() || !resourceType.has_value()) { + return; + } + + auto& performanceTracer = + jsinspector_modern::tracing::PerformanceTracer::getInstance(); + + if (performanceTracer.isTracing()) { + performanceTracer.reportResourceTiming( + *devtoolsRequestId, + entry.name, + entry.fetchStart, + *entry.responseStart, + *entry.responseEnd, + *entry.responseStatus, + *requestMethod, + *resourceType); + } +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index bae4f093922..8ae2af0d24c 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -118,7 +118,10 @@ class PerformanceEntryReporter { std::optional connectEnd, HighResTimeStamp responseStart, HighResTimeStamp responseEnd, - const std::optional& responseStatus); + const std::optional& responseStatus, + const std::optional& devtoolsRequestId, + const std::optional& requestMethod, + const std::optional& resourceType); private: std::unique_ptr observerRegistry_; @@ -182,6 +185,11 @@ class PerformanceEntryReporter { void traceMeasure( const PerformanceMeasure& entry, UserTimingDetailProvider&& detailProvider) const; + void traceResourceTiming( + const PerformanceResourceTiming& entry, + const std::optional& devtoolsRequestId, + const std::optional& requestMethod, + const std::optional& resourceType) const; }; } // namespace facebook::react