diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 2b933d897d0b4e..58bd66a49b6423 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -137,6 +137,18 @@ void NativePerformance::measure( #endif } +void NativePerformance::logEvent( + jsi::Runtime& rt, + std::string name, + double startTime, + double duration, + double processingStart, + double processingEnd, + double interactionId) { + PerformanceEntryReporter::getInstance()->logEventEntry( + name, startTime, duration, processingStart, processingEnd, interactionId); +} + std::unordered_map NativePerformance::getSimpleMemoryInfo( jsi::Runtime& rt) { auto heapInfo = rt.instrumentation().getHeapInfo(false); diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index fae3bf6c0dc4f9..73963c5fe228a6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -36,6 +36,15 @@ class NativePerformance : public NativePerformanceCxxSpec { std::optional startMark, std::optional endMark); + void logEvent( + jsi::Runtime& rt, + std::string name, + double startTime, + double duration, + double processingStart, + double processingEnd, + double interactionId); + // To align with web API, we will make sure to return three properties // (jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize) + anything needed from // the VM side. diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 57f59ac8b6e804..965689b5baf850 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -37,17 +37,14 @@ jsi::Object NativePerformanceObserver::createObserver( // the spec. The specification requires us to queue a single task that // dispatches observer callbacks. Instead, we are queuing all callbacks as // separate tasks in the scheduler. - PerformanceObserverCallback cb = [this, callback = std::move(callback)]( - PerformanceObserver& currentObserver) { - jsInvoker_->invokeAsync( - SchedulerPriority::IdlePriority, - [currentObserver, callback](jsi::Runtime& /*rt*/) mutable { - callback.call(); - currentObserver.flush(); - }); + PerformanceObserverCallback cb = [callback = std::move(callback)]() { + callback.callWithPriority(SchedulerPriority::IdlePriority); }; - auto observer = std::make_shared(std::move(cb)); + auto& registry = + PerformanceEntryReporter::getInstance()->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, std::move(cb)); jsi::Object observerObj{rt}; observerObj.setNativeState(rt, observer); return observerObj; @@ -97,10 +94,6 @@ void NativePerformanceObserver::observe( {.buffered = buffered, .durationThreshold = durationThreshold}); } } - - auto& registry = - PerformanceEntryReporter::getInstance()->getObserverRegistry(); - registry.addObserver(observer); } void NativePerformanceObserver::disconnect( @@ -114,10 +107,7 @@ void NativePerformanceObserver::disconnect( } observerObj.setNativeState(rt, nullptr); - - auto& registry = - PerformanceEntryReporter::getInstance()->getObserverRegistry(); - registry.removeObserver(observer); + observer->disconnect(); } std::vector NativePerformanceObserver::takeRecords( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 89d80cdd782732..da92cae4e5d7a9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -16,6 +16,7 @@ #endif #include +#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h index 54c3a49b2dabf0..b0fb5920a76a77 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h @@ -26,7 +26,7 @@ namespace facebook::react { template class CircularBuffer { public: - explicit CircularBuffer(size_t maxSize): maxSize_(maxSize) { + explicit CircularBuffer(size_t maxSize) : maxSize_(maxSize) { entries_.reserve(maxSize_); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 1b227cb391c366..9c5e8a74e041bf 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -29,6 +29,7 @@ void PerformanceObserver::handleEntry(const PerformanceEntry& entry) { std::vector PerformanceObserver::takeRecords() { std::vector result; buffer_.swap(result); + flush(); return result; } @@ -51,6 +52,7 @@ void PerformanceObserver::observe( scheduleFlushBuffer(); } } + registry_.addObserver(shared_from_this()); } void PerformanceObserver::observe( @@ -59,6 +61,7 @@ void PerformanceObserver::observe( observedTypes_ = std::move(types); requiresDroppedEntries_ = false; durationThreshold_ = options.durationThreshold; + registry_.addObserver(shared_from_this()); } double PerformanceObserver::getDroppedEntriesCount() noexcept { @@ -77,6 +80,10 @@ double PerformanceObserver::getDroppedEntriesCount() noexcept { return droppedEntriesCount; } +void PerformanceObserver::disconnect() noexcept { + registry_.removeObserver(shared_from_this()); +} + void PerformanceObserver::flush() noexcept { didScheduleFlushBuffer = false; } @@ -89,7 +96,7 @@ void PerformanceObserver::scheduleFlushBuffer() { if (!didScheduleFlushBuffer) { didScheduleFlushBuffer = true; - callback_(*this); + callback_(); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 307d3eb79dcf42..e5c02fab15dcd4 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -13,6 +13,7 @@ #include #include #include "PerformanceEntryBuffer.h" +#include "PerformanceObserverRegistry.h" namespace facebook::react { @@ -20,8 +21,7 @@ class PerformanceObserver; using PerformanceObserverEntryTypeFilter = std::unordered_set; -using PerformanceObserverCallback = - std::function; +using PerformanceObserverCallback = std::function; /** * Represents subset of spec's `PerformanceObserverInit` that is allowed for @@ -52,10 +52,27 @@ struct PerformanceObserverObserveSingleOptions { * Entries are pushed to the observer by the `PerformanceEntryReporter` class, * through the `PerformanceObserverRegistry` class which acts as a central hub. */ -class PerformanceObserver : public jsi::NativeState { +class PerformanceObserver + : public jsi::NativeState, + public std::enable_shared_from_this { + private: + struct PrivateUseCreateMethod { + explicit PrivateUseCreateMethod() = default; + }; + public: - explicit PerformanceObserver(PerformanceObserverCallback&& callback) - : callback_(std::move(callback)) {} + explicit PerformanceObserver( + PrivateUseCreateMethod, + PerformanceObserverRegistry& registry, + PerformanceObserverCallback&& callback) + : registry_(registry), callback_(std::move(callback)) {} + + static std::shared_ptr create( + PerformanceObserverRegistry& registry, + PerformanceObserverCallback&& callback) { + return std::make_shared( + PrivateUseCreateMethod(), registry, std::move(callback)); + } ~PerformanceObserver() = default; @@ -95,19 +112,21 @@ class PerformanceObserver : public jsi::NativeState { PerformanceObserverObserveMultipleOptions options = {}); /** - * Internal function called by JS bridge to get number of dropped entries - * count counted at call time. + * Disconnects observer from the registry */ - double getDroppedEntriesCount() noexcept; + void disconnect() noexcept; /** - * Called when the callback was dispatched + * Internal function called by JS bridge to get number of dropped entries + * count counted at call time. */ - void flush() noexcept; + double getDroppedEntriesCount() noexcept; private: void scheduleFlushBuffer(); + void flush() noexcept; + PerformanceObserverRegistry& registry_; PerformanceObserverCallback callback_; PerformanceObserverEntryTypeFilter observedTypes_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index cd61870652f50e..c1ce53881917eb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -6,6 +6,7 @@ */ #include "PerformanceObserverRegistry.h" +#include "PerformanceObserver.h" namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index 8b90df7ab7ac07..52bd710b03cbcc 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -10,10 +10,12 @@ #include #include #include -#include "PerformanceObserver.h" +#include "PerformanceEntry.h" namespace facebook::react { +class PerformanceObserver; + /** * PerformanceObserverRegistry acts as a container for known performance * observer instances. diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp index 3dd59765f0ef6a..a198b98e6b0715 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp @@ -19,7 +19,7 @@ constexpr auto OK = false; constexpr auto OVERWRITE = true; TEST(CircularBuffer, CanAddAndRetrieveElements) { - CircularBuffer buffer; + CircularBuffer buffer{5}; ASSERT_EQ(OK, buffer.add(1)); ASSERT_EQ(OK, buffer.add(2)); 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 65cb1a97887b25..eb9b1256e3b1fb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -41,76 +41,19 @@ namespace facebook::react { using namespace facebook::react; -TEST(PerformanceEntryReporter, PerformanceEntryReporterTestStartReporting) { - auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); - reporter->clearEntries(); - - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - - ASSERT_TRUE(reporter->isReporting(PerformanceEntryType::MARK)); - ASSERT_TRUE(reporter->isReporting(PerformanceEntryType::MEASURE)); - - ASSERT_FALSE(reporter->isReporting(PerformanceEntryType::EVENT)); -} - -TEST(PerformanceEntryReporter, PerformanceEntryReporterTestStopReporting) { - auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); - reporter->clearEntries(); - - reporter->startReporting(PerformanceEntryType::MARK); - - reporter->mark("mark0", 0.0); - reporter->mark("mark1", 0.0); - reporter->mark("mark2", 0.0); - reporter->measure("measure0", 0.0, 0.0); - - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(3, entries.size()); - - res = reporter->popPendingEntries(); - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(0, res.entries.size()); - - reporter->stopReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - - reporter->mark("mark3"); - reporter->measure("measure1", 0.0, 0.0); - - res = reporter->popPendingEntries(); - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(1, res.entries.size()); - ASSERT_STREQ("measure1", res.entries[0].name.c_str()); -} - TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { auto reporter = PerformanceEntryReporter::getInstance(); - reporter->stopReporting(); reporter->clearEntries(); - reporter->startReporting(PerformanceEntryType::MARK); - reporter->mark("mark0", 0.0); reporter->mark("mark1", 1.0); reporter->mark("mark2", 2.0); // Report mark0 again reporter->mark("mark0", 3.0); - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; + const auto& entries = reporter->getEntries(); - ASSERT_EQ(0, res.droppedEntriesCount); ASSERT_EQ(4, entries.size()); const std::vector expected = { @@ -133,13 +76,8 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); reporter->clearEntries(); - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - reporter->mark("mark0", 0.0); reporter->mark("mark1", 1.0); reporter->mark("mark2", 2.0); @@ -160,10 +98,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { // Uses the last reported time for mark4 reporter->measure("measure7", 0.0, 0.0, std::nullopt, "mark1", "mark4"); - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; - - ASSERT_EQ(0, res.droppedEntriesCount); + const auto& entries = reporter->getEntries(); const std::vector expected = { {.name = "mark0", @@ -244,18 +179,12 @@ static std::vector getTypes( TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); reporter->clearEntries(); - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(0, entries.size()); - - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); + { + const auto& entries = reporter->getEntries(); + ASSERT_EQ(0, entries.size()); + } reporter->mark("common_name", 0.0); reporter->mark("mark1", 1.0); @@ -267,17 +196,15 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { reporter->measure("measure3", 0.0, 0.0, 5.0, "mark1"); reporter->measure("measure4", 1.5, 0.0, std::nullopt, std::nullopt, "mark2"); - res = reporter->popPendingEntries(); - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(8, res.entries.size()); - - reporter->getEntries(PerformanceEntryType::MARK); - const auto marks = reporter->getEntries(PerformanceEntryType::MARK); + { + const auto& entries = reporter->getEntries(); + ASSERT_EQ(8, entries.size()); + } - const auto measures = reporter->getEntries(PerformanceEntryType::MEASURE); - const auto common_name = reporter->getEntries(std::nullopt, "common_name"); + const auto marks = reporter->getEntriesByType(PerformanceEntryType::MARK); + const auto measures = reporter->getEntriesByType(PerformanceEntryType::MEASURE); + const auto common_name = reporter->getEntriesByName("common_name"); - reporter->getEntries(); const auto all = reporter->getEntries(); ASSERT_EQ( @@ -320,13 +247,8 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearEntries) { auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); reporter->clearEntries(); - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - reporter->mark("common_name", 0.0); reporter->mark("mark1", 1.0); reporter->mark("mark2", 2.0); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp new file mode 100644 index 00000000000000..acb4a1312e65bf --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp @@ -0,0 +1,198 @@ +/* +* 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. +*/ + +#include +#include + +#include "../PerformanceEntryReporter.h" +#include "../PerformanceObserver.h" + +namespace facebook::react { + +[[maybe_unused]] static bool operator==( + const PerformanceEntry& lhs, + const PerformanceEntry& rhs) { + return lhs.name == rhs.name && lhs.entryType == rhs.entryType && + lhs.startTime == rhs.startTime && lhs.duration == rhs.duration && + lhs.processingStart == rhs.processingStart && + lhs.processingEnd == rhs.processingEnd && + lhs.interactionId == rhs.interactionId; +} +} // namespace facebook::react + + using namespace facebook::react; + +TEST(PerformanceObserver, PerformanceObserverTestObserveFlushes) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + bool in = false; + auto observer = PerformanceObserver::create(registry, [&]() { in = true; }); + observer->observe(PerformanceEntryType::MARK); + + // buffer is empty + ASSERT_FALSE(in); + + registry.queuePerformanceEntry({ .name = "test", .entryType = PerformanceEntryType::MARK, .startTime = 10, .duration = 10 }); + ASSERT_TRUE(in); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilteredSingle) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe(PerformanceEntryType::MEASURE); + registry.queuePerformanceEntry({ .name = "test", .entryType = PerformanceEntryType::MARK, .startTime = 10, .duration = 10 }); + + // wrong type + ASSERT_EQ(observer->takeRecords().size(), 0); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilterMulti) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto called = false; + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe({ PerformanceEntryType::MEASURE, PerformanceEntryType::MARK }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test1", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test2", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "off3", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + + ASSERT_EQ(observer->takeRecords().size(), 2); + ASSERT_FALSE(called); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilterSingleCallbackNotCalled) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto called = false; + auto observer = PerformanceObserver::create(registry, [&]() { called = true; }); + observer->observe(PerformanceEntryType::MEASURE); + registry.queuePerformanceEntry({ .name = "test", .entryType = PerformanceEntryType::MARK, .startTime = 10, .duration = 10 }); + + ASSERT_FALSE(called); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilterMultiCallbackNotCalled) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto called = false; + auto observer = PerformanceObserver::create(registry, [&]() { called = true; }); + observer->observe({ PerformanceEntryType::MEASURE, PerformanceEntryType::MARK }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test1", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test2", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "off3", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + + ASSERT_FALSE(called); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe(PerformanceEntryType::MARK); + registry.queuePerformanceEntry({ .name = "test1", .entryType = PerformanceEntryType::MARK, .startTime = 10 }); + registry.queuePerformanceEntry({ .name = "off", .entryType = PerformanceEntryType::MEASURE, .startTime = 10 }); + registry.queuePerformanceEntry({ .name = "test2", .entryType = PerformanceEntryType::MARK, .startTime = 20 }); + registry.queuePerformanceEntry({ .name = "test3", .entryType = PerformanceEntryType::MARK, .startTime = 30 }); + + const std::vector expected = { + { .name = "test1", .entryType = PerformanceEntryType::MARK, .startTime = 10 }, + { .name = "test2", .entryType = PerformanceEntryType::MARK, .startTime = 20 }, + { .name = "test3", .entryType = PerformanceEntryType::MARK, .startTime = 30 }, + }; + + ASSERT_EQ(expected, observer->takeRecords()); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestObserveDurationThreshold) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe(PerformanceEntryType::EVENT, { .durationThreshold = 50 }); + registry.queuePerformanceEntry({ .name = "test1", .entryType = PerformanceEntryType::EVENT, .duration = 50 }); + registry.queuePerformanceEntry({ .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "off1", .entryType = PerformanceEntryType::EVENT, .duration = 40 }); + registry.queuePerformanceEntry({ .name = "off2", .entryType = PerformanceEntryType::MARK, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }); + + const std::vector expected = { + { .name = "test1", .entryType = PerformanceEntryType::EVENT, .duration = 50 }, + { .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }, + { .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }, + }; + + ASSERT_EQ(expected, observer->takeRecords()); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestMultiple) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer1 = PerformanceObserver::create(registry, [&]() {}); + auto observer2 = PerformanceObserver::create(registry, [&]() {}); + observer1->observe(PerformanceEntryType::EVENT, { .durationThreshold = 50 }); + observer2->observe(PerformanceEntryType::EVENT, { .durationThreshold = 80 }); + + registry.queuePerformanceEntry({ .name = "test1", .entryType = PerformanceEntryType::MEASURE, .duration = 50 }); + registry.queuePerformanceEntry({ .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "off1", .entryType = PerformanceEntryType::EVENT, .duration = 40 }); + registry.queuePerformanceEntry({ .name = "off2", .entryType = PerformanceEntryType::MARK, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }); + + const std::vector expected1 = { + { .name = "test1", .entryType = PerformanceEntryType::EVENT, .duration = 50 }, + { .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }, + { .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }, + }; + + const std::vector expected2 = { + { .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 } + }; + + ASSERT_EQ(expected1, observer1->takeRecords()); + ASSERT_EQ(expected2, observer2->takeRecords()); + + observer1->disconnect(); + observer2->disconnect(); +} \ No newline at end of file diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 5861d3582803b8..7e4dd07f73d1b0 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -40,16 +40,6 @@ declare var global: { const getCurrentTimeStamp: () => DOMHighResTimeStamp = NativePerformance?.now ?? global.nativePerformanceNow ?? (() => Date.now()); -// We want some of the performance entry types to be always logged, -// even if they are not currently observed - this is either to be able to -// retrieve them at any time via Performance.getEntries* or to refer by other entries -// (such as when measures may refer to marks, even if the latter are not observed) -if (NativePerformanceObserver?.setIsBuffered) { - NativePerformanceObserver?.setIsBuffered( - ALWAYS_LOGGED_ENTRY_TYPES.map(performanceEntryTypeToRaw), - true, - ); -} function warnNoNativePerformance() { warnOnce( @@ -145,7 +135,7 @@ export default class Performance { return; } - NativePerformanceObserver?.clearEntries( + NativePerformanceObserver.clearEntries( RawPerformanceEntryTypeValues.MARK, markName, ); diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index acd03d67e16517..811e5760257c32 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -22,6 +22,7 @@ import { rawToPerformanceEntryType, } from './RawPerformanceEntry'; import NativePerformanceObserver from './specs/NativePerformanceObserver'; +import type { OpaqueNativeObserverHandle } from './specs/NativePerformanceObserver'; export type PerformanceEntryList = $ReadOnlyArray; @@ -56,27 +57,23 @@ export class PerformanceObserverEntryList { } } +export type PerformanceObserverCallbackOptions = { + droppedEntriesCount: number, +}; + export type PerformanceObserverCallback = ( list: PerformanceObserverEntryList, observer: PerformanceObserver, // The number of buffered entries which got dropped from the buffer due to the buffer being full: - droppedEntryCount?: number, + options?: PerformanceObserverCallbackOptions ) => void; -export type PerformanceObserverInit = - | { - entryTypes: Array, - } - | { - type: PerformanceEntryType, - durationThreshold?: DOMHighResTimeStamp, - }; - -type PerformanceObserverConfig = {| - callback: PerformanceObserverCallback, - entryTypes: $ReadOnlySet, - durationThreshold: ?number, -|}; +export type PerformanceObserverInit = { + entryTypes?: Array, + type?: PerformanceEntryType, + buffered?: boolean, + durationThreshold?: DOMHighResTimeStamp, +}; export function warnNoNativePerformanceObserver() { warnOnce( @@ -121,7 +118,7 @@ function getSupportedPerformanceEntryTypes(): $ReadOnlyArray { - const entryList = new PerformanceObserverEntryList(NativePerformanceObserver.takeRecords(this.#nativeObserverHandle)); + + return NativePerformanceObserver.createObserver(() => { // $FlowNotNull + const entries = NativePerformanceObserver + .takeRecords(this.#nativeObserverHandle) + .map(rawToPerformanceEntry); + const entryList = new PerformanceObserverEntryList(entries); + let droppedEntriesCount = 0; if (!this.#calledAtLeastOnce) { droppedEntriesCount = NativePerformanceObserver.getDroppedEntriesCount(this.#nativeObserverHandle); this.#calledAtLeastOnce = true; } + this.#callback(entryList, this, { droppedEntriesCount }); }); } diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js index 3e7a951686600d..057af0229a3f39 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js @@ -9,13 +9,16 @@ * @oncall react_native */ -import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry'; - // NOTE: Jest mocks of transitive dependencies don't appear to work with // ES6 module imports, therefore forced to use commonjs style imports here. const Performance = require('../Performance').default; -const NativePerformanceObserverMock = - require('../specs/__mocks__/NativePerformanceObserver').default; +const NativePerformanceMock = + require('../specs/__mocks__/NativePerformance').default; + +jest.mock( + '../specs/NativePerformance', + () => require('../specs/__mocks__/NativePerformance').default, +); jest.mock( '../specs/NativePerformanceObserver', @@ -29,41 +32,20 @@ describe('EventCounts', () => { }); it('consistently implements the API for EventCounts', async () => { - const eventDefaultValues = { - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 100, - }; - - NativePerformanceObserverMock.logRawEntry({ - name: 'click', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'input', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'input', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); + let interactionId = 0; + const eventDefaultValues = [ + 0, // startTime + 100, // duration + 0, // processing start + 100, // processingEnd + ]; + + NativePerformanceMock?.logEvent('click', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); const eventCounts = new Performance().eventCounts; expect(eventCounts.size).toBe(3); @@ -89,35 +71,17 @@ describe('EventCounts', () => { expect(Array.from(eventCounts.values())).toStrictEqual([1, 2, 3]); await jest.runAllTicks(); - NativePerformanceObserverMock.logRawEntry({ - name: 'input', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); + NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); expect(Array.from(eventCounts.values())).toStrictEqual([1, 3, 5]); await jest.runAllTicks(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'click', - ...eventDefaultValues, - }); + NativePerformanceMock?.logEvent('click', ...eventDefaultValues, interactionId++); await jest.runAllTicks(); - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); expect(Array.from(eventCounts.values())).toStrictEqual([2, 3, 6]); }); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js index 1c4bc1d60d992a..4c26bc0ca0431b 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js @@ -11,50 +11,23 @@ import type {PerformanceEntryList} from '../PerformanceObserver'; -import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry'; - -// NOTE: Jest mocks of transitive dependencies don't appear to work with -// ES6 module imports, therefore forced to use commonjs style imports here. -const {PerformanceObserver} = require('../PerformanceObserver'); -const NativePerformanceObserverMock = - require('../specs/__mocks__/NativePerformanceObserver').default; -const NativePerformanceObserver = require('../specs/NativePerformanceObserver'); +jest.mock( + '../specs/NativePerformance', + () => require('../specs/__mocks__/NativePerformance').default, +); jest.mock( '../specs/NativePerformanceObserver', () => require('../specs/__mocks__/NativePerformanceObserver').default, ); -describe('PerformanceObserver', () => { - it('can be mocked by a reference NativePerformanceObserver implementation', async () => { - expect(NativePerformanceObserver).not.toBe(undefined); - - let totalEntries = 0; - const observer: PerformanceObserver = new PerformanceObserver( - (list, _observer) => { - expect(_observer).toBe(observer); - const entries = list.getEntries(); - expect(entries).toHaveLength(1); - const entry = entries[0]; - expect(entry.name).toBe('mark1'); - expect(entry.entryType).toBe('mark'); - totalEntries += entries.length; - }, - ); - expect(() => observer.observe({entryTypes: ['mark']})).not.toThrow(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 0, - }); - - await jest.runAllTicks(); - expect(totalEntries).toBe(1); - observer.disconnect(); - }); +// // NOTE: Jest mocks of transitive dependencies don't appear to work with +// // ES6 module imports, therefore forced to use commonjs style imports here. +const {PerformanceObserver} = require('../PerformanceObserver'); +const NativePerformanceMock = + require('../specs/__mocks__/NativePerformance').default; +describe('PerformanceObserver', () => { it('prevents durationThreshold to be used together with entryTypes', async () => { const observer = new PerformanceObserver((list, _observer) => {}); @@ -73,223 +46,10 @@ describe('PerformanceObserver', () => { observer.observe({type: 'measure', durationThreshold: 100}); - NativePerformanceObserverMock.logRawEntry({ - name: 'measure1', - entryType: RawPerformanceEntryTypeValues.MEASURE, - startTime: 0, - duration: 200, - }); + NativePerformanceMock?.measure('measure1', 0, 200); await jest.runAllTicks(); expect(entries).toHaveLength(1); expect(entries.map(e => e.name)).toStrictEqual(['measure1']); }); - - it('handles durationThreshold argument as expected', async () => { - let entries: PerformanceEntryList = []; - const observer = new PerformanceObserver((list, _observer) => { - entries = [...entries, ...list.getEntries()]; - }); - - observer.observe({type: 'event', durationThreshold: 100}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event2', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 20, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event3', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 100, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event4', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 500, - }); - - await jest.runAllTicks(); - expect(entries).toHaveLength(3); - expect(entries.map(e => e.name)).toStrictEqual([ - 'event1', - 'event3', - 'event4', - ]); - }); - - it('correctly works with multiple PerformanceObservers with durationThreshold', async () => { - let entries1: PerformanceEntryList = []; - const observer1 = new PerformanceObserver((list, _observer) => { - entries1 = [...entries1, ...list.getEntries()]; - }); - - let entries2: PerformanceEntryList = []; - const observer2 = new PerformanceObserver((list, _observer) => { - entries2 = [...entries2, ...list.getEntries()]; - }); - - let entries3: PerformanceEntryList = []; - const observer3 = new PerformanceObserver((list, _observer) => { - entries3 = [...entries3, ...list.getEntries()]; - }); - - let entries4: PerformanceEntryList = []; - const observer4 = new PerformanceObserver((list, _observer) => { - entries4 = [...entries4, ...list.getEntries()]; - }); - - observer2.observe({type: 'event', durationThreshold: 200}); - observer1.observe({type: 'event', durationThreshold: 100}); - observer3.observe({type: 'event', durationThreshold: 300}); - observer3.observe({type: 'event', durationThreshold: 500}); - observer4.observe({entryTypes: ['event']}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event2', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 20, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event3', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 100, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event4', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 500, - }); - - await jest.runAllTicks(); - observer1.disconnect(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event5', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event6', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 300, - }); - - await jest.runAllTicks(); - observer3.disconnect(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event7', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - await jest.runAllTicks(); - observer4.disconnect(); - - expect(entries1.map(e => e.name)).toStrictEqual([ - 'event1', - 'event3', - 'event4', - ]); - expect(entries2.map(e => e.name)).toStrictEqual([ - 'event1', - 'event4', - 'event5', - 'event6', - 'event7', - ]); - expect(entries3.map(e => e.name)).toStrictEqual(['event4', 'event6']); - expect(entries4.map(e => e.name)).toStrictEqual([ - 'event1', - 'event2', - 'event3', - 'event4', - 'event5', - 'event6', - 'event7', - ]); - }); - - it('should guard against errors in observer callbacks', () => { - jest.spyOn(console, 'error').mockImplementation(() => {}); - - const observer1Callback = jest.fn((_entries, _observer, _options) => { - throw new Error('observer 1 callback'); - }); - const observer1 = new PerformanceObserver(observer1Callback); - - const observer2Callback = jest.fn(); - const observer2 = new PerformanceObserver(observer2Callback); - - observer1.observe({type: 'mark'}); - observer2.observe({type: 'mark'}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 200, - }); - - jest.runAllTicks(); - - expect(observer1Callback).toHaveBeenCalled(); - expect(observer2Callback).toHaveBeenCalled(); - - expect(console.error).toHaveBeenCalledWith( - new Error('observer 1 callback'), - ); - }); - - it('should not invoke observers with non-matching entries', () => { - const observer1Callback = jest.fn(); - const observer1 = new PerformanceObserver(observer1Callback); - - const observer2Callback = jest.fn(); - const observer2 = new PerformanceObserver(observer2Callback); - - observer1.observe({type: 'mark'}); - observer2.observe({type: 'measure'}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 200, - }); - - jest.runAllTicks(); - - expect(observer1Callback).toHaveBeenCalled(); - expect(observer2Callback).not.toHaveBeenCalled(); - }); }); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js index b05f2e74e4e1e6..db1dc5ac442a50 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -27,6 +27,14 @@ export interface Spec extends TurboModule { startMark?: string, endMark?: string, ) => void; + +logEvent: ( + name: string, + startTime: number, + duration: number, + processingStart: number, + processingEnd: number, + interactionId: number, + ) => void; +getSimpleMemoryInfo: () => NativeMemoryInfo; +getReactNativeStartupTiming: () => ReactNativeStartupTiming; } diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index c1a9cb8411d620..7354d1ef496380 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -35,10 +35,10 @@ export type GetPendingEntriesResult = {| |}; export type PerformanceObserverInit = { - entryTypes: $ReadOnlyArray; - type: number; - buffered: boolean; - durationThreshold: number; + entryTypes?: $ReadOnlyArray; + type?: number; + buffered?: boolean; + durationThreshold?: number; }; export interface Spec extends TurboModule { @@ -49,7 +49,7 @@ export interface Spec extends TurboModule { +observe: (observer: OpaqueNativeObserverHandle, options: PerformanceObserverInit) => void; +disconnect: (observer: OpaqueNativeObserverHandle) => void; +takeRecords: (observer: OpaqueNativeObserverHandle) => $ReadOnlyArray; - + +clearEntries: ( entryType?: RawPerformanceEntryType, entryName?: string, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js index b1e7072584fbf8..476a5abd5ed522 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js @@ -11,23 +11,24 @@ import type { NativeMemoryInfo, ReactNativeStartupTiming, - Spec as NativePerformance, } from '../NativePerformance'; import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; -import NativePerformanceObserver from '../NativePerformanceObserver'; +import NativePerformance from '../NativePerformance'; +import { logMockEntry } from './NativePerformanceObserver'; const marks: Map = new Map(); -const NativePerformanceMock: NativePerformance = { +const NativePerformanceMock: typeof NativePerformance = { mark: (name: string, startTime: number): void => { - NativePerformanceObserver?.logRawEntry({ - name, + NativePerformance?.mark(name, startTime); + marks.set(name, startTime); + logMockEntry({ entryType: RawPerformanceEntryTypeValues.MARK, + name, startTime, duration: 0, }); - marks.set(name, startTime); }, measure: ( @@ -40,11 +41,32 @@ const NativePerformanceMock: NativePerformance = { ): void => { const start = startMark != null ? marks.get(startMark) ?? 0 : startTime; const end = endMark != null ? marks.get(endMark) ?? 0 : endTime; - NativePerformanceObserver?.logRawEntry({ - name, + NativePerformance?.measure(name, start, end); + logMockEntry({ entryType: RawPerformanceEntryTypeValues.MEASURE, - startTime: start, - duration: duration ?? (end ? end - start : 0), + name, + startTime, + duration: duration ?? 0, + }); + }, + + logEvent: ( + name: string, + startTime: number, + duration: number, + processingStart: number, + processingEnd: number, + interactionId: number, + ): void => { + NativePerformance?.logEvent(name, startTime, duration, processingStart, processingEnd, interactionId); + logMockEntry({ + entryType: RawPerformanceEntryTypeValues.EVENT, + name, + startTime, + duration: duration ?? 0, + processingStart, + processingEnd, + interactionId, }); }, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js index 1104198efbfb7f..38c9e643a23840 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js @@ -9,90 +9,72 @@ */ import type { - GetPendingEntriesResult, + NativeBatchedObserverCallback, RawPerformanceEntry, RawPerformanceEntryType, + OpaqueNativeObserverHandle, + PerformanceObserverInit, Spec as NativePerformanceObserver, } from '../NativePerformanceObserver'; import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; -const reportingType: Set = new Set(); -const isAlwaysLogged: Set = new Set(); const eventCounts: Map = new Map(); -const durationThresholds: Map = new Map(); +const observers = new WeakSet(); let entries: Array = []; -let onPerformanceEntryCallback: ?() => void; -const NativePerformanceObserverMock: NativePerformanceObserver = { - startReporting: (entryType: RawPerformanceEntryType) => { - reportingType.add(entryType); - }, +export function logMockEntry(entry: RawPerformanceEntry) { + entries.push(entry); +} - stopReporting: (entryType: RawPerformanceEntryType) => { - reportingType.delete(entryType); - durationThresholds.delete(entryType); - }, +type MockObserver = { + callback: NativeBatchedObserverCallback, + entries: Array, + options: PerformanceObserverInit, + droppedEntriesCount: number +}; - setIsBuffered: ( - entryTypes: $ReadOnlyArray, - isBuffered: boolean, - ) => { - for (const entryType of entryTypes) { - if (isBuffered) { - isAlwaysLogged.add(entryType); - } else { - isAlwaysLogged.delete(entryType); - } - } +const NativePerformanceObserverMock: NativePerformanceObserver = { + getEventCounts: (): $ReadOnlyArray<[string, number]> => { + return Array.from(eventCounts.entries()); }, - popPendingEntries: (): GetPendingEntriesResult => { - const res = entries; - entries = []; - return { + createObserver: (callback: NativeBatchedObserverCallback): OpaqueNativeObserverHandle => { + const observer: MockObserver = { + callback, + entries: [], + options: {}, droppedEntriesCount: 0, - entries: res, }; + + return observer; }, - setOnPerformanceEntryCallback: (callback?: () => void) => { - onPerformanceEntryCallback = callback; + getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle): number => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + return mockObserver.droppedEntriesCount; }, - logRawEntry: (entry: RawPerformanceEntry) => { - if ( - reportingType.has(entry.entryType) || - isAlwaysLogged.has(entry.entryType) - ) { - const durationThreshold = durationThresholds.get(entry.entryType); - if ( - durationThreshold !== undefined && - entry.duration < durationThreshold - ) { - return; - } - entries.push(entry); - // $FlowFixMe[incompatible-call] - global.queueMicrotask(() => { - // We want to emulate the way it's done in native (i.e. async/batched) - onPerformanceEntryCallback?.(); - }); - } - if (entry.entryType === RawPerformanceEntryTypeValues.EVENT) { - eventCounts.set(entry.name, (eventCounts.get(entry.name) ?? 0) + 1); - } + observe: (observer: OpaqueNativeObserverHandle, options: PerformanceObserverInit): void => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + mockObserver.options = options; + observers.add(mockObserver); }, - getEventCounts: (): $ReadOnlyArray<[string, number]> => { - return Array.from(eventCounts.entries()); + disconnect: (observer: OpaqueNativeObserverHandle): void => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + observers.delete(mockObserver); }, - setDurationThreshold: ( - entryType: RawPerformanceEntryType, - durationThreshold: number, - ) => { - durationThresholds.set(entryType, durationThreshold); + takeRecords: (observer: OpaqueNativeObserverHandle): $ReadOnlyArray => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + const observerEntries = mockObserver.entries; + mockObserver.entries = []; + return observerEntries; }, clearEntries: (entryType?: RawPerformanceEntryType, entryName?: string) => { diff --git a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js index be57b29e2bb7bc..be7ac4eec5f32d 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js +++ b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js @@ -29,9 +29,9 @@ describe('NativePerformanceMock', () => { observer.observe({type: 'mark'}); - NativePerformanceMock.mark('mark1', 0); - NativePerformanceMock.mark('mark2', 5); - NativePerformanceMock.mark('mark3', 10); + NativePerformanceMock?.mark('mark1', 0); + NativePerformanceMock?.mark('mark2', 5); + NativePerformanceMock?.mark('mark3', 10); await jest.runAllTicks(); expect(entries).toHaveLength(3); @@ -47,13 +47,13 @@ describe('NativePerformanceMock', () => { observer.observe({entryTypes: ['measure']}); - NativePerformanceMock.mark('mark0', 0.0); - NativePerformanceMock.mark('mark1', 1.0); - NativePerformanceMock.mark('mark2', 2.0); + NativePerformanceMock?.mark('mark0', 0.0); + NativePerformanceMock?.mark('mark1', 1.0); + NativePerformanceMock?.mark('mark2', 2.0); - NativePerformanceMock.measure('measure0', 0, 2); - NativePerformanceMock.measure('measure1', 0, 2, 4); - NativePerformanceMock.measure( + NativePerformanceMock?.measure('measure0', 0, 2); + NativePerformanceMock?.measure('measure1', 0, 2, 4); + NativePerformanceMock?.measure( 'measure2', 0, 0, @@ -61,8 +61,8 @@ describe('NativePerformanceMock', () => { 'mark1', 'mark2', ); - NativePerformanceMock.measure('measure3', 0, 0, 5, 'mark1'); - NativePerformanceMock.measure( + NativePerformanceMock?.measure('measure3', 0, 0, 5, 'mark1'); + NativePerformanceMock?.measure( 'measure4', 1.5, 0, diff --git a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js index 3e3205ce115235..6147c17bbc78a7 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js +++ b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js @@ -9,71 +9,20 @@ * @oncall react_native */ -import NativePerformanceObserverMock from '../__mocks__/NativePerformanceObserver'; +import NativePerformanceObserverMock, { logMockEntry } from '../__mocks__/NativePerformanceObserver'; import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; describe('NativePerformanceObserver', () => { - it('correctly starts and stops listening to entries in a nominal scenario', async () => { - NativePerformanceObserverMock.startReporting( - RawPerformanceEntryTypeValues.MARK, - ); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 10, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark2', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 20, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 20, - }); - - const entriesResult = NativePerformanceObserverMock.popPendingEntries(); - expect(entriesResult).not.toBe(undefined); - const entries = entriesResult.entries; - - expect(entries.length).toBe(2); - expect(entries[0].name).toBe('mark1'); - expect(entries[1].name).toBe('mark2'); - - const entriesResult1 = NativePerformanceObserverMock.popPendingEntries(); - expect(entriesResult1).not.toBe(undefined); - const entries1 = entriesResult1.entries; - expect(entries1.length).toBe(0); - - NativePerformanceObserverMock.stopReporting( - RawPerformanceEntryTypeValues.MARK, - ); - }); - it('correctly clears/gets entries', async () => { - NativePerformanceObserverMock.startReporting( - RawPerformanceEntryTypeValues.MARK, - ); - - NativePerformanceObserverMock.startReporting( - RawPerformanceEntryTypeValues.EVENT, - ); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'mark1', entryType: RawPerformanceEntryTypeValues.MARK, startTime: 0, duration: 0, }); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'event1', entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, @@ -84,21 +33,21 @@ describe('NativePerformanceObserver', () => { expect(NativePerformanceObserverMock.getEntries()).toStrictEqual([]); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'entry1', entryType: RawPerformanceEntryTypeValues.MARK, startTime: 0, duration: 0, }); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'entry2', entryType: RawPerformanceEntryTypeValues.MARK, startTime: 0, duration: 0, }); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'entry1', entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0,