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
This commit is contained in:
Alex Hunt
2025-09-15 07:45:32 -07:00
committed by Facebook GitHub Bot
parent 0caf8e70d5
commit dba8dbdeeb
8 changed files with 231 additions and 5 deletions
@@ -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<std::mutex> 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));
}
@@ -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
@@ -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<char> s;
/** The ID for the thread that output this event. */
ThreadId tid;
@@ -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()) {
@@ -8,7 +8,9 @@
#include "NetworkReporter.h"
#ifdef REACT_NATIVE_DEBUGGER_ENABLED
#include "jsinspector-modern/network/NetworkHandler.h"
#include <jsinspector-modern/network/CdpNetwork.h>
#include <jsinspector-modern/network/HttpUtils.h>
#include <jsinspector-modern/network/NetworkHandler.h>
#endif
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/performance/timeline/PerformanceEntryReporter.h>
@@ -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);
}
}
@@ -28,6 +28,8 @@ namespace facebook::react {
*/
struct ResourceTimingData {
std::string url;
std::string requestMethod;
std::optional<std::string> resourceType;
HighResTimeStamp fetchStart;
HighResTimeStamp requestStart;
std::optional<HighResTimeStamp> connectStart;
@@ -307,7 +307,10 @@ void PerformanceEntryReporter::reportResourceTiming(
std::optional<HighResTimeStamp> connectEnd,
HighResTimeStamp responseStart,
HighResTimeStamp responseEnd,
const std::optional<int>& responseStatus) {
const std::optional<int>& responseStatus,
const std::optional<std::string>& devtoolsRequestId,
const std::optional<std::string>& requestMethod,
const std::optional<std::string>& 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<std::string>& devtoolsRequestId,
const std::optional<std::string>& requestMethod,
const std::optional<std::string>& 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
@@ -118,7 +118,10 @@ class PerformanceEntryReporter {
std::optional<HighResTimeStamp> connectEnd,
HighResTimeStamp responseStart,
HighResTimeStamp responseEnd,
const std::optional<int>& responseStatus);
const std::optional<int>& responseStatus,
const std::optional<std::string>& devtoolsRequestId,
const std::optional<std::string>& requestMethod,
const std::optional<std::string>& resourceType);
private:
std::unique_ptr<PerformanceObserverRegistry> observerRegistry_;
@@ -182,6 +185,11 @@ class PerformanceEntryReporter {
void traceMeasure(
const PerformanceMeasure& entry,
UserTimingDetailProvider&& detailProvider) const;
void traceResourceTiming(
const PerformanceResourceTiming& entry,
const std::optional<std::string>& devtoolsRequestId,
const std::optional<std::string>& requestMethod,
const std::optional<std::string>& resourceType) const;
};
} // namespace facebook::react