Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Pod::Spec.new do |s|

add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-featureflags")
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <folly/dynamic.h>
#include <jsinspector-modern/cdp/CdpJson.h>
#endif
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/performance/timeline/PerformanceEntryReporter.h>

#ifdef REACT_NATIVE_DEBUGGER_ENABLED
#include <chrono>
Expand All @@ -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();
Expand Down Expand Up @@ -74,7 +76,23 @@ void NetworkReporter::reportRequestStart(
const std::string& requestId,
const RequestInfo& requestInfo,
int encodedDataLength,
const std::optional<ResponseInfo>& redirectResponse) const {
const std::optional<ResponseInfo>& redirectResponse) {
if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp();

// All builds: Annotate PerformanceResourceTiming metadata
{
std::lock_guard<std::mutex> 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()) {
Expand Down Expand Up @@ -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<std::mutex> 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()) {
Expand Down Expand Up @@ -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<std::mutex> 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()) {
Expand All @@ -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<std::mutex> 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()) {
Expand All @@ -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<std::mutex> 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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@

#include "NetworkTypes.h"

#include <react/timing/primitives.h>

#include <atomic>
#include <functional>
#include <mutex>
#include <string>

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

/**
* 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<DOMHighResTimeStamp> connectStart;
std::optional<DOMHighResTimeStamp> connectEnd;
std::optional<DOMHighResTimeStamp> responseStart;
std::optional<int> responseStatus;
};

/**
* [Experimental] An interface for reporting network events to the modern
* debugger server and Web Performance APIs.
Expand Down Expand Up @@ -67,7 +88,7 @@ class NetworkReporter {
const std::string& requestId,
const RequestInfo& requestInfo,
int encodedDataLength,
const std::optional<ResponseInfo>& redirectResponse) const;
const std::optional<ResponseInfo>& redirectResponse);

/**
* Report detailed timing info, such as DNS lookup, when a request has
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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_;
Expand All @@ -134,6 +154,9 @@ class NetworkReporter {
inline bool isDebuggingEnabledNoSync() const {
return debuggingEnabled_.load(std::memory_order_relaxed);
}

std::unordered_map<std::string, ResourceTimingData> perfTimingsBuffer_{};
std::mutex perfTimingsMutex_;
};

} // namespace facebook::react::jsinspector_modern
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ struct PerformanceResourceTiming : AbstractPerformanceEntry {
static constexpr PerformanceEntryType entryType =
PerformanceEntryType::RESOURCE;
/** Aligns with `startTime`. */
std::optional<DOMHighResTimeStamp> fetchStart;
std::optional<DOMHighResTimeStamp> requestStart;
DOMHighResTimeStamp fetchStart;
DOMHighResTimeStamp requestStart;
std::optional<DOMHighResTimeStamp> connectStart;
std::optional<DOMHighResTimeStamp> connectEnd;
std::optional<DOMHighResTimeStamp> responseStart;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ std::vector<PerformanceEntryType> getSupportedEntryTypesInternal() {
PerformanceEntryType::LONGTASK,
};

if (ReactNativeFeatureFlags::enableResourceTimingAPI()) {
supportedEntryTypes.emplace_back(PerformanceEntryType::RESOURCE);
}

return supportedEntryTypes;
}

Expand Down Expand Up @@ -289,6 +293,37 @@ void PerformanceEntryReporter::reportLongTask(
observerRegistry_->queuePerformanceEntry(entry);
}

PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming(
const std::string& url,
DOMHighResTimeStamp fetchStart,
DOMHighResTimeStamp requestStart,
std::optional<DOMHighResTimeStamp> connectStart,
std::optional<DOMHighResTimeStamp> connectEnd,
DOMHighResTimeStamp responseStart,
DOMHighResTimeStamp responseEnd,
const std::optional<int>& 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -101,15 +104,26 @@ class PerformanceEntryReporter {

void reportLongTask(double startTime, double duration);

PerformanceResourceTiming reportResourceTiming(
const std::string& url,
DOMHighResTimeStamp fetchStart,
DOMHighResTimeStamp requestStart,
std::optional<DOMHighResTimeStamp> connectStart,
std::optional<DOMHighResTimeStamp> connectEnd,
DOMHighResTimeStamp responseStart,
DOMHighResTimeStamp responseEnd,
const std::optional<int>& responseStatus);

private:
std::unique_ptr<PerformanceObserverRegistry> 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<std::string, uint32_t> eventCounts_;

Expand All @@ -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");
}
Expand All @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand Down
Loading
Loading