diff --git a/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp b/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp index eeca4b3c92966a..ef6a44c0f12b97 100644 --- a/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp +++ b/packages/react-native/ReactCommon/cxxreact/JSExecutor.cpp @@ -10,10 +10,8 @@ #include "RAMBundleRegistry.h" #include -#include #include -#include namespace facebook::react { @@ -29,8 +27,8 @@ std::string JSExecutor::getSyntheticBundlePath( return buffer.data(); } -double JSExecutor::performanceNow() { - return chronoToDOMHighResTimeStamp(std::chrono::steady_clock::now()); +HighResTimeStamp JSExecutor::performanceNow() { + return HighResTimeStamp::now(); } jsinspector_modern::RuntimeTargetDelegate& diff --git a/packages/react-native/ReactCommon/cxxreact/JSExecutor.h b/packages/react-native/ReactCommon/cxxreact/JSExecutor.h index 469f4ca6fd73d5..775e3971f58284 100644 --- a/packages/react-native/ReactCommon/cxxreact/JSExecutor.h +++ b/packages/react-native/ReactCommon/cxxreact/JSExecutor.h @@ -14,6 +14,7 @@ #include #include #include +#include #ifndef RN_EXPORT #define RN_EXPORT __attribute__((visibility("default"))) @@ -138,7 +139,7 @@ class RN_EXPORT JSExecutor { uint32_t bundleId, const std::string& bundlePath); - static double performanceNow(); + static HighResTimeStamp performanceNow(); /** * Get a reference to the \c RuntimeTargetDelegate owned (or implemented) by diff --git a/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp b/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp index 6b226006768696..39be0f0f055a2f 100644 --- a/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp +++ b/packages/react-native/ReactCommon/jsiexecutor/jsireact/JSIExecutor.cpp @@ -556,7 +556,9 @@ void bindNativePerformanceNow(Runtime& runtime) { [](jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* args, - size_t count) { return Value(JSExecutor::performanceNow()); })); + size_t /*count*/) { + return JSExecutor::performanceNow().toDOMHighResTimeStamp(); + })); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp index f00c175fad197c..fb0f425a63bd59 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTarget.cpp @@ -179,7 +179,7 @@ RuntimeTargetController::collectSamplingProfile() { void RuntimeTarget::registerForTracing() { jsExecutor_([](auto& /*runtime*/) { - PerformanceTracer::getInstance().reportJavaScriptThread(); + tracing::PerformanceTracer::getInstance().reportJavaScriptThread(); }); } diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp index 28407ee865db82..0a3e365fd3d73f 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.cpp @@ -44,7 +44,7 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { } bool correctlyStartedPerformanceTracer = - PerformanceTracer::getInstance().startTracing(); + tracing::PerformanceTracer::getInstance().startTracing(); if (!correctlyStartedPerformanceTracer) { frontendChannel_(cdp::jsonError( @@ -56,7 +56,7 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { } instanceAgent_->startTracing(); - instanceTracingStartTimestamp_ = std::chrono::steady_clock::now(); + instanceTracingStartTimestamp_ = HighResTimeStamp::now(); frontendChannel_(cdp::jsonResult(req.id)); return true; @@ -73,7 +73,8 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) { instanceAgent_->stopTracing(); - PerformanceTracer& performanceTracer = PerformanceTracer::getInstance(); + tracing::PerformanceTracer& performanceTracer = + tracing::PerformanceTracer::getInstance(); bool correctlyStopped = performanceTracer.stopTracing(); if (!correctlyStopped) { frontendChannel_(cdp::jsonError( diff --git a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h index cd5792fef5f320..95ed57efc4587b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/TracingAgent.h @@ -11,6 +11,8 @@ #include "InstanceAgent.h" #include +#include +#include namespace facebook::react::jsinspector_modern { @@ -54,9 +56,10 @@ class TracingAgent { /** * Timestamp of when we started tracing of an Instance, will be used as a - * a start of JavaScript samples recording. + * a start of JavaScript samples recording and as a time origin for the events + * in this trace. */ - std::chrono::steady_clock::time_point instanceTracingStartTimestamp_; + HighResTimeStamp instanceTracingStartTimestamp_; }; } // namespace facebook::react::jsinspector_modern diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp index 1eb6e19f20f6d6..9f207a0a23620b 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.cpp @@ -78,7 +78,7 @@ void NetworkReporter::reportRequestStart( int encodedDataLength, const std::optional& redirectResponse) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -127,7 +127,7 @@ void NetworkReporter::reportRequestStart( void NetworkReporter::reportConnectionTiming(const std::string& requestId) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -168,7 +168,7 @@ void NetworkReporter::reportResponseStart( const ResponseInfo& responseInfo, int encodedDataLength) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -205,7 +205,7 @@ void NetworkReporter::reportResponseStart( void NetworkReporter::reportDataReceived(const std::string& requestId) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Annotate PerformanceResourceTiming metadata { @@ -233,7 +233,7 @@ void NetworkReporter::reportResponseEnd( const std::string& requestId, int encodedDataLength) { if (ReactNativeFeatureFlags::enableResourceTimingAPI()) { - double now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); + auto now = PerformanceEntryReporter::getInstance()->getCurrentTimeStamp(); // All builds: Report PerformanceResourceTiming event { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h index 4227deca3df0b8..90701c412bfe47 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/NetworkReporter.h @@ -35,11 +35,11 @@ using FrontendChannel = std::function; */ struct ResourceTimingData { std::string url; - DOMHighResTimeStamp fetchStart; - DOMHighResTimeStamp requestStart; - std::optional connectStart; - std::optional connectEnd; - std::optional responseStart; + HighResTimeStamp fetchStart; + HighResTimeStamp requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; std::optional responseStatus; }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt b/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt index 282c84ecc691dc..36ea342318c5d6 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/CMakeLists.txt @@ -19,6 +19,7 @@ target_include_directories(jsinspector_tracing PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(jsinspector_tracing folly_runtime oscompat + react_timing ) target_compile_reactnative_options(jsinspector_tracing PRIVATE) target_compile_options(jsinspector_tracing PRIVATE -Wpedantic) diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp index d2c44ae3263eed..535fe2af5f1444 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.cpp @@ -8,41 +8,28 @@ #include "EventLoopReporter.h" #if defined(REACT_NATIVE_DEBUGGER_ENABLED) +#include #include "PerformanceTracer.h" #endif namespace facebook::react::jsinspector_modern::tracing { #if defined(REACT_NATIVE_DEBUGGER_ENABLED) -namespace { - -inline uint64_t formatTimePointToUnixTimestamp( - std::chrono::steady_clock::time_point timestamp) { - return std::chrono::duration_cast( - timestamp.time_since_epoch()) - .count(); -} - -} // namespace EventLoopReporter::EventLoopReporter(EventLoopPhase phase) - : startTimestamp_(std::chrono::steady_clock::now()), phase_(phase) {} + : startTimestamp_(HighResTimeStamp::now()), phase_(phase) {} EventLoopReporter::~EventLoopReporter() { PerformanceTracer& performanceTracer = PerformanceTracer::getInstance(); if (performanceTracer.isTracing()) { - auto end = std::chrono::steady_clock::now(); + auto end = HighResTimeStamp::now(); switch (phase_) { case EventLoopPhase::Task: - performanceTracer.reportEventLoopTask( - formatTimePointToUnixTimestamp(startTimestamp_), - formatTimePointToUnixTimestamp(end)); + performanceTracer.reportEventLoopTask(startTimestamp_, end); break; case EventLoopPhase::Microtasks: - performanceTracer.reportEventLoopMicrotasks( - formatTimePointToUnixTimestamp(startTimestamp_), - formatTimePointToUnixTimestamp(end)); + performanceTracer.reportEventLoopMicrotasks(startTimestamp_, end); break; default: diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h index bff47ca4155d6e..211fe139cba76d 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/EventLoopReporter.h @@ -7,7 +7,9 @@ #pragma once -#include +#if defined(REACT_NATIVE_DEBUGGER_ENABLED) +#include +#endif namespace facebook::react::jsinspector_modern::tracing { @@ -29,7 +31,7 @@ struct EventLoopReporter { private: #if defined(REACT_NATIVE_DEBUGGER_ENABLED) - std::chrono::steady_clock::time_point startTimestamp_; + HighResTimeStamp startTimestamp_; EventLoopPhase phase_; #endif }; diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp index 6f958ea3a5c14c..837e484efb0139 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.cpp @@ -6,25 +6,17 @@ */ #include "PerformanceTracer.h" +#include "Timing.h" #include +#include #include #include #include -namespace facebook::react::jsinspector_modern { - -namespace { - -uint64_t getUnixTimestampOfNow() { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count(); -} - -} // namespace +namespace facebook::react::jsinspector_modern::tracing { PerformanceTracer& PerformanceTracer::getInstance() { static PerformanceTracer tracer; @@ -52,7 +44,7 @@ bool PerformanceTracer::startTracing() { .name = "TracingStartedInPage", .cat = "disabled-by-default-devtools.timeline", .ph = 'I', - .ts = getUnixTimestampOfNow(), + .ts = HighResTimeStamp::now(), .pid = processId_, .tid = oscompat::getCurrentThreadId(), .args = folly::dynamic::object("data", folly::dynamic::object()), @@ -78,7 +70,7 @@ bool PerformanceTracer::stopTracing() { .name = "ReactNative-TracingStopped", .cat = "disabled-by-default-devtools.timeline", .ph = 'I', - .ts = getUnixTimestampOfNow(), + .ts = HighResTimeStamp::now(), .pid = processId_, .tid = oscompat::getCurrentThreadId(), }); @@ -117,7 +109,7 @@ void PerformanceTracer::collectEvents( void PerformanceTracer::reportMark( const std::string_view& name, - uint64_t start) { + HighResTimeStamp start) { if (!tracing_) { return; } @@ -139,8 +131,8 @@ void PerformanceTracer::reportMark( void PerformanceTracer::reportMeasure( const std::string_view& name, - uint64_t start, - uint64_t duration, + HighResTimeStamp start, + HighResDuration duration, const std::optional& trackMetadata) { if (!tracing_) { return; @@ -197,7 +189,7 @@ void PerformanceTracer::reportProcess(uint64_t id, const std::string& name) { .name = "process_name", .cat = "__metadata", .ph = 'M', - .ts = 0, + .ts = TRACING_TIME_ORIGIN, .pid = id, .tid = 0, .args = folly::dynamic::object("name", name), @@ -222,7 +214,7 @@ void PerformanceTracer::reportThread(uint64_t id, const std::string& name) { .name = "thread_name", .cat = "__metadata", .ph = 'M', - .ts = 0, + .ts = TRACING_TIME_ORIGIN, .pid = processId_, .tid = id, .args = folly::dynamic::object("name", name), @@ -237,13 +229,15 @@ void PerformanceTracer::reportThread(uint64_t id, const std::string& name) { .name = "ReactNative-ThreadRegistered", .cat = "disabled-by-default-devtools.timeline", .ph = 'I', - .ts = 0, + .ts = TRACING_TIME_ORIGIN, .pid = processId_, .tid = id, }); } -void PerformanceTracer::reportEventLoopTask(uint64_t start, uint64_t end) { +void PerformanceTracer::reportEventLoopTask( + HighResTimeStamp start, + HighResTimeStamp end) { if (!tracing_) { return; } @@ -265,8 +259,8 @@ void PerformanceTracer::reportEventLoopTask(uint64_t start, uint64_t end) { } void PerformanceTracer::reportEventLoopMicrotasks( - uint64_t start, - uint64_t end) { + HighResTimeStamp start, + HighResTimeStamp end) { if (!tracing_) { return; } @@ -290,7 +284,7 @@ void PerformanceTracer::reportEventLoopMicrotasks( folly::dynamic PerformanceTracer::getSerializedRuntimeProfileTraceEvent( uint64_t threadId, uint16_t profileId, - uint64_t eventUnixTimestamp) { + HighResTimeStamp profileTimestamp) { // CDT prioritizes event timestamp over startTime metadata field. // https://fburl.com/lo764pf4 return serializeTraceEvent(TraceEvent{ @@ -298,25 +292,28 @@ folly::dynamic PerformanceTracer::getSerializedRuntimeProfileTraceEvent( .name = "Profile", .cat = "disabled-by-default-v8.cpu_profiler", .ph = 'P', - .ts = eventUnixTimestamp, + .ts = profileTimestamp, .pid = processId_, .tid = threadId, .args = folly::dynamic::object( - "data", folly ::dynamic::object("startTime", eventUnixTimestamp)), + "data", + folly ::dynamic::object( + "startTime", + highResTimeStampToTracingClockTimeStamp(profileTimestamp))), }); } folly::dynamic PerformanceTracer::getSerializedRuntimeProfileChunkTraceEvent( uint16_t profileId, uint64_t threadId, - uint64_t eventUnixTimestamp, + HighResTimeStamp chunkTimestamp, const tracing::TraceEventProfileChunk& traceEventProfileChunk) { return serializeTraceEvent(TraceEvent{ .id = profileId, .name = "ProfileChunk", .cat = "disabled-by-default-v8.cpu_profiler", .ph = 'P', - .ts = eventUnixTimestamp, + .ts = chunkTimestamp, .pid = processId_, .tid = threadId, .args = @@ -336,15 +333,15 @@ folly::dynamic PerformanceTracer::serializeTraceEvent( result["name"] = event.name; result["cat"] = event.cat; result["ph"] = std::string(1, event.ph); - result["ts"] = event.ts; + result["ts"] = highResTimeStampToTracingClockTimeStamp(event.ts); result["pid"] = event.pid; result["tid"] = event.tid; result["args"] = event.args; if (event.dur.has_value()) { - result["dur"] = event.dur.value(); + result["dur"] = highResDurationToTracingClockDuration(event.dur.value()); } return result; } -} // namespace facebook::react::jsinspector_modern +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h index 31a06674ac8f08..c01ff8b63f601c 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/PerformanceTracer.h @@ -11,14 +11,17 @@ #include "TraceEvent.h" #include "TraceEventProfile.h" -#include +#include + +#include +#include #include #include #include #include -namespace facebook::react::jsinspector_modern { +namespace facebook::react::jsinspector_modern::tracing { // TODO: Review how this API is integrated into jsinspector_modern (singleton // design is copied from earlier FuseboxTracer prototype). @@ -65,7 +68,7 @@ class PerformanceTracer { * * See https://w3c.github.io/user-timing/#mark-method. */ - void reportMark(const std::string_view& name, uint64_t start); + void reportMark(const std::string_view& name, HighResTimeStamp start); /** * Record a `Performance.measure()` event - a labelled duration. If not @@ -75,8 +78,8 @@ class PerformanceTracer { */ void reportMeasure( const std::string_view& name, - uint64_t start, - uint64_t duration, + HighResTimeStamp start, + HighResDuration duration, const std::optional& trackMetadata); /** @@ -99,13 +102,13 @@ class PerformanceTracer { * Record an Event Loop tick, which will be represented as an Event Loop task * on a timeline view and grouped with JavaScript samples. */ - void reportEventLoopTask(uint64_t start, uint64_t end); + void reportEventLoopTask(HighResTimeStamp start, HighResTimeStamp end); /** * Record Microtasks phase of the Event Loop tick. Will be represented as a * "Run Microtasks" block under a task. */ - void reportEventLoopMicrotasks(uint64_t start, uint64_t end); + void reportEventLoopMicrotasks(HighResTimeStamp start, HighResTimeStamp end); /** * Create and serialize Profile Trace Event. @@ -114,7 +117,7 @@ class PerformanceTracer { folly::dynamic getSerializedRuntimeProfileTraceEvent( uint64_t threadId, uint16_t profileId, - uint64_t eventUnixTimestamp); + HighResTimeStamp profileTimestamp); /** * Create and serialize ProfileChunk Trace Event. @@ -123,8 +126,8 @@ class PerformanceTracer { folly::dynamic getSerializedRuntimeProfileChunkTraceEvent( uint16_t profileId, uint64_t threadId, - uint64_t eventUnixTimestamp, - const tracing::TraceEventProfileChunk& traceEventProfileChunk); + HighResTimeStamp chunkTimestamp, + const TraceEventProfileChunk& traceEventProfileChunk); private: PerformanceTracer(); @@ -135,10 +138,11 @@ class PerformanceTracer { folly::dynamic serializeTraceEvent(const TraceEvent& event) const; bool tracing_{false}; + uint64_t processId_; uint32_t performanceMeasureCount_{0}; std::vector buffer_; std::mutex mutex_; }; -} // namespace facebook::react::jsinspector_modern +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec b/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec index 9df0699bc78735..4765a6b32f4d71 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec @@ -47,6 +47,7 @@ Pod::Spec.new do |s| end s.dependency "React-oscompat" + s.dependency "React-timing" add_rn_third_party_dependencies(s) end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp index 27e44039e4000d..6e181b5e0b741f 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.cpp @@ -14,6 +14,17 @@ namespace facebook::react::jsinspector_modern::tracing { namespace { +// To capture samples timestamps Hermes is using steady_clock and returns +// them in microseconds granularity since epoch. In the future we might want to +// update Hermes to return timestamps in chrono type. +HighResTimeStamp getHighResTimeStampForSample( + const RuntimeSamplingProfile::Sample& sample) { + auto microsecondsSinceSteadyClockEpoch = sample.getTimestamp(); + auto chronoTimePoint = std::chrono::steady_clock::time_point( + std::chrono::microseconds(microsecondsSinceSteadyClockEpoch)); + return HighResTimeStamp::fromChronoSteadyClockTimePoint(chronoTimePoint); +} + // Right now we only emit single Profile. We might revisit this decision in the // future, once we support multiple VMs being sampled at the same time. constexpr uint16_t PROFILE_ID = 1; @@ -28,13 +39,6 @@ constexpr std::string_view ROOT_FRAME_NAME = "(root)"; constexpr std::string_view IDLE_FRAME_NAME = "(idle)"; constexpr std::string_view PROGRAM_FRAME_NAME = "(program)"; -uint64_t formatTimePointToUnixTimestamp( - std::chrono::steady_clock::time_point timestamp) { - return std::chrono::duration_cast( - timestamp.time_since_epoch()) - .count(); -} - TraceEventProfileChunk::CPUProfile::Node convertToTraceEventProfileNode( const ProfileTreeNode& node) { const RuntimeSamplingProfile::SampleCallStackFrame& callFrame = @@ -91,10 +95,10 @@ class ProfileTreeRootNode : public ProfileTreeNode { void RuntimeSamplingProfileTraceEventSerializer::sendProfileTraceEvent( uint64_t threadId, uint16_t profileId, - uint64_t profileStartUnixTimestamp) const { + HighResTimeStamp profileStartTimestamp) const { folly::dynamic serializedTraceEvent = performanceTracer_.getSerializedRuntimeProfileTraceEvent( - threadId, profileId, profileStartUnixTimestamp); + threadId, profileId, profileStartTimestamp); notificationCallback_(folly::dynamic::array(serializedTraceEvent)); } @@ -102,7 +106,7 @@ void RuntimeSamplingProfileTraceEventSerializer::sendProfileTraceEvent( void RuntimeSamplingProfileTraceEventSerializer::chunkEmptySample( ProfileChunk& chunk, uint32_t idleNodeId, - long long samplesTimeDelta) { + HighResDuration samplesTimeDelta) { chunk.samples.push_back(idleNodeId); chunk.timeDeltas.push_back(samplesTimeDelta); } @@ -139,7 +143,7 @@ void RuntimeSamplingProfileTraceEventSerializer::processCallStack( ProfileChunk& chunk, ProfileTreeNode& rootNode, uint32_t idleNodeId, - long long samplesTimeDelta, + HighResDuration samplesTimeDelta, NodeIdGenerator& nodeIdGenerator) { if (callStack.empty()) { chunkEmptySample(chunk, idleNodeId, samplesTimeDelta); @@ -183,7 +187,7 @@ void RuntimeSamplingProfileTraceEventSerializer:: void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( const RuntimeSamplingProfile& profile, - std::chrono::steady_clock::time_point tracingStartTime) { + HighResTimeStamp tracingStartTime) { const std::vector& samples = profile.getSamples(); if (samples.empty()) { @@ -191,18 +195,15 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( } uint64_t firstChunkThreadId = samples.front().getThreadId(); - uint64_t tracingStartUnixTimestamp = - formatTimePointToUnixTimestamp(tracingStartTime); - uint64_t previousSampleUnixTimestamp = tracingStartUnixTimestamp; - uint64_t currentChunkUnixTimestamp = tracingStartUnixTimestamp; + HighResTimeStamp previousSampleTimestamp = tracingStartTime; + HighResTimeStamp currentChunkTimestamp = tracingStartTime; - sendProfileTraceEvent( - firstChunkThreadId, PROFILE_ID, tracingStartUnixTimestamp); + sendProfileTraceEvent(firstChunkThreadId, PROFILE_ID, tracingStartTime); // There could be any number of new nodes in this chunk. Empty if all nodes // are already emitted in previous chunks. ProfileChunk chunk{ - profileChunkSize_, firstChunkThreadId, currentChunkUnixTimestamp}; + profileChunkSize_, firstChunkThreadId, currentChunkTimestamp}; NodeIdGenerator nodeIdGenerator{}; @@ -224,7 +225,7 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( for (const auto& sample : samples) { uint64_t currentSampleThreadId = sample.getThreadId(); - long long currentSampleUnixTimestamp = sample.getTimestamp(); + auto currentSampleTimestamp = getHighResTimeStampForSample(sample); // We should not attempt to merge samples from different threads. // From past observations, this only happens for GC nodes. @@ -233,7 +234,7 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( if (currentSampleThreadId != chunk.threadId || chunk.isFull()) { bufferProfileChunkTraceEvent(chunk, PROFILE_ID); chunk = ProfileChunk{ - profileChunkSize_, currentSampleThreadId, currentChunkUnixTimestamp}; + profileChunkSize_, currentSampleThreadId, currentChunkTimestamp}; } if (traceEventBuffer_.size() == traceEventChunkSize_) { @@ -245,10 +246,10 @@ void RuntimeSamplingProfileTraceEventSerializer::serializeAndNotify( chunk, rootNode, idleNodeId, - currentSampleUnixTimestamp - previousSampleUnixTimestamp, + currentSampleTimestamp - previousSampleTimestamp, nodeIdGenerator); - previousSampleUnixTimestamp = currentSampleUnixTimestamp; + previousSampleTimestamp = currentSampleTimestamp; } if (!chunk.isEmpty()) { diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h index 6f4cffa78a5b58..620d42df119024 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h @@ -11,6 +11,8 @@ #include "ProfileTreeNode.h" #include "RuntimeSamplingProfile.h" +#include + namespace facebook::react::jsinspector_modern::tracing { namespace { @@ -36,7 +38,7 @@ class RuntimeSamplingProfileTraceEventSerializer { ProfileChunk( uint16_t chunkSize, uint64_t chunkThreadId, - uint64_t chunkTimestamp) + HighResTimeStamp chunkTimestamp) : size(chunkSize), threadId(chunkThreadId), timestamp(chunkTimestamp) { samples.reserve(size); timeDeltas.reserve(size); @@ -52,10 +54,10 @@ class RuntimeSamplingProfileTraceEventSerializer { std::vector nodes; std::vector samples; - std::vector timeDeltas; + std::vector timeDeltas; uint16_t size; uint64_t threadId; - uint64_t timestamp; + HighResTimeStamp timestamp; }; public: @@ -89,7 +91,7 @@ class RuntimeSamplingProfileTraceEventSerializer { */ void serializeAndNotify( const RuntimeSamplingProfile& profile, - std::chrono::steady_clock::time_point tracingStartTime); + HighResTimeStamp tracingStartTime); private: /** @@ -102,7 +104,7 @@ class RuntimeSamplingProfileTraceEventSerializer { void sendProfileTraceEvent( uint64_t threadId, uint16_t profileId, - uint64_t profileStartUnixTimestamp) const; + HighResTimeStamp profileStartTimestamp) const; /** * Encapsulates logic for processing the empty sample, when the VM was idling. @@ -114,7 +116,7 @@ class RuntimeSamplingProfileTraceEventSerializer { void chunkEmptySample( ProfileChunk& chunk, uint32_t idleNodeId, - long long samplesTimeDelta); + HighResDuration samplesTimeDelta); /** * Records ProfileChunk as a "ProfileChunk" Trace Event in traceEventBuffer_. @@ -143,7 +145,7 @@ class RuntimeSamplingProfileTraceEventSerializer { ProfileChunk& chunk, ProfileTreeNode& rootNode, uint32_t idleNodeId, - long long samplesTimeDelta, + HighResDuration samplesTimeDelta, NodeIdGenerator& nodeIdGenerator); /** diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/Timing.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/Timing.h new file mode 100644 index 00000000000000..bda8f65df22d14 --- /dev/null +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/Timing.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include + +namespace facebook::react::jsinspector_modern::tracing { + +// The Tracing Clock time origin is the steady_clock epoch. This is mostly done +// to replicate Chromium's behavior, but also saves us from aligning custom +// DOMHighResTimeStamps that can be specified in performance.mark / +// performance.measure calls: these should not extend the timeline window, this +// is the current approach in Chromium. +constexpr HighResTimeStamp TRACING_TIME_ORIGIN = + HighResTimeStamp::fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point()); + +// Tracing timestamps are represented a time value in microseconds since +// arbitrary time origin (epoch) with no fractional part. +inline uint64_t highResTimeStampToTracingClockTimeStamp( + HighResTimeStamp timestamp) { + assert( + timestamp >= TRACING_TIME_ORIGIN && + "Provided timestamp is before time origin"); + auto duration = timestamp - TRACING_TIME_ORIGIN; + return static_cast( + static_cast(duration.toNanoseconds()) / 1e3); +} + +inline int64_t highResDurationToTracingClockDuration(HighResDuration duration) { + return static_cast( + static_cast(duration.toNanoseconds()) / 1e3); +} + +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h index 8c1a930548ecf0..fd961d90d27a0f 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEvent.h @@ -7,11 +7,11 @@ #pragma once -#include +#include -namespace facebook::react::jsinspector_modern { +#include -namespace { +namespace facebook::react::jsinspector_modern::tracing { /** * A trace event to send to the debugger frontend, as defined by the Trace Event @@ -42,7 +42,7 @@ struct TraceEvent { char ph; /** The tracing clock timestamp of the event, in microseconds (µs). */ - uint64_t ts; + HighResTimeStamp ts; /** The process ID for the process that output this event. */ uint64_t pid; @@ -57,9 +57,7 @@ struct TraceEvent { * The duration of the event, in microseconds (µs). Only applicable to * complete events ("ph": "X"). */ - std::optional dur; + std::optional dur; }; -} // namespace - -} // namespace facebook::react::jsinspector_modern +} // namespace facebook::react::jsinspector_modern::tracing diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h index 48685444909310..cbafe64d527bd1 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/TraceEventProfile.h @@ -7,6 +7,9 @@ #pragma once +#include +#include + #include namespace facebook::react::jsinspector_modern::tracing { @@ -18,10 +21,15 @@ struct TraceEventProfileChunk { /// Will be sent as part of the "ProfileChunk" trace event. struct TimeDeltas { folly::dynamic toDynamic() const { - return folly::dynamic::array(deltas.begin(), deltas.end()); + auto value = folly::dynamic::array(); + value.reserve(deltas.size()); + for (const auto& delta : deltas) { + value.push_back(highResDurationToTracingClockDuration(delta)); + } + return value; } - std::vector deltas; + std::vector deltas; }; /// Contains Profile information that will be emitted in this chunk: nodes and diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp index e63cc80192bed7..1303f560fba192 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/tests/RuntimeSamplingProfileTraceEventSerializerTest.cpp @@ -6,10 +6,10 @@ */ #include +#include #include #include -#include #include namespace facebook::react::jsinspector_modern::tracing { @@ -71,7 +71,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptyProfile) { PerformanceTracer::getInstance(), notificationCallback, 10); auto profile = createEmptyProfile(); - auto tracingStartTime = std::chrono::steady_clock::now(); + auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); @@ -119,7 +119,7 @@ TEST_F( samples.emplace_back(createSample(timestamp3, threadId, callStack3)); auto profile = createProfileWithSamples(std::move(samples)); - auto tracingStartTime = std::chrono::steady_clock::now(); + auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); @@ -148,7 +148,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, EmptySample) { samples.emplace_back(createSample(timestamp, threadId, emptyCallStack)); auto profile = createProfileWithSamples(std::move(samples)); - auto tracingStartTime = std::chrono::steady_clock::now(); + auto tracingStartTime = HighResTimeStamp::now(); // Mock the performance tracer methods folly::dynamic profileEvent = folly::dynamic::object; @@ -189,7 +189,7 @@ TEST_F( auto profile = createProfileWithSamples(std::move(samples)); - auto tracingStartTime = std::chrono::steady_clock::now(); + auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); @@ -228,7 +228,7 @@ TEST_F( } auto profile = createProfileWithSamples(std::move(samples)); - auto tracingStartTime = std::chrono::steady_clock::now(); + auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); @@ -269,7 +269,7 @@ TEST_F(RuntimeSamplingProfileTraceEventSerializerTest, ProfileChunkSizeLimit) { } auto profile = createProfileWithSamples(std::move(samples)); - auto tracingStartTime = std::chrono::steady_clock::now(); + auto tracingStartTime = HighResTimeStamp::now(); // Execute serializer.serializeAndNotify(profile, tracingStartTime); diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp index db7287e79d129a..804d4a1f3f3cee 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -784,7 +784,7 @@ TEST_F(BridgingTest, highResTimeStampTest) { bridging::fromJs( rt, bridging::toJs(rt, timestamp), invoker)); - HighResDuration duration = HighResDuration::fromNanoseconds(1); + auto duration = HighResDuration::fromNanoseconds(1); EXPECT_EQ( duration, bridging::fromJs( diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h index 0d932e996d5a6e..3872a65217a509 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h @@ -51,7 +51,7 @@ using NativeIntersectionObserverEntry = // isIntersectingAboveThresholds bool, // time - double>; + HighResTimeStamp>; template <> struct Bridging diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index e8b60299d66603..d6ab76d20d846b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -116,30 +116,31 @@ std::shared_ptr tryGetObserver( NativePerformance::NativePerformance(std::shared_ptr jsInvoker) : NativePerformanceCxxSpec(std::move(jsInvoker)) {} -double NativePerformance::now(jsi::Runtime& /*rt*/) { +HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) { return JSExecutor::performanceNow(); } -double NativePerformance::markWithResult( +HighResTimeStamp NativePerformance::markWithResult( jsi::Runtime& rt, std::string name, - std::optional startTime) { + std::optional startTime) { auto entry = PerformanceEntryReporter::getInstance()->reportMark(name, startTime); return entry.startTime; } -std::tuple NativePerformance::measureWithResult( +std::tuple +NativePerformance::measureWithResult( jsi::Runtime& runtime, std::string name, - double startTime, - double endTime, - std::optional duration, + HighResTimeStamp startTime, + HighResTimeStamp endTime, + std::optional duration, std::optional startMark, std::optional endMark) { auto reporter = PerformanceEntryReporter::getInstance(); - DOMHighResTimeStamp startTimeValue = startTime; + HighResTimeStamp startTimeValue = startTime; // If the start time mark name is specified, it takes precedence over the // startTime parameter, which can be set to 0 by default from JavaScript. if (startMark) { @@ -151,7 +152,7 @@ std::tuple NativePerformance::measureWithResult( } } - DOMHighResTimeStamp endTimeValue = endTime; + HighResTimeStamp endTimeValue = endTime; // If the end time mark name is specified, it takes precedence over the // startTime parameter, which can be set to 0 by default from JavaScript. if (endMark) { @@ -345,7 +346,8 @@ void NativePerformance::observe( return; } - auto durationThreshold = options.durationThreshold.value_or(0.0); + auto durationThreshold = + options.durationThreshold.value_or(HighResDuration::zero()); // observer of type multiple if (options.entryTypes.has_value()) { diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index 1a23499f988348..d2602b0b78bd63 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -32,7 +32,7 @@ using NativePerformancePerformanceObserverObserveOptions = // buffered std::optional, // durationThreshold - std::optional>; + std::optional>; template <> struct Bridging { @@ -52,21 +52,21 @@ struct Bridging { struct NativePerformanceEntry { std::string name; PerformanceEntryType entryType; - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp duration; + HighResTimeStamp startTime; + HighResDuration duration; // For PerformanceEventTiming only - std::optional processingStart; - std::optional processingEnd; + std::optional processingStart; + std::optional processingEnd; std::optional interactionId; // For PerformanceResourceTiming only - std::optional fetchStart; - std::optional requestStart; - std::optional connectStart; - std::optional connectEnd; - std::optional responseStart; - std::optional responseEnd; + std::optional fetchStart; + std::optional requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; + std::optional responseEnd; std::optional responseStatus; }; @@ -86,23 +86,23 @@ class NativePerformance : public NativePerformanceCxxSpec { #pragma mark - DOM Performance (High Resolution Time) (https://www.w3.org/TR/hr-time-3/#dom-performance) // https://www.w3.org/TR/hr-time-3/#now-method - double now(jsi::Runtime& rt); + HighResTimeStamp now(jsi::Runtime& rt); #pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/) // https://w3c.github.io/user-timing/#mark-method - double markWithResult( + HighResTimeStamp markWithResult( jsi::Runtime& rt, std::string name, - std::optional startTime); + std::optional startTime); // https://w3c.github.io/user-timing/#measure-method - std::tuple measureWithResult( + std::tuple measureWithResult( jsi::Runtime& rt, std::string name, - double startTime, - double endTime, - std::optional duration, + HighResTimeStamp startTime, + HighResTimeStamp endTime, + std::optional duration, std::optional startMark, std::optional endMark); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index e794902f85dc86..887af0d1a0753c 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -8,6 +8,7 @@ #pragma once #include + #include #include #include @@ -28,8 +29,8 @@ enum class PerformanceEntryType { struct AbstractPerformanceEntry { std::string name; - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp duration = 0; + HighResTimeStamp startTime; + HighResDuration duration = HighResDuration::zero(); }; struct PerformanceMark : AbstractPerformanceEntry { @@ -43,8 +44,8 @@ struct PerformanceMeasure : AbstractPerformanceEntry { struct PerformanceEventTiming : AbstractPerformanceEntry { static constexpr PerformanceEntryType entryType = PerformanceEntryType::EVENT; - DOMHighResTimeStamp processingStart; - DOMHighResTimeStamp processingEnd; + HighResTimeStamp processingStart; + HighResTimeStamp processingEnd; PerformanceEntryInteractionId interactionId; }; @@ -57,13 +58,13 @@ struct PerformanceResourceTiming : AbstractPerformanceEntry { static constexpr PerformanceEntryType entryType = PerformanceEntryType::RESOURCE; /** Aligns with `startTime`. */ - DOMHighResTimeStamp fetchStart; - DOMHighResTimeStamp requestStart; - std::optional connectStart; - std::optional connectEnd; - std::optional responseStart; + HighResTimeStamp fetchStart; + HighResTimeStamp requestStart; + std::optional connectStart; + std::optional connectEnd; + std::optional responseStart; /** Aligns with `duration`. */ - std::optional responseEnd; + std::optional responseEnd; std::optional responseStatus; }; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 96a137ace1463f..c3ce79d34127eb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -14,7 +14,7 @@ namespace facebook::react { // Default duration threshold for reporting performance entries (0 means "report // all") -constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; +constexpr HighResDuration DEFAULT_DURATION_THRESHOLD = HighResDuration::zero(); /** * Abstract performance entry buffer with reporting flags. @@ -22,7 +22,7 @@ constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; */ class PerformanceEntryBuffer { public: - double durationThreshold{DEFAULT_DURATION_THRESHOLD}; + HighResDuration durationThreshold = DEFAULT_DURATION_THRESHOLD; size_t droppedEntriesCount{0}; explicit PerformanceEntryBuffer() = default; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 4f7090bdfcb3c1..01a7591ccefde1 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -37,14 +37,6 @@ std::vector getSupportedEntryTypesInternal() { return supportedEntryTypes; } -uint64_t timestampToMicroseconds(DOMHighResTimeStamp timestamp) { - return static_cast(timestamp * 1000); -} - -double performanceNow() { - return chronoToDOMHighResTimeStamp(std::chrono::steady_clock::now()); -} - #if defined(__clang__) #define NO_DESTROY [[clang::no_destroy]] #else @@ -87,9 +79,9 @@ PerformanceEntryReporter::PerformanceEntryReporter() #endif } -DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { +HighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() - : performanceNow(); + : HighResTimeStamp::now(); } std::vector @@ -177,7 +169,7 @@ void PerformanceEntryReporter::clearEntries( PerformanceMark PerformanceEntryReporter::reportMark( const std::string& name, - const std::optional& startTime) { + const std::optional& startTime) { // Resolve timings auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp(); const auto entry = PerformanceMark{{.name = name, .startTime = startTimeVal}}; @@ -197,11 +189,11 @@ PerformanceMark PerformanceEntryReporter::reportMark( PerformanceMeasure PerformanceEntryReporter::reportMeasure( const std::string& name, - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackMetadata) { - DOMHighResTimeStamp duration = endTime - startTime; + HighResDuration duration = endTime - startTime; const auto entry = PerformanceMeasure{ {.name = std::string(name), @@ -221,7 +213,7 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure( return entry; } -std::optional PerformanceEntryReporter::getMarkTime( +std::optional PerformanceEntryReporter::getMarkTime( const std::string& markName) const { std::shared_lock lock(buffersMutex_); @@ -235,10 +227,10 @@ std::optional PerformanceEntryReporter::getMarkTime( void PerformanceEntryReporter::reportEvent( std::string name, - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp duration, - DOMHighResTimeStamp processingStart, - DOMHighResTimeStamp processingEnd, + HighResTimeStamp startTime, + HighResDuration duration, + HighResTimeStamp processingStart, + HighResTimeStamp processingEnd, uint32_t interactionId) { eventCounts_[name]++; @@ -264,8 +256,8 @@ void PerformanceEntryReporter::reportEvent( } void PerformanceEntryReporter::reportLongTask( - DOMHighResTimeStamp startTime, - DOMHighResTimeStamp duration) { + HighResTimeStamp startTime, + HighResDuration duration) { const auto entry = PerformanceLongTaskTiming{ {.name = std::string{"self"}, .startTime = startTime, @@ -281,12 +273,12 @@ void PerformanceEntryReporter::reportLongTask( PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( const std::string& url, - DOMHighResTimeStamp fetchStart, - DOMHighResTimeStamp requestStart, - std::optional connectStart, - std::optional connectEnd, - DOMHighResTimeStamp responseStart, - DOMHighResTimeStamp responseEnd, + HighResTimeStamp fetchStart, + HighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + HighResTimeStamp responseStart, + HighResTimeStamp responseEnd, const std::optional& responseStatus) { const auto entry = PerformanceResourceTiming{ {.name = url, .startTime = fetchStart}, @@ -312,13 +304,12 @@ PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming( void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { auto& performanceTracer = - jsinspector_modern::PerformanceTracer::getInstance(); + jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) { auto [trackName, eventName] = parseTrackName(entry.name); if (performanceTracer.isTracing()) { - performanceTracer.reportMark( - entry.name, timestampToMicroseconds(entry.startTime)); + performanceTracer.reportMark(entry.name, entry.startTime); } if (ReactPerfettoLogger::isTracing()) { @@ -330,7 +321,7 @@ void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const { void PerformanceEntryReporter::traceMeasure( const PerformanceMeasure& entry) const { auto& performanceTracer = - jsinspector_modern::PerformanceTracer::getInstance(); + jsinspector_modern::tracing::PerformanceTracer::getInstance(); if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) { auto [trackName, eventName] = parseTrackName(entry.name); @@ -342,10 +333,7 @@ void PerformanceEntryReporter::traceMeasure( trackMetadata = {.track = trackName.value()}; } performanceTracer.reportMeasure( - eventName, - timestampToMicroseconds(entry.startTime), - timestampToMicroseconds(entry.duration), - trackMetadata); + eventName, entry.startTime, entry.duration, trackMetadata); } if (ReactPerfettoLogger::isTracing()) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 549fe6f591a724..2eb67ceca0331f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -27,7 +27,8 @@ 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; +constexpr HighResDuration LONG_TASK_DURATION_THRESHOLD = + HighResDuration::fromMilliseconds(50); class PerformanceEntryReporter { public: @@ -66,9 +67,9 @@ class PerformanceEntryReporter { PerformanceEntryType entryType, const std::string& entryName); - DOMHighResTimeStamp getCurrentTimeStamp() const; + HighResTimeStamp getCurrentTimeStamp() const; - void setTimeStampProvider(std::function provider) { + void setTimeStampProvider(std::function provider) { timeStampProvider_ = std::move(provider); } @@ -80,37 +81,38 @@ class PerformanceEntryReporter { return eventCounts_; } - std::optional getMarkTime(const std::string& markName) const; + std::optional getMarkTime( + const std::string& markName) const; PerformanceMark reportMark( const std::string& name, - const std::optional& startTime = std::nullopt); + const std::optional& startTime = std::nullopt); PerformanceMeasure reportMeasure( const std::string& name, - double startTime, - double endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackMetadata = std::nullopt); void reportEvent( std::string name, - double startTime, - double duration, - double processingStart, - double processingEnd, + HighResTimeStamp startTime, + HighResDuration duration, + HighResTimeStamp processingStart, + HighResTimeStamp processingEnd, uint32_t interactionId); - void reportLongTask(double startTime, double duration); + void reportLongTask(HighResTimeStamp startTime, HighResDuration duration); PerformanceResourceTiming reportResourceTiming( const std::string& url, - DOMHighResTimeStamp fetchStart, - DOMHighResTimeStamp requestStart, - std::optional connectStart, - std::optional connectEnd, - DOMHighResTimeStamp responseStart, - DOMHighResTimeStamp responseEnd, + HighResTimeStamp fetchStart, + HighResTimeStamp requestStart, + std::optional connectStart, + std::optional connectEnd, + HighResTimeStamp responseStart, + HighResTimeStamp responseEnd, const std::optional& responseStatus); private: @@ -126,7 +128,7 @@ class PerformanceEntryReporter { std::unordered_map eventCounts_; - std::function timeStampProvider_ = nullptr; + std::function timeStampProvider_ = nullptr; const inline PerformanceEntryBuffer& getBuffer( PerformanceEntryType entryType) const { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index a79d8d0a99feb4..8b387ec753c54d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -7,12 +7,15 @@ #pragma once +#include "PerformanceEntryBuffer.h" +#include "PerformanceObserverRegistry.h" + +#include + #include #include #include #include -#include "PerformanceEntryBuffer.h" -#include "PerformanceObserverRegistry.h" namespace facebook::react { @@ -27,7 +30,7 @@ using PerformanceObserverCallback = std::function; * https://w3c.github.io/performance-timeline/#performanceobserverinit-dictionary */ struct PerformanceObserverObserveMultipleOptions { - double durationThreshold = 0.0; + HighResDuration durationThreshold = DEFAULT_DURATION_THRESHOLD; }; /** @@ -38,7 +41,7 @@ struct PerformanceObserverObserveMultipleOptions { */ struct PerformanceObserverObserveSingleOptions { bool buffered = false; - double durationThreshold = 0.0; + HighResDuration durationThreshold = DEFAULT_DURATION_THRESHOLD; }; /** @@ -124,7 +127,7 @@ class PerformanceObserver PerformanceObserverEntryTypeFilter observedTypes_; /// https://www.w3.org/TR/event-timing/#sec-modifications-perf-timeline - double durationThreshold_{DEFAULT_DURATION_THRESHOLD}; + HighResDuration durationThreshold_ = DEFAULT_DURATION_THRESHOLD; std::vector buffer_; bool didScheduleFlushBuffer_ = false; bool requiresDroppedEntries_ = false; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp index 5217debd97d262..330eb185f44d5f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -58,8 +58,10 @@ namespace facebook::react { [&](const auto& entryDetails) -> std::ostream& { os << "{ .name = \"" << entryDetails.name << "\"" << ", .entryType = " << entryTypeNames[static_cast(entryDetails.entryType) - 1] - << ", .startTime = " << entryDetails.startTime - << ", .duration = " << entryDetails.duration << " }"; + << ", .startTime = " + << entryDetails.startTime.toDOMHighResTimeStamp() + << ", .duration = " << entryDetails.duration.toDOMHighResTimeStamp() + << " }"; return os; }, entry); @@ -77,60 +79,116 @@ std::vector toSorted( TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); - reporter->reportMark("mark0", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark2", 2); + reporter->reportMark("mark0", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); // Report mark0 again - reporter->reportMark("mark0", 3); + reporter->reportMark( + "mark0", timeOrigin + HighResDuration::fromMilliseconds(3)); const auto entries = toSorted(reporter->getEntries()); ASSERT_EQ(4, entries.size()); const std::vector expected = { - PerformanceMark{{.name = "mark0", .startTime = 0, .duration = 0}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, - PerformanceMark{{.name = "mark0", .startTime = 3, .duration = 0}}}; + PerformanceMark{ + {.name = "mark0", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark0", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(3), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, entries); } TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); - reporter->reportMark("mark0", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark2", 2); - - reporter->reportMeasure("measure0", 0, 2); - reporter->reportMeasure("measure1", 0, 3); - - reporter->reportMark("mark3", 2.5); - reporter->reportMeasure("measure2", 2.0, 2.0); - reporter->reportMark("mark4", 3.0); + reporter->reportMark("mark0", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); + + reporter->reportMeasure( + "measure0", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(3)); + + reporter->reportMark( + "mark3", timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(2), + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMark( + "mark4", timeOrigin + HighResDuration::fromMilliseconds(3)); const auto entries = toSorted(reporter->getEntries()); const std::vector expected = { - PerformanceMark{{.name = "mark0", .startTime = 0, .duration = 0}}, - PerformanceMeasure{{.name = "measure0", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, - PerformanceMeasure{{.name = "measure2", .startTime = 2, .duration = 0}}, - PerformanceMark{{.name = "mark3", .startTime = 2.5, .duration = 0}}, - PerformanceMark{{.name = "mark4", .startTime = 3, .duration = 0}}}; + PerformanceMark{ + {.name = "mark0", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "measure0", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMeasure{ + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark4", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(3), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, entries); } TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); { @@ -138,27 +196,61 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { ASSERT_EQ(0, entries.size()); } - reporter->reportMark("common_name", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark2", 2); - - reporter->reportMeasure("common_name", 0, 2); - reporter->reportMeasure("measure1", 0, 3); - reporter->reportMeasure("measure2", 1, 6); - reporter->reportMeasure("measure3", 1.5, 2); + reporter->reportMark("common_name", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); + + reporter->reportMeasure( + "common_name", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(3)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(1), + timeOrigin + HighResDuration::fromMilliseconds(6)); + reporter->reportMeasure( + "measure3", + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + timeOrigin + HighResDuration::fromMilliseconds(2)); { const auto allEntries = toSorted(reporter->getEntries()); const std::vector expected = { - PerformanceMark{{.name = "common_name", .startTime = 0, .duration = 0}}, + PerformanceMark{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMeasure{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, PerformanceMeasure{ - {.name = "common_name", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 5}}, + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, PerformanceMeasure{ - {.name = "measure3", .startTime = 1.5, .duration = 0.5}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}}; + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::fromMilliseconds(5)}}, + PerformanceMeasure{ + {.name = "measure3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + .duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, allEntries); } @@ -166,9 +258,18 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { const auto marks = toSorted(reporter->getEntries(PerformanceEntryType::MARK)); const std::vector expected = { - PerformanceMark{{.name = "common_name", .startTime = 0, .duration = 0}}, - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}}; + PerformanceMark{ + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}}; ASSERT_EQ(expected, marks); } @@ -177,17 +278,28 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { toSorted(reporter->getEntries(PerformanceEntryType::MEASURE)); const std::vector expected = { PerformanceMeasure{ - {.name = "common_name", .startTime = 0, .duration = 2}}, - PerformanceMeasure{{.name = "measure1", .startTime = 0, .duration = 3}}, - PerformanceMeasure{{.name = "measure2", .startTime = 1, .duration = 5}}, + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}, + PerformanceMeasure{ + {.name = "measure1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(3)}}, PerformanceMeasure{ - {.name = "measure3", .startTime = 1.5, .duration = 0.5}}}; + {.name = "measure2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::fromMilliseconds(5)}}, + PerformanceMeasure{ + {.name = "measure3", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + .duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}}; ASSERT_EQ(expected, measures); } { const std::vector expected = { - PerformanceMark{{.name = "common_name", .startTime = 0}}}; + PerformanceMark{{.name = "common_name", .startTime = timeOrigin}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MARK, "common_name"); ASSERT_EQ(expected, commonName); @@ -195,7 +307,9 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { { const std::vector expected = {PerformanceMeasure{ - {.name = "common_name", .startTime = 0, .duration = 2}}}; + {.name = "common_name", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(2)}}}; const auto commonName = reporter->getEntries(PerformanceEntryType::MEASURE, "common_name"); ASSERT_EQ(expected, commonName); @@ -204,26 +318,52 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) { auto reporter = PerformanceEntryReporter::getInstance(); + auto timeOrigin = HighResTimeStamp::now(); reporter->clearEntries(); - reporter->reportMark("common_name", 0); - reporter->reportMark("mark1", 1); - reporter->reportMark("mark1", 2.1); - reporter->reportMark("mark2", 2); - - reporter->reportMeasure("common_name", 0, 2); - reporter->reportMeasure("measure1", 0, 3); - reporter->reportMeasure("measure2", 1, 6); - reporter->reportMeasure("measure3", 1.5, 2); + reporter->reportMark("common_name", timeOrigin); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(1)); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromNanoseconds(2.1 * 1e6)); + reporter->reportMark( + "mark2", timeOrigin + HighResDuration::fromMilliseconds(2)); + + reporter->reportMeasure( + "common_name", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(2)); + reporter->reportMeasure( + "measure1", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(3)); + reporter->reportMeasure( + "measure2", + timeOrigin + HighResDuration::fromMilliseconds(1), + timeOrigin + HighResDuration::fromMilliseconds(6)); + reporter->reportMeasure( + "measure3", + timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6), + timeOrigin + HighResDuration::fromMilliseconds(2)); reporter->clearEntries(PerformanceEntryType::MARK, "common_name"); { auto entries = toSorted(reporter->getEntries(PerformanceEntryType::MARK)); std::vector expected = { - PerformanceMark{{.name = "mark1", .startTime = 1, .duration = 0}}, - PerformanceMark{{.name = "mark2", .startTime = 2, .duration = 0}}, - PerformanceMark{{.name = "mark1", .startTime = 2.1, .duration = 0}}, + PerformanceMark{ + {.name = "mark1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(1), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(2), + .duration = HighResDuration::zero()}}, + PerformanceMark{ + {.name = "mark1", + .startTime = + timeOrigin + HighResDuration::fromNanoseconds(2.1 * 1e6), + .duration = HighResDuration::zero()}}, }; ASSERT_EQ(expected, entries); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp index 06cf2c3e09bffe..0a954958288728 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp @@ -49,12 +49,14 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveFlushes) { bool callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); + auto timeOrigin = HighResTimeStamp::now(); observer->observe(PerformanceEntryType::MARK); // buffer is empty ASSERT_FALSE(callbackCalled); - reporter->reportMark("test", 10); + reporter->reportMark( + "test", timeOrigin + HighResDuration::fromMilliseconds(10)); ASSERT_TRUE(callbackCalled); observer->disconnect(); @@ -64,10 +66,12 @@ TEST(PerformanceObserver, PerformanceObserverTestFilteredSingle) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); observer->observe(PerformanceEntryType::MEASURE); - reporter->reportMark("test", 10); + reporter->reportMark( + "test", timeOrigin + HighResDuration::fromMilliseconds(10)); // wrong type ASSERT_EQ(observer->takeRecords().size(), 0); @@ -79,15 +83,34 @@ TEST(PerformanceObserver, PerformanceObserverTestFilterMulti) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); observer->observe( {PerformanceEntryType::MEASURE, PerformanceEntryType::MARK}); - reporter->reportEvent("test1", 10, 10, 0, 0, 0); - reporter->reportEvent("test2", 10, 10, 0, 0, 0); - reporter->reportEvent("test3", 10, 10, 0, 0, 0); + reporter->reportEvent( + "test1", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test3", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); ASSERT_EQ(observer->takeRecords().size(), 0); ASSERT_FALSE(callbackCalled); @@ -101,11 +124,14 @@ TEST( auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); observer->observe(PerformanceEntryType::MEASURE); - reporter->reportMark("test", 10); + + reporter->reportMark( + "test", timeOrigin + HighResDuration::fromMilliseconds(10)); ASSERT_FALSE(callbackCalled); @@ -116,14 +142,34 @@ TEST(PerformanceObserver, PerformanceObserverTestFilterMultiCallbackNotCalled) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto callbackCalled = false; auto observer = PerformanceObserver::create( reporter->getObserverRegistry(), [&]() { callbackCalled = true; }); observer->observe( {PerformanceEntryType::MEASURE, PerformanceEntryType::MARK}); - reporter->reportEvent("test1", 10, 10, 0, 0, 0); - reporter->reportEvent("test2", 10, 10, 0, 0, 0); - reporter->reportEvent("off3", 10, 10, 0, 0, 0); + + reporter->reportEvent( + "test1", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "off3", + timeOrigin + HighResDuration::fromMilliseconds(10), + HighResDuration::fromMilliseconds(10), + timeOrigin, + timeOrigin, + 0); ASSERT_FALSE(callbackCalled); @@ -134,18 +180,32 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); observer->observe(PerformanceEntryType::MARK); - reporter->reportMark("test1", 10); - reporter->reportMeasure("off", 10, 20); - reporter->reportMark("test2", 20); - reporter->reportMark("test3", 30); + + reporter->reportMark( + "test1", timeOrigin + HighResDuration::fromMilliseconds(10)); + reporter->reportMeasure( + "off", + timeOrigin + HighResDuration::fromMilliseconds(10), + timeOrigin + HighResDuration::fromMilliseconds(20)); + reporter->reportMark( + "test2", timeOrigin + HighResDuration::fromMilliseconds(20)); + reporter->reportMark( + "test3", timeOrigin + HighResDuration::fromMilliseconds(30)); const std::vector expected = { - PerformanceMark{{.name = "test1", .startTime = 10}}, - PerformanceMark{{.name = "test2", .startTime = 20}}, - PerformanceMark{{.name = "test3", .startTime = 30}}, + PerformanceMark{ + {.name = "test1", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(10)}}, + PerformanceMark{ + {.name = "test2", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(20)}}, + PerformanceMark{ + {.name = "test3", + .startTime = timeOrigin + HighResDuration::fromMilliseconds(30)}}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -157,19 +217,66 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveDurationThreshold) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); - observer->observe(PerformanceEntryType::EVENT, {.durationThreshold = 50}); - reporter->reportEvent("test1", 0, 50, 0, 0, 0); - reporter->reportEvent("test2", 0, 100, 0, 0, 0); - reporter->reportEvent("off1", 0, 40, 0, 0, 0); - reporter->reportMark("off2", 100); - reporter->reportEvent("test3", 0, 60, 0, 0, 0); + observer->observe( + PerformanceEntryType::EVENT, + {.durationThreshold = HighResDuration::fromMilliseconds(50)}); + + reporter->reportEvent( + "test1", + timeOrigin, + HighResDuration::fromMilliseconds(50), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "off1", + timeOrigin, + HighResDuration::fromMilliseconds(40), + timeOrigin, + timeOrigin, + 0); + reporter->reportMark( + "off2", timeOrigin + HighResDuration::fromMilliseconds(100)); + reporter->reportEvent( + "test3", + timeOrigin, + HighResDuration::fromMilliseconds(60), + timeOrigin, + timeOrigin, + 0); const std::vector expected = { - PerformanceEventTiming{{.name = "test1", .duration = 50}, 0, 0, 0}, - PerformanceEventTiming{{.name = "test2", .duration = 100}, 0, 0, 0}, - PerformanceEventTiming{{.name = "test3", .duration = 60}, 0, 0, 0}, + PerformanceEventTiming{ + {.name = "test1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(50)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "test2", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "test3", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(60)}, + timeOrigin, + timeOrigin, + 0}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -181,23 +288,65 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveBuffered) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); - reporter->reportEvent("test1", 0, 50, 0, 0, 0); - reporter->reportEvent("test2", 0, 100, 0, 0, 0); - reporter->reportEvent("test3", 0, 40, 0, 0, 0); - reporter->reportEvent("test4", 0, 100, 0, 0, 0); + auto timeOrigin = HighResTimeStamp::now(); + reporter->reportEvent( + "test1", + timeOrigin, + HighResDuration::fromMilliseconds(50), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test2", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test3", + timeOrigin, + HighResDuration::fromMilliseconds(40), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "test4", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); auto observer = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); observer->observe( - PerformanceEntryType::EVENT, {.buffered = true, .durationThreshold = 50}); + PerformanceEntryType::EVENT, + {.buffered = true, + .durationThreshold = HighResDuration::fromMilliseconds(50)}); const std::vector expected = { PerformanceEventTiming{ - {.name = "test1", .startTime = 0, .duration = 50}, 0, 0, 0}, + {.name = "test1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(50)}, + timeOrigin, + timeOrigin, + 0}, PerformanceEventTiming{ - {.name = "test2", .startTime = 0, .duration = 100}, 0, 0, 0}, + {.name = "test2", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, PerformanceEventTiming{ - {.name = "test4", .startTime = 0, .duration = 100}, 0, 0, 0}, + {.name = "test4", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, }; ASSERT_EQ(expected, observer->takeRecords()); @@ -209,26 +358,71 @@ TEST(PerformanceObserver, PerformanceObserverTestMultiple) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); + auto timeOrigin = HighResTimeStamp::now(); auto observer1 = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); auto observer2 = PerformanceObserver::create(reporter->getObserverRegistry(), [&]() {}); - observer1->observe(PerformanceEntryType::EVENT, {.durationThreshold = 50}); - observer2->observe(PerformanceEntryType::EVENT, {.durationThreshold = 80}); - - reporter->reportMeasure("measure", 0, 50); - reporter->reportEvent("event1", 0, 100, 0, 0, 0); - reporter->reportEvent("event2", 0, 40, 0, 0, 0); - reporter->reportMark("mark1", 100); - reporter->reportEvent("event3", 0, 60, 0, 0, 0); + observer1->observe( + PerformanceEntryType::EVENT, + {.durationThreshold = HighResDuration::fromMilliseconds(50)}); + observer2->observe( + PerformanceEntryType::EVENT, + {.durationThreshold = HighResDuration::fromMilliseconds(80)}); + + reporter->reportMeasure( + "measure", + timeOrigin, + timeOrigin + HighResDuration::fromMilliseconds(50)); + reporter->reportEvent( + "event1", + timeOrigin, + HighResDuration::fromMilliseconds(100), + timeOrigin, + timeOrigin, + 0); + reporter->reportEvent( + "event2", + timeOrigin, + HighResDuration::fromMilliseconds(40), + timeOrigin, + timeOrigin, + 0); + reporter->reportMark( + "mark1", timeOrigin + HighResDuration::fromMilliseconds(100)); + reporter->reportEvent( + "event3", + timeOrigin, + HighResDuration::fromMilliseconds(60), + timeOrigin, + timeOrigin, + 0); const std::vector expected1 = { - PerformanceEventTiming{{.name = "event1", .duration = 100}, 0, 0, 0}, - PerformanceEventTiming{{.name = "event3", .duration = 60}, 0, 0, 0}, + PerformanceEventTiming{ + {.name = "event1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, + PerformanceEventTiming{ + {.name = "event3", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(60)}, + timeOrigin, + timeOrigin, + 0}, }; const std::vector expected2 = { - PerformanceEventTiming{{.name = "event1", .duration = 100}, 0, 0, 0}, + PerformanceEventTiming{ + {.name = "event1", + .startTime = timeOrigin, + .duration = HighResDuration::fromMilliseconds(100)}, + timeOrigin, + timeOrigin, + 0}, }; ASSERT_EQ(expected1, observer1->takeRecords()); diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h b/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h index 4e5d211e463c25..1d7526ec471583 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventLogger.h @@ -34,8 +34,7 @@ class EventLogger { virtual EventTag onEventStart( std::string_view name, SharedEventTarget target, - std::optional eventStartTimeStamp = - std::nullopt) = 0; + std::optional eventStartTimeStamp = std::nullopt) = 0; /* * Called when event starts getting dispatched (processed by the handlers, if diff --git a/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h b/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h index 817512e5c75282..d78cbd118606c3 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h +++ b/packages/react-native/ReactCommon/react/renderer/core/RawEvent.h @@ -87,7 +87,7 @@ struct RawEvent { // The client may specify a platform-specific timestamp for the event start // time, for example when MotionEvent was triggered on the Android native // side. - std::optional eventStartTimeStamp = std::nullopt; + std::optional eventStartTimeStamp = std::nullopt; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp b/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp index 467689efddba3a..b6ebd6f4905534 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/tests/EventQueueProcessorTest.cpp @@ -25,7 +25,7 @@ class MockEventLogger : public EventLogger { EventTag onEventStart( std::string_view /*name*/, SharedEventTarget /*target*/, - std::optional /*eventStartTimeStamp*/) override { + std::optional /*eventStartTimeStamp*/) override { return EMPTY_EVENT_TAG; } void onEventProcessingStart(EventTag /*tag*/) override {} diff --git a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp index 6dc9680b232109..f3665ddddaf5da 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.cpp @@ -105,7 +105,7 @@ EventPerformanceLogger::EventPerformanceLogger( EventTag EventPerformanceLogger::onEventStart( std::string_view name, SharedEventTarget target, - std::optional eventStartTimeStamp) { + std::optional eventStartTimeStamp) { auto performanceEntryReporter = performanceEntryReporter_.lock(); if (performanceEntryReporter == nullptr) { return EMPTY_EVENT_TAG; @@ -123,7 +123,7 @@ EventTag EventPerformanceLogger::onEventStart( // The event start timestamp may be provided by the caller in order to // specify the platform specific event start time. - DOMHighResTimeStamp timeStamp = eventStartTimeStamp + HighResTimeStamp timeStamp = eventStartTimeStamp ? *eventStartTimeStamp : performanceEntryReporter->getCurrentTimeStamp(); { @@ -213,7 +213,7 @@ void EventPerformanceLogger::dispatchPendingEventTimingEntries( void EventPerformanceLogger::shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept { + HighResTimeStamp mountTime) noexcept { auto performanceEntryReporter = performanceEntryReporter_.lock(); if (performanceEntryReporter == nullptr) { return; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h index ffa1d01fb79ff5..2addb87f1d6639 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/events/EventPerformanceLogger.h @@ -32,7 +32,7 @@ class EventPerformanceLogger : public EventLogger, EventTag onEventStart( std::string_view name, SharedEventTarget target, - std::optional eventStartTimeStamp = + std::optional eventStartTimeStamp = std::nullopt) override; void onEventProcessingStart(EventTag tag) override; void onEventProcessingEnd(EventTag tag) override; @@ -47,15 +47,15 @@ class EventPerformanceLogger : public EventLogger, void shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept override; + HighResTimeStamp mountTime) noexcept override; private: struct EventEntry { std::string_view name; SharedEventTarget target{nullptr}; - DOMHighResTimeStamp startTime; - std::optional processingStartTime; - std::optional processingEndTime; + HighResTimeStamp startTime; + std::optional processingStartTime; + std::optional processingEndTime; bool isWaitingForMount{false}; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp index d2e70b1d061983..3346eb836a8a27 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp @@ -132,7 +132,7 @@ static Float getHighestThresholdCrossed( std::optional IntersectionObserver::updateIntersectionObservation( const RootShadowNode& rootShadowNode, - double time) { + HighResTimeStamp time) { bool hasCustomRoot = observationRootShadowNodeFamily_.has_value(); auto rootAncestors = hasCustomRoot @@ -208,7 +208,7 @@ IntersectionObserver::updateIntersectionObservation( std::optional IntersectionObserver::updateIntersectionObservationForSurfaceUnmount( - double time) { + HighResTimeStamp time) { return setNotIntersectingState(Rect{}, Rect{}, Rect{}, time); } @@ -219,7 +219,7 @@ IntersectionObserver::setIntersectingState( const Rect& intersectionRect, Float threshold, Float rootThreshold, - double time) { + HighResTimeStamp time) { auto newState = IntersectionObserverState::Intersecting(threshold, rootThreshold); @@ -245,7 +245,7 @@ IntersectionObserver::setNotIntersectingState( const Rect& rootBoundingRect, const Rect& targetBoundingRect, const Rect& intersectionRect, - double time) { + HighResTimeStamp time) { if (state_ != IntersectionObserverState::NotIntersecting()) { state_ = IntersectionObserverState::NotIntersecting(); IntersectionObserverEntry entry{ diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h index 89483ec3b53828..20815c738c1328 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h @@ -25,9 +25,7 @@ struct IntersectionObserverEntry { Rect rootRect; Rect intersectionRect; bool isIntersectingAboveThresholds; - // TODO(T156529385) Define `DOMHighResTimeStamp` as an alias for `double` and - // use it here. - double time; + HighResTimeStamp time; bool sameShadowNodeFamily( const ShadowNodeFamily& otherShadowNodeFamily) const { @@ -49,10 +47,10 @@ class IntersectionObserver { // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo std::optional updateIntersectionObservation( const RootShadowNode& rootShadowNode, - double time); + HighResTimeStamp time); std::optional - updateIntersectionObservationForSurfaceUnmount(double time); + updateIntersectionObservationForSurfaceUnmount(HighResTimeStamp time); IntersectionObserverObserverId getIntersectionObserverId() const { return intersectionObserverId_; @@ -73,13 +71,13 @@ class IntersectionObserver { const Rect& intersectionRect, Float threshold, Float rootThreshold, - double time); + HighResTimeStamp time); std::optional setNotIntersectingState( const Rect& rootBoundingRect, const Rect& targetBoundingRect, const Rect& intersectionRect, - double time); + HighResTimeStamp time); IntersectionObserverObserverId intersectionObserverId_; std::optional observationRootShadowNodeFamily_; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp index a8eb27afeaf744..17e9667fdc4206 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp @@ -283,14 +283,14 @@ void IntersectionObserverManager::updateIntersectionObservations( void IntersectionObserverManager::shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double time) noexcept { + HighResTimeStamp time) noexcept { updateIntersectionObservations( rootShadowNode->getSurfaceId(), rootShadowNode.get(), time); } void IntersectionObserverManager::shadowTreeDidUnmount( SurfaceId surfaceId, - double time) noexcept { + HighResTimeStamp time) noexcept { updateIntersectionObservations(surfaceId, nullptr, time); } @@ -299,7 +299,7 @@ void IntersectionObserverManager::shadowTreeDidUnmount( void IntersectionObserverManager::updateIntersectionObservations( SurfaceId surfaceId, const RootShadowNode* rootShadowNode, - double time) { + HighResTimeStamp time) { TraceSection s("IntersectionObserverManager::updateIntersectionObservations"); std::vector entries; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h index 8faa627b40a29b..27af06df363570 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h @@ -56,9 +56,10 @@ class IntersectionObserverManager final void shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double time) noexcept override; + HighResTimeStamp time) noexcept override; - void shadowTreeDidUnmount(SurfaceId surfaceId, double time) noexcept override; + void shadowTreeDidUnmount(SurfaceId surfaceId, HighResTimeStamp time) noexcept + override; private: mutable std::unordered_map< @@ -93,7 +94,7 @@ class IntersectionObserverManager final void updateIntersectionObservations( SurfaceId surfaceId, const RootShadowNode* rootShadowNode, - double time); + HighResTimeStamp time); const IntersectionObserver& getRegisteredIntersectionObserver( SurfaceId surfaceId, diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp index 10fef3e9831ff4..fcdb9565edb684 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp @@ -460,12 +460,10 @@ void RuntimeScheduler_Modern::reportLongTasks( return; } - auto checkedDurationMs = - longestPeriodWithoutYieldingOpportunity_.toDOMHighResTimeStamp(); - if (checkedDurationMs >= LONG_TASK_DURATION_THRESHOLD_MS) { - auto durationMs = (endTime - startTime).toDOMHighResTimeStamp(); - auto startTimeMs = startTime.toDOMHighResTimeStamp(); - reporter->reportLongTask(startTimeMs, durationMs); + if (longestPeriodWithoutYieldingOpportunity_ >= + LONG_TASK_DURATION_THRESHOLD) { + auto duration = endTime - startTime; + reporter->reportLongTask(startTime, duration); } } diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp index 0fdff1b072f8ee..6251b5a552b614 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -1257,8 +1258,9 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasks) { [startTime](const auto& entryDetails) { EXPECT_EQ(entryDetails.entryType, PerformanceEntryType::LONGTASK); EXPECT_EQ( - entryDetails.startTime, startTime.toDOMHighResTimeStamp() + 100); - EXPECT_EQ(entryDetails.duration, 50); + entryDetails.startTime.toDOMHighResTimeStamp(), + startTime.toDOMHighResTimeStamp() + 100); + EXPECT_EQ(entryDetails.duration, HighResDuration::fromMilliseconds(50)); }, entry); } @@ -1344,8 +1346,10 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { [startTime](const auto& entryDetails) { EXPECT_EQ(entryDetails.entryType, PerformanceEntryType::LONGTASK); EXPECT_EQ( - entryDetails.startTime, startTime.toDOMHighResTimeStamp() + 100); - EXPECT_EQ(entryDetails.duration, 120); + entryDetails.startTime.toDOMHighResTimeStamp(), + startTime.toDOMHighResTimeStamp() + 100); + EXPECT_EQ( + entryDetails.duration, HighResDuration::fromMilliseconds(120)); }, entry); } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h index 3e5627b53eb15f..1a014411d85973 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h @@ -26,11 +26,11 @@ class UIManagerMountHook { */ virtual void shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept = 0; + HighResTimeStamp mountTime) noexcept = 0; virtual void shadowTreeDidUnmount( SurfaceId /*surfaceId*/, - double /*unmountTime*/) noexcept { + HighResTimeStamp /*unmountTime*/) noexcept { // Default no-op implementation for backwards compatibility. } diff --git a/packages/react-native/ReactCommon/react/timing/primitives.h b/packages/react-native/ReactCommon/react/timing/primitives.h index 1c39e303f5c918..9c3be8990b8af5 100644 --- a/packages/react-native/ReactCommon/react/timing/primitives.h +++ b/packages/react-native/ReactCommon/react/timing/primitives.h @@ -11,23 +11,6 @@ namespace facebook::react { -// `DOMHighResTimeStamp` represents a time value in milliseconds (time point or -// duration), with sub-millisecond precision. -// On the Web, the precision can be reduced for security purposes, but that is -// not necessary in React Native. -using DOMHighResTimeStamp = double; - -inline DOMHighResTimeStamp chronoToDOMHighResTimeStamp( - std::chrono::steady_clock::duration duration) { - return static_cast>(duration) - .count(); -} - -inline DOMHighResTimeStamp chronoToDOMHighResTimeStamp( - std::chrono::steady_clock::time_point timePoint) { - return chronoToDOMHighResTimeStamp(timePoint.time_since_epoch()); -} - class HighResDuration; class HighResTimeStamp; @@ -238,6 +221,20 @@ class HighResTimeStamp { .toDOMHighResTimeStamp(); } + // This method is expected to be used only when converting time stamps from + // external systems. + static constexpr HighResTimeStamp fromChronoSteadyClockTimePoint( + std::chrono::steady_clock::time_point chronoTimePoint) { + return HighResTimeStamp(chronoTimePoint); + } + + // This method is provided for convenience, if you need to convert + // HighResTimeStamp to some common epoch with time stamps from other sources. + constexpr std::chrono::steady_clock::time_point toChronoSteadyClockTimePoint() + const { + return chronoTimePoint_; + } + constexpr bool operator==(const HighResTimeStamp& rhs) const { return chronoTimePoint_ == rhs.chronoTimePoint_; } diff --git a/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp b/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp index bde37c28551c45..f94a379c036d84 100644 --- a/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp +++ b/packages/react-native/ReactCommon/react/timing/tests/PrimitivesTest.cpp @@ -11,39 +11,6 @@ namespace facebook::react { -using Clock = std::chrono::steady_clock; -using TimePoint = std::chrono::time_point; - -TEST(chronoToDOMHighResTimeStamp, withDurations) { - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::nanoseconds(10)), 0.00001); - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::microseconds(10)), 0.01); - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::milliseconds(10)), 10.0); - EXPECT_EQ(chronoToDOMHighResTimeStamp(std::chrono::seconds(10)), 10000.0); - EXPECT_EQ( - chronoToDOMHighResTimeStamp( - std::chrono::seconds(1) + std::chrono::nanoseconds(20)), - 1000.000020); -} - -TEST(chronoToDOMHighResTimeStamp, withTimePoints) { - EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::nanoseconds(10))), - 0.00001); - EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::microseconds(10))), - 0.01); - EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::milliseconds(10))), - 10.0); - EXPECT_EQ( - chronoToDOMHighResTimeStamp(TimePoint(std::chrono::seconds(10))), - 10000.0); - EXPECT_EQ( - chronoToDOMHighResTimeStamp( - TimePoint(std::chrono::seconds(1) + std::chrono::nanoseconds(20))), - 1000.000020); -} - TEST(HighResDuration, CorrectlyConvertsToDOMHighResTimeStamp) { EXPECT_EQ( HighResDuration::fromNanoseconds(10).toDOMHighResTimeStamp(), 0.00001); @@ -128,4 +95,11 @@ TEST(HighResTimeStamp, ComparisonOperators) { EXPECT_FALSE(now >= later); } +TEST(HighResTimeStamp, SteadyClockTimePointConversion) { + [[maybe_unused]] auto timestamp = + HighResTimeStamp::now().toChronoSteadyClockTimePoint(); + + EXPECT_TRUE(decltype(timestamp)::clock::is_steady); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp index c51d6a7a778a22..1ae283d7f5093e 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/HermesPerfettoDataSource.cpp @@ -113,7 +113,7 @@ void HermesPerfettoDataSource::OnStart(const StartArgs&) { "react-native", perfetto::DynamicString{"Profiling Started"}, getPerfettoWebPerfTrackSync("JS Sampling"), - performanceNowToPerfettoTraceTime(0)); + perfetto::TrackEvent::GetTraceTimeNs()); } void HermesPerfettoDataSource::OnFlush(const FlushArgs&) { diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp index a0786d4c649df4..c280ad4ca08a2d 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.cpp @@ -80,13 +80,17 @@ perfetto::Track getPerfettoWebPerfTrackAsync(const std::string& trackName) { } // Perfetto's monotonic clock seems to match the std::chrono::steady_clock we -// use in JSExecutor::performanceNow on Android platforms, but if that +// use in HighResTimeStamp on Android platforms, but if that // assumption is incorrect we may need to manually offset perfetto timestamps. -uint64_t performanceNowToPerfettoTraceTime(double perfNowTime) { - if (perfNowTime == 0) { - return perfetto::TrackEvent::GetTraceTimeNs(); - } - return static_cast(perfNowTime * 1.e6); +uint64_t highResTimeStampToPerfettoTraceTime(HighResTimeStamp timestamp) { + auto chronoDurationSinceSteadyClockEpoch = + timestamp.toChronoSteadyClockTimePoint().time_since_epoch(); + auto nanoseconds = std::chrono::duration_cast( + chronoDurationSinceSteadyClockEpoch); + + return std::chrono::duration_cast>( + nanoseconds) + .count(); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h index c01ac604c89fc1..c46ff051c53654 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfetto.h @@ -10,6 +10,7 @@ #ifdef WITH_PERFETTO #include +#include #include #include @@ -20,7 +21,7 @@ void initializePerfetto(); perfetto::Track getPerfettoWebPerfTrackSync(const std::string& trackName); perfetto::Track getPerfettoWebPerfTrackAsync(const std::string& trackName); -uint64_t performanceNowToPerfettoTraceTime(double perfNowTime); +uint64_t highResTimeStampToPerfettoTraceTime(HighResTimeStamp timestamp); } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp index c895334b7c5cdc..d021a912d492ad 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.cpp @@ -13,8 +13,6 @@ #include #endif -#include - namespace facebook::react { namespace { @@ -30,10 +28,9 @@ std::string toPerfettoTrackName( : PERFETTO_DEFAULT_TRACK_NAME; } #elif defined(WITH_FBSYSTRACE) -int64_t getDeltaNanos(double jsTime) { - auto now = std::chrono::steady_clock::now().time_since_epoch(); - return static_cast(jsTime * 1.e6) - - std::chrono::duration_cast(now).count(); +int64_t getDeltaNanos(HighResTimeStamp jsTime) { + auto now = HighResTimeStamp::now(); + return (jsTime - now).toNanoseconds(); } #endif @@ -51,8 +48,8 @@ int64_t getDeltaNanos(double jsTime) { /* static */ void ReactPerfettoLogger::measure( const std::string_view& eventName, - double startTime, - double endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackName) { #if defined(WITH_PERFETTO) if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) { @@ -61,9 +58,9 @@ int64_t getDeltaNanos(double jsTime) { "react-native", perfetto::DynamicString(eventName.data(), eventName.size()), track, - performanceNowToPerfettoTraceTime(startTime)); + highResTimeStampToPerfettoTraceTime(startTime)); TRACE_EVENT_END( - "react-native", track, performanceNowToPerfettoTraceTime(endTime)); + "react-native", track, highResTimeStampToPerfettoTraceTime(endTime)); } #elif defined(WITH_FBSYSTRACE) static int cookie = 0; @@ -77,7 +74,7 @@ int64_t getDeltaNanos(double jsTime) { /* static */ void ReactPerfettoLogger::mark( const std::string_view& eventName, - double startTime, + HighResTimeStamp startTime, const std::optional& trackName) { #if defined(WITH_PERFETTO) if (TRACE_EVENT_CATEGORY_ENABLED("react-native")) { @@ -85,7 +82,7 @@ int64_t getDeltaNanos(double jsTime) { "react-native", perfetto::DynamicString(eventName.data(), eventName.size()), getPerfettoWebPerfTrackSync(toPerfettoTrackName(trackName)), - performanceNowToPerfettoTraceTime(startTime)); + highResTimeStampToPerfettoTraceTime(startTime)); } #elif defined(WITH_FBSYSTRACE) static const char* kTrackName = "# Web Performance: Markers"; diff --git a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h index a85d06a5b3900c..7206d679faba48 100644 --- a/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h +++ b/packages/react-native/ReactCommon/reactperflogger/reactperflogger/ReactPerfettoLogger.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -24,13 +25,13 @@ class ReactPerfettoLogger { static void mark( const std::string_view& eventName, - double startTime, + HighResTimeStamp startTime, const std::optional& trackName); static void measure( const std::string_view& eventName, - double startTime, - double endTime, + HighResTimeStamp startTime, + HighResTimeStamp endTime, const std::optional& trackName); };