From 559c6029fddb82d44ab7dde7c976c689379e1f88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 2 Oct 2024 03:13:24 -0700 Subject: [PATCH] Refactor Performance and PerformanceObserver internals (#46693) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46693 Changelog: [internal] This unifies the native modules for `Performance` and `PerformanceObserver` in the same module. Keeping them separate is artificial and introduces unnecessary duplication. These APIs are very closely related so it makes sense to unify Reviewed By: javache Differential Revision: D63471855 fbshipit-source-id: fa8c5dc7b7c68954fc11867f68909d2c6c2ee85c --- .../src/jest/environment.js | 1 - .../webperformance/NativePerformance.cpp | 219 ++++++++++++++++ .../webperformance/NativePerformance.h | 107 ++++++++ .../NativePerformanceObserver.cpp | 200 -------------- .../NativePerformanceObserver.h | 105 -------- .../performance/timeline/PerformanceEntry.h | 1 - .../timeline/PerformanceEntryBuffer.h | 8 +- .../PerformanceEntryCircularBuffer.cpp | 6 +- .../timeline/PerformanceEntryCircularBuffer.h | 7 +- .../timeline/PerformanceEntryKeyedBuffer.cpp | 8 +- .../timeline/PerformanceEntryKeyedBuffer.h | 7 +- .../timeline/PerformanceEntryReporter.cpp | 140 +++++----- .../timeline/PerformanceEntryReporter.h | 70 +++-- .../timeline/PerformanceObserver.cpp | 2 +- .../React-performancetimeline.podspec | 1 + .../tests/PerformanceEntryReporterTest.cpp | 192 ++++++-------- .../webapis/performance/EventTiming.js | 21 +- .../webapis/performance/Performance.js | 70 +++-- .../webapis/performance/PerformanceEntry.js | 7 +- .../performance/PerformanceObserver.js | 52 ++-- .../performance/RawPerformanceEntry.js | 2 +- .../private/webapis/performance/Utilities.js | 7 - .../performance/__tests__/EventCounts-test.js | 135 ---------- .../performance/__tests__/Performance-test.js | 194 +++++++++++++- .../__tests__/PerformanceObserver-test.js | 31 ++- .../performance/specs/NativePerformance.js | 53 ++++ .../specs/NativePerformanceObserver.js | 69 ----- .../specs/__mocks__/NativePerformance.js | 69 ----- .../specs/__mocks__/NativePerformanceMock.js | 247 ++++++++++++++++++ .../__mocks__/NativePerformanceObserver.js | 154 ----------- .../__tests__/NativePerformanceMock-test.js | 86 ------ .../NativePerformanceObserverMock-test.js | 68 ----- 32 files changed, 1095 insertions(+), 1244 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp delete mode 100644 packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h delete mode 100644 packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js delete mode 100644 packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js delete mode 100644 packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js create mode 100644 packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceMock.js delete mode 100644 packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js delete mode 100644 packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js delete mode 100644 packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js diff --git a/packages/react-native-test-renderer/src/jest/environment.js b/packages/react-native-test-renderer/src/jest/environment.js index 166bba2c214..9f70431cb74 100644 --- a/packages/react-native-test-renderer/src/jest/environment.js +++ b/packages/react-native-test-renderer/src/jest/environment.js @@ -63,7 +63,6 @@ module.exports = class ReactNativeEnvironment extends NodeEnv { Networking: {}, ImageLoader: {}, NativePerformanceCxx: {}, - NativePerformanceObserverCxx: {}, LogBox: {}, SettingsManager: { getConstants: () => ({settings: {}}), diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index aac7fac28e5..6268e06bf17 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -15,6 +15,8 @@ #include #include #include +#include + #if __has_include() #include #define HAS_FUSEBOX @@ -71,6 +73,40 @@ std::tuple parseTrackName( return std::make_tuple(trackNameRef, eventName); } +class PerformanceObserverWrapper : public jsi::NativeState { + public: + explicit PerformanceObserverWrapper( + const std::shared_ptr observer) + : observer(observer) {} + + const std::shared_ptr observer; +}; + +void sortEntries(std::vector& entries) { + return std::stable_sort( + entries.begin(), entries.end(), PerformanceEntrySorter{}); +} + +const std::array ENTRY_TYPES_AVAILABLE_FROM_TIMELINE{ + {PerformanceEntryType::MARK, PerformanceEntryType::MEASURE}}; + +bool isAvailableFromTimeline(PerformanceEntryType entryType) { + return entryType == PerformanceEntryType::MARK || + entryType == PerformanceEntryType::MEASURE; +} + +std::shared_ptr tryGetObserver( + jsi::Runtime& rt, + jsi::Object& observerObj) { + if (!observerObj.hasNativeState(rt)) { + return nullptr; + } + + auto observerWrapper = std::dynamic_pointer_cast( + observerObj.getNativeState(rt)); + return observerWrapper ? observerWrapper->observer : nullptr; +} + } // namespace NativePerformance::NativePerformance(std::shared_ptr jsInvoker) @@ -137,6 +173,87 @@ void NativePerformance::measure( #endif } +void NativePerformance::clearMarks( + jsi::Runtime& /*rt*/, + std::optional entryName) { + if (entryName) { + PerformanceEntryReporter::getInstance()->clearEntries( + PerformanceEntryType::MARK, *entryName); + } else { + PerformanceEntryReporter::getInstance()->clearEntries( + PerformanceEntryType::MARK); + } +} + +void NativePerformance::clearMeasures( + jsi::Runtime& /*rt*/, + std::optional entryName) { + if (entryName) { + PerformanceEntryReporter::getInstance()->clearEntries( + PerformanceEntryType::MEASURE, *entryName); + } else { + PerformanceEntryReporter::getInstance()->clearEntries( + PerformanceEntryType::MEASURE); + } +} + +std::vector NativePerformance::getEntries( + jsi::Runtime& /*rt*/) { + std::vector entries; + + for (auto entryType : ENTRY_TYPES_AVAILABLE_FROM_TIMELINE) { + PerformanceEntryReporter::getInstance()->getEntries(entries, entryType); + } + + sortEntries(entries); + + return entries; +} + +std::vector NativePerformance::getEntriesByName( + jsi::Runtime& /*rt*/, + std::string entryName, + std::optional entryType) { + std::vector entries; + + if (entryType) { + if (isAvailableFromTimeline(*entryType)) { + PerformanceEntryReporter::getInstance()->getEntries( + entries, *entryType, entryName); + } + } else { + for (auto type : ENTRY_TYPES_AVAILABLE_FROM_TIMELINE) { + PerformanceEntryReporter::getInstance()->getEntries( + entries, type, entryName); + } + } + + sortEntries(entries); + + return entries; +} + +std::vector NativePerformance::getEntriesByType( + jsi::Runtime& /*rt*/, + PerformanceEntryType entryType) { + std::vector entries; + + if (isAvailableFromTimeline(entryType)) { + PerformanceEntryReporter::getInstance()->getEntries(entries, entryType); + } + + sortEntries(entries); + + return entries; +} + +std::vector> NativePerformance::getEventCounts( + jsi::Runtime& /*rt*/) { + const auto& eventCounts = + PerformanceEntryReporter::getInstance()->getEventCounts(); + return {eventCounts.begin(), eventCounts.end()}; +} + std::unordered_map NativePerformance::getSimpleMemoryInfo( jsi::Runtime& rt) { auto heapInfo = rt.instrumentation().getHeapInfo(false); @@ -185,4 +302,106 @@ NativePerformance::getReactNativeStartupTiming(jsi::Runtime& rt) { return result; } +jsi::Object NativePerformance::createObserver( + jsi::Runtime& rt, + NativePerformancePerformanceObserverCallback callback) { + // The way we dispatch performance observer callbacks is a bit different from + // 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 = [callback = std::move(callback)]() { + callback.callWithPriority(SchedulerPriority::IdlePriority); + }; + + auto& registry = + PerformanceEntryReporter::getInstance()->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, std::move(cb)); + auto observerWrapper = std::make_shared(observer); + jsi::Object observerObj{rt}; + observerObj.setNativeState(rt, observerWrapper); + return observerObj; +} + +double NativePerformance::getDroppedEntriesCount( + jsi::Runtime& rt, + jsi::Object observerObj) { + auto observer = tryGetObserver(rt, observerObj); + + if (!observer) { + return 0; + } + + return observer->getDroppedEntriesCount(); +} + +void NativePerformance::observe( + jsi::Runtime& rt, + jsi::Object observerObj, + NativePerformancePerformanceObserverObserveOptions options) { + auto observer = tryGetObserver(rt, observerObj); + + if (!observer) { + return; + } + + auto durationThreshold = options.durationThreshold.value_or(0.0); + + // observer of type multiple + if (options.entryTypes.has_value()) { + std::unordered_set entryTypes; + auto rawTypes = options.entryTypes.value(); + + for (auto rawType : rawTypes) { + entryTypes.insert(Bridging::fromJs(rt, rawType)); + } + + observer->observe(entryTypes); + } else { // single + auto buffered = options.buffered.value_or(false); + if (options.type.has_value()) { + observer->observe( + static_cast(options.type.value()), + {.buffered = buffered, .durationThreshold = durationThreshold}); + } + } +} + +void NativePerformance::disconnect(jsi::Runtime& rt, jsi::Object observerObj) { + auto observerWrapper = std::dynamic_pointer_cast( + observerObj.getNativeState(rt)); + + if (!observerWrapper) { + return; + } + + auto observer = observerWrapper->observer; + observer->disconnect(); +} + +std::vector NativePerformance::takeRecords( + jsi::Runtime& rt, + jsi::Object observerObj, + bool sort) { + auto observerWrapper = std::dynamic_pointer_cast( + observerObj.getNativeState(rt)); + + if (!observerWrapper) { + return {}; + } + + auto observer = observerWrapper->observer; + auto records = observer->takeRecords(); + if (sort) { + sortEntries(records); + } + return records; +} + +std::vector +NativePerformance::getSupportedPerformanceEntryTypes(jsi::Runtime& /*rt*/) { + auto supportedEntryTypes = PerformanceEntryReporter::getSupportedEntryTypes(); + return {supportedEntryTypes.begin(), supportedEntryTypes.end()}; +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index fae3bf6c0dc..4e173796db3 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -14,19 +14,65 @@ #else #include #endif + +#include #include +#include #include namespace facebook::react { +using NativePerformancePerformanceObserverCallback = AsyncCallback<>; +using NativePerformancePerformanceObserverObserveOptions = + NativePerformancePerformanceObserverInit< + // entryTypes + std::optional>, + // type + std::optional, + // buffered + std::optional, + // durationThreshold + std::optional>; + +template <> +struct Bridging { + static PerformanceEntryType fromJs( + jsi::Runtime& /*rt*/, + const jsi::Value& value) { + return static_cast(value.asNumber()); + } + + static jsi::Value toJs( + jsi::Runtime& /*rt*/, + const PerformanceEntryType& value) { + return {static_cast(value)}; + } +}; + +template <> +struct Bridging + : NativePerformanceRawPerformanceEntryBridging {}; + +template <> +struct Bridging + : NativePerformancePerformanceObserverInitBridging< + NativePerformancePerformanceObserverObserveOptions> {}; + class NativePerformance : public NativePerformanceCxxSpec { public: NativePerformance(std::shared_ptr jsInvoker); +#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); +#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/) + + // https://w3c.github.io/user-timing/#mark-method void mark(jsi::Runtime& rt, std::string name, double startTime); + // https://w3c.github.io/user-timing/#measure-method void measure( jsi::Runtime& rt, std::string name, @@ -36,6 +82,65 @@ class NativePerformance : public NativePerformanceCxxSpec { std::optional startMark, std::optional endMark); + // https://w3c.github.io/user-timing/#clearmarks-method + void clearMarks( + jsi::Runtime& rt, + std::optional entryName = std::nullopt); + + // https://w3c.github.io/user-timing/#clearmeasures-method + void clearMeasures( + jsi::Runtime& rt, + std::optional entryName = std::nullopt); + +#pragma mark - Performance Timeline (https://w3c.github.io/performance-timeline/#performance-timeline) + + // https://www.w3.org/TR/performance-timeline/#getentries-method + std::vector getEntries(jsi::Runtime& rt); + + // https://www.w3.org/TR/performance-timeline/#getentriesbytype-method + std::vector getEntriesByType( + jsi::Runtime& rt, + PerformanceEntryType entryType); + + // https://www.w3.org/TR/performance-timeline/#getentriesbyname-method + std::vector getEntriesByName( + jsi::Runtime& rt, + std::string entryName, + std::optional entryType = std::nullopt); + +#pragma mark - Performance Observer (https://w3c.github.io/performance-timeline/#the-performanceobserver-interface) + + jsi::Object createObserver( + jsi::Runtime& rt, + NativePerformancePerformanceObserverCallback callback); + + // https://www.w3.org/TR/performance-timeline/#dom-performanceobservercallbackoptions-droppedentriescount + double getDroppedEntriesCount(jsi::Runtime& rt, jsi::Object observerObj); + + void observe( + jsi::Runtime& rt, + jsi::Object observer, + NativePerformancePerformanceObserverObserveOptions options); + void disconnect(jsi::Runtime& rt, jsi::Object observer); + std::vector takeRecords( + jsi::Runtime& rt, + jsi::Object observerObj, + // When called via `observer.takeRecords` it should be in insertion order. + // When called via the observer callback, it should be in chronological + // order with respect to `startTime`. + bool sort); + + std::vector getSupportedPerformanceEntryTypes( + jsi::Runtime& rt); + +#pragma mark - Event Timing API functions (https://www.w3.org/TR/event-timing/) + + // https://www.w3.org/TR/event-timing/#dom-performance-eventcounts + std::vector> getEventCounts( + jsi::Runtime& rt); + +#pragma mark - Non-standard memory functions + // To align with web API, we will make sure to return three properties // (jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize) + anything needed from // the VM side. @@ -49,6 +154,8 @@ class NativePerformance : public NativePerformanceCxxSpec { // for heap size information, as double's 2^53 sig bytes is large enough. std::unordered_map getSimpleMemoryInfo(jsi::Runtime& rt); +#pragma mark - RN-specific startup timing + // Collect and return the RN app startup timing information for performance // tracking. std::unordered_map getReactNativeStartupTiming( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp deleted file mode 100644 index 688ef0d0584..00000000000 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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 "NativePerformanceObserver.h" -#include -#include -#include -#include -#include -#include -#include - -#ifdef RN_DISABLE_OSS_PLUGIN_HEADER -#include "Plugins.h" -#endif - -std::shared_ptr -NativePerformanceObserverModuleProvider( - std::shared_ptr jsInvoker) { - return std::make_shared( - std::move(jsInvoker)); -} - -namespace facebook::react { - -class PerformanceObserverWrapper : public jsi::NativeState { - public: - explicit PerformanceObserverWrapper( - const std::shared_ptr observer) - : observer(observer) {} - - std::shared_ptr observer; -}; - -NativePerformanceObserver::NativePerformanceObserver( - std::shared_ptr jsInvoker) - : NativePerformanceObserverCxxSpec(std::move(jsInvoker)) {} - -jsi::Object NativePerformanceObserver::createObserver( - jsi::Runtime& rt, - NativePerformanceObserverCallback callback) { - // The way we dispatch performance observer callbacks is a bit different from - // 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 = [callback = std::move(callback)]() { - callback.callWithPriority(SchedulerPriority::IdlePriority); - }; - - auto& registry = - PerformanceEntryReporter::getInstance()->getObserverRegistry(); - - auto observer = PerformanceObserver::create(registry, std::move(cb)); - auto observerWrapper = std::make_shared(observer); - jsi::Object observerObj{rt}; - observerObj.setNativeState(rt, observerWrapper); - return observerObj; -} - -double NativePerformanceObserver::getDroppedEntriesCount( - jsi::Runtime& rt, - jsi::Object observerObj) { - auto observerWrapper = std::dynamic_pointer_cast( - observerObj.getNativeState(rt)); - - if (!observerWrapper) { - return 0; - } - - auto observer = observerWrapper->observer; - return observer->getDroppedEntriesCount(); -} - -void NativePerformanceObserver::observe( - jsi::Runtime& rt, - jsi::Object observerObj, - NativePerformanceObserverObserveOptions options) { - auto observerWrapper = std::dynamic_pointer_cast( - observerObj.getNativeState(rt)); - - if (!observerWrapper) { - return; - } - - auto observer = observerWrapper->observer; - auto durationThreshold = options.durationThreshold.value_or(0.0); - - // observer of type multiple - if (options.entryTypes.has_value()) { - std::unordered_set entryTypes; - auto rawTypes = options.entryTypes.value(); - - for (auto rawType : rawTypes) { - entryTypes.insert(Bridging::fromJs(rt, rawType)); - } - - observer->observe(entryTypes); - } else { // single - auto buffered = options.buffered.value_or(false); - if (options.type.has_value()) { - observer->observe( - static_cast(options.type.value()), - {.buffered = buffered, .durationThreshold = durationThreshold}); - } - } -} - -void NativePerformanceObserver::disconnect( - jsi::Runtime& rt, - jsi::Object observerObj) { - auto observerWrapper = std::dynamic_pointer_cast( - observerObj.getNativeState(rt)); - - if (!observerWrapper) { - return; - } - - auto observer = observerWrapper->observer; - observer->disconnect(); -} - -std::vector NativePerformanceObserver::takeRecords( - jsi::Runtime& rt, - jsi::Object observerObj, - bool sort) { - auto observerWrapper = std::dynamic_pointer_cast( - observerObj.getNativeState(rt)); - - if (!observerWrapper) { - return {}; - } - - auto observer = observerWrapper->observer; - auto records = observer->takeRecords(); - if (sort) { - std::stable_sort(records.begin(), records.end(), PerformanceEntrySorter{}); - } - return records; -} - -void NativePerformanceObserver::clearEntries( - jsi::Runtime& /*rt*/, - PerformanceEntryType entryType, - std::optional entryName) { - PerformanceEntryReporter::getInstance()->clearEntries(entryType, entryName); -} - -std::vector NativePerformanceObserver::getEntries( - jsi::Runtime& /*rt*/, - std::optional entryType, - std::optional entryName) { - const auto reporter = PerformanceEntryReporter::getInstance(); - - std::vector entries; - - if (entryType.has_value()) { - if (entryName.has_value()) { - entries = - reporter->getEntriesByName(entryName.value(), entryType.value()); - } else { - entries = reporter->getEntriesByType(entryType.value()); - } - } else if (entryName.has_value()) { - entries = reporter->getEntriesByName(entryName.value()); - } else { - entries = reporter->getEntries(); - } - - std::stable_sort(entries.begin(), entries.end(), PerformanceEntrySorter{}); - - return entries; -} - -std::vector -NativePerformanceObserver::getSupportedPerformanceEntryTypes( - jsi::Runtime& /*rt*/) { - std::vector supportedEntries = { - PerformanceEntryType::MARK, - PerformanceEntryType::MEASURE, - PerformanceEntryType::EVENT, - }; - - if (ReactNativeFeatureFlags::enableLongTaskAPI()) { - supportedEntries.push_back(PerformanceEntryType::LONGTASK); - } - - return supportedEntries; -} - -std::vector> -NativePerformanceObserver::getEventCounts(jsi::Runtime& /*rt*/) { - const auto& eventCounts = - PerformanceEntryReporter::getInstance()->getEventCounts(); - return {eventCounts.begin(), eventCounts.end()}; -} -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h deleted file mode 100644 index 35d1e7cf40a..00000000000 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 - -#if __has_include("rncoreJSI.h") // Cmake headers on Android -#include "rncoreJSI.h" -#elif __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple -#include "FBReactNativeSpecJSI.h" -#else -#include -#endif - -#include -#include -#include -#include - -namespace facebook::react { - -using NativePerformanceObserverCallback = AsyncCallback<>; -using NativePerformanceObserverObserveOptions = - NativePerformanceObserverPerformanceObserverInit< - // entryTypes - std::optional>, - // type - std::optional, - // buffered - std::optional, - // durationThreshold - std::optional>; - -#pragma mark - Structs - -template <> -struct Bridging { - static PerformanceEntryType fromJs( - jsi::Runtime& /*rt*/, - const jsi::Value& value) { - return static_cast(value.asNumber()); - } - - static jsi::Value toJs( - jsi::Runtime& /*rt*/, - const PerformanceEntryType& value) { - return {static_cast(value)}; - } -}; - -template <> -struct Bridging - : NativePerformanceObserverPerformanceObserverInitBridging< - NativePerformanceObserverObserveOptions> {}; - -template <> -struct Bridging - : NativePerformanceObserverRawPerformanceEntryBridging {}; - -#pragma mark - implementation - -class NativePerformanceObserver - : public NativePerformanceObserverCxxSpec { - public: - explicit NativePerformanceObserver(std::shared_ptr jsInvoker); - - jsi::Object createObserver( - jsi::Runtime& rt, - NativePerformanceObserverCallback callback); - double getDroppedEntriesCount(jsi::Runtime& rt, jsi::Object observerObj); - - void observe( - jsi::Runtime& rt, - jsi::Object observer, - NativePerformanceObserverObserveOptions options); - void disconnect(jsi::Runtime& rt, jsi::Object observer); - std::vector takeRecords( - jsi::Runtime& rt, - jsi::Object observerObj, - // When called via `observer.takeRecords` it should be in insertion order. - // When called via the observer callback, it should be in chronological - // order with respect to `startTime`. - bool sort); - - std::vector> getEventCounts( - jsi::Runtime& rt); - - void clearEntries( - jsi::Runtime& rt, - PerformanceEntryType entryType, - std::optional entryName); - - std::vector getEntries( - jsi::Runtime& rt, - std::optional entryType, - std::optional entryName); - - std::vector getSupportedPerformanceEntryTypes( - jsi::Runtime& rt); -}; - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index ff292871aed..191970e4bc5 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -10,7 +10,6 @@ #include #include #include -#include #include namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 5d31d94ce79..96a137ace14 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -29,12 +29,12 @@ class PerformanceEntryBuffer { virtual ~PerformanceEntryBuffer() = default; virtual void add(const PerformanceEntry& entry) = 0; - virtual void getEntries( - std::string_view name, - std::vector& target) const = 0; virtual void getEntries(std::vector& target) const = 0; + virtual void getEntries( + std::vector& target, + const std::string& name) const = 0; virtual void clear() = 0; - virtual void clear(std::string_view name) = 0; + virtual void clear(const std::string& name) = 0; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index fadbe0e756f..4a38e847d7e 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -21,8 +21,8 @@ void PerformanceEntryCircularBuffer::getEntries( } void PerformanceEntryCircularBuffer::getEntries( - std::string_view name, - std::vector& target) const { + std::vector& target, + const std::string& name) const { buffer_.getEntries( target, [&](const PerformanceEntry& e) { return e.name == name; }); } @@ -31,7 +31,7 @@ void PerformanceEntryCircularBuffer::clear() { buffer_.clear(); } -void PerformanceEntryCircularBuffer::clear(std::string_view name) { +void PerformanceEntryCircularBuffer::clear(const std::string& name) { buffer_.clear([&](const PerformanceEntry& e) { return e.name == name; }); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index e9e4e54ace7..cfdaf81ff8d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -20,11 +20,12 @@ class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; void getEntries(std::vector& target) const override; - void getEntries(std::string_view name, std::vector& target) - const override; + void getEntries( + std::vector& target, + const std::string& name) const override; void clear() override; - void clear(std::string_view name) override; + void clear(const std::string& name) override; private: CircularBuffer buffer_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index 0b11279a8a3..89e3a3c1906 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -28,8 +28,8 @@ void PerformanceEntryKeyedBuffer::getEntries( } void PerformanceEntryKeyedBuffer::getEntries( - std::string_view name, - std::vector& target) const { + std::vector& target, + const std::string& name) const { std::string nameStr{name}; if (auto node = entryMap_.find(nameStr); node != entryMap_.end()) { @@ -41,8 +41,8 @@ void PerformanceEntryKeyedBuffer::clear() { entryMap_.clear(); } -void PerformanceEntryKeyedBuffer::clear(std::string_view nameView) { - entryMap_.erase(std::string(nameView)); +void PerformanceEntryKeyedBuffer::clear(const std::string& name) { + entryMap_.erase(name); } std::optional PerformanceEntryKeyedBuffer::find( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index fe57f18091a..85deed9f418 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -23,11 +23,12 @@ class PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { void getEntries(std::vector& target) const override; - void getEntries(std::string_view name, std::vector& target) - const override; + void getEntries( + std::vector& target, + const std::string& name) const override; void clear() override; - void clear(std::string_view name) override; + void clear(const std::string& name) override; std::optional find(const std::string& name) const; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 88f1a3bf37d..45164592e24 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -8,9 +8,26 @@ #include "PerformanceEntryReporter.h" #include +#include namespace facebook::react { +namespace { +std::vector getSupportedEntryTypesInternal() { + std::vector supportedEntryTypes{ + PerformanceEntryType::MARK, + PerformanceEntryType::MEASURE, + PerformanceEntryType::EVENT, + }; + + if (ReactNativeFeatureFlags::enableLongTaskAPI()) { + supportedEntryTypes.emplace_back(PerformanceEntryType::LONGTASK); + } + + return supportedEntryTypes; +} +} // namespace + std::shared_ptr& PerformanceEntryReporter::getInstance() { static auto instance = std::make_shared(); @@ -20,90 +37,93 @@ PerformanceEntryReporter::getInstance() { PerformanceEntryReporter::PerformanceEntryReporter() : observerRegistry_(std::make_unique()) {} -#pragma mark - DOM Performance (High Resolution Time) (https://www.w3.org/TR/hr-time-3/#dom-performance) - DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() : JSExecutor::performanceNow(); } -#pragma mark - Performance Timeline (https://w3c.github.io/performance-timeline/) - -uint32_t PerformanceEntryReporter::getDroppedEntriesCount( - PerformanceEntryType type) const noexcept { - return getBuffer(type).droppedEntriesCount; +std::vector +PerformanceEntryReporter::getSupportedEntryTypes() { + static std::vector supportedEntries = + getSupportedEntryTypesInternal(); + return supportedEntries; } -void PerformanceEntryReporter::clearEntries( - std::optional entryType, - std::optional entryName) { +uint32_t PerformanceEntryReporter::getDroppedEntriesCount( + PerformanceEntryType entryType) const noexcept { std::lock_guard lock(buffersMutex_); - // Clear all entry types - if (!entryType) { - if (entryName.has_value()) { - markBuffer_.clear(*entryName); - measureBuffer_.clear(*entryName); - eventBuffer_.clear(*entryName); - longTaskBuffer_.clear(*entryName); - } else { - markBuffer_.clear(); - measureBuffer_.clear(); - eventBuffer_.clear(); - longTaskBuffer_.clear(); - } - return; - } - - auto& buffer = getBufferRef(*entryType); - if (entryName.has_value()) { - buffer.clear(*entryName); - } else { - buffer.clear(); - } + return getBuffer(entryType).droppedEntriesCount; } std::vector PerformanceEntryReporter::getEntries() const { - std::vector res; - // Collect all entry types - for (int i = 1; i <= NUM_PERFORMANCE_ENTRY_TYPES; i++) { - getBuffer(static_cast(i)).getEntries(res); + std::vector entries; + getEntries(entries); + return entries; +} + +void PerformanceEntryReporter::getEntries( + std::vector& dest) const { + std::lock_guard lock(buffersMutex_); + + for (auto entryType : getSupportedEntryTypes()) { + getBuffer(entryType).getEntries(dest); } - return res; } -std::vector PerformanceEntryReporter::getEntriesByType( +std::vector PerformanceEntryReporter::getEntries( PerformanceEntryType entryType) const { - std::vector res; - getEntriesByType(entryType, res); - return res; + std::vector dest; + getEntries(dest, entryType); + return dest; } -void PerformanceEntryReporter::getEntriesByType( +void PerformanceEntryReporter::getEntries( + std::vector& dest, + PerformanceEntryType entryType) const { + std::lock_guard lock(buffersMutex_); + + getBuffer(entryType).getEntries(dest); +} + +std::vector PerformanceEntryReporter::getEntries( PerformanceEntryType entryType, - std::vector& target) const { - getBuffer(entryType).getEntries(target); + const std::string& entryName) const { + std::vector entries; + getEntries(entries, entryType, entryName); + return entries; } -std::vector PerformanceEntryReporter::getEntriesByName( - std::string_view entryName) const { - std::vector res; - // Collect all entry types - for (int i = 1; i <= NUM_PERFORMANCE_ENTRY_TYPES; i++) { - getBuffer(static_cast(i)).getEntries(entryName, res); +void PerformanceEntryReporter::getEntries( + std::vector& dest, + PerformanceEntryType entryType, + const std::string& entryName) const { + std::lock_guard lock(buffersMutex_); + + getBuffer(entryType).getEntries(dest, entryName); +} + +void PerformanceEntryReporter::clearEntries() { + std::lock_guard lock(buffersMutex_); + + for (auto entryType : getSupportedEntryTypes()) { + getBufferRef(entryType).clear(); } - return res; } -std::vector PerformanceEntryReporter::getEntriesByName( - std::string_view entryName, - PerformanceEntryType entryType) const { - std::vector res; - getBuffer(entryType).getEntries(entryName, res); - return res; +void PerformanceEntryReporter::clearEntries(PerformanceEntryType entryType) { + std::lock_guard lock(buffersMutex_); + + getBufferRef(entryType).clear(); } -#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/) +void PerformanceEntryReporter::clearEntries( + PerformanceEntryType entryType, + const std::string& entryName) { + std::lock_guard lock(buffersMutex_); + + getBufferRef(entryType).clear(entryName); +} void PerformanceEntryReporter::reportMark( const std::string& name, @@ -166,8 +186,6 @@ DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( } } -#pragma mark - Event Timing API functions (https://www.w3.org/TR/event-timing/) - void PerformanceEntryReporter::reportEvent( std::string name, DOMHighResTimeStamp startTime, @@ -201,8 +219,6 @@ void PerformanceEntryReporter::reportEvent( observerRegistry_->queuePerformanceEntry(entry); } -#pragma mark - Long Tasks API functions (https://w3c.github.io/longtasks/) - void PerformanceEntryReporter::reportLongTask( DOMHighResTimeStamp startTime, DOMHighResTimeStamp duration) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 4b10e05b7ce..4acf2072a12 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "PerformanceEntryCircularBuffer.h" #include "PerformanceEntryKeyedBuffer.h" #include "PerformanceObserverRegistry.h" @@ -36,43 +37,47 @@ class PerformanceEntryReporter { return *observerRegistry_; } -#pragma mark - DOM Performance (High Resolution Time) (https://www.w3.org/TR/hr-time-3/#dom-performance) + std::vector getEntries() const; + void getEntries(std::vector& dest) const; + + std::vector getEntries( + PerformanceEntryType entryType) const; + void getEntries( + std::vector& dest, + PerformanceEntryType entryType) const; + + std::vector getEntries( + PerformanceEntryType entryType, + const std::string& entryName) const; + void getEntries( + std::vector& dest, + PerformanceEntryType entryType, + const std::string& entryName) const; + + void clearEntries(); + void clearEntries(PerformanceEntryType entryType); + void clearEntries( + PerformanceEntryType entryType, + const std::string& entryName); - // https://www.w3.org/TR/hr-time-3/#now-method DOMHighResTimeStamp getCurrentTimeStamp() const; void setTimeStampProvider(std::function provider) { timeStampProvider_ = std::move(provider); } -#pragma mark - Performance Timeline (https://w3c.github.io/performance-timeline/) + static std::vector getSupportedEntryTypes(); - // https://www.w3.org/TR/performance-timeline/#dom-performanceobservercallbackoptions-droppedentriescount uint32_t getDroppedEntriesCount(PerformanceEntryType type) const noexcept; - // https://www.w3.org/TR/performance-timeline/#getentries-method - // https://www.w3.org/TR/performance-timeline/#getentriesbytype-method - // https://www.w3.org/TR/performance-timeline/#getentriesbyname-method - std::vector getEntries() const; - std::vector getEntriesByType( - PerformanceEntryType entryType) const; - void getEntriesByType( - PerformanceEntryType entryType, - std::vector& target) const; - std::vector getEntriesByName( - std::string_view entryName) const; - std::vector getEntriesByName( - std::string_view entryName, - PerformanceEntryType entryType) const; + const std::unordered_map& getEventCounts() const { + return eventCounts_; + } -#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/) - - // https://w3c.github.io/user-timing/#mark-method void reportMark( const std::string& name, const std::optional& startTime = std::nullopt); - // https://w3c.github.io/user-timing/#measure-method void reportMeasure( const std::string_view& name, double startTime, @@ -81,14 +86,6 @@ class PerformanceEntryReporter { const std::optional& startMark = std::nullopt, const std::optional& endMark = std::nullopt); - // https://w3c.github.io/user-timing/#clearmarks-method - // https://w3c.github.io/user-timing/#clearmeasures-method - void clearEntries( - std::optional entryType = std::nullopt, - std::optional entryName = std::nullopt); - -#pragma mark - Event Timing API functions (https://www.w3.org/TR/event-timing/) - void reportEvent( std::string name, double startTime, @@ -97,13 +94,6 @@ class PerformanceEntryReporter { double processingEnd, uint32_t interactionId); - // https://www.w3.org/TR/event-timing/#dom-performance-eventcounts - const std::unordered_map& getEventCounts() const { - return eventCounts_; - } - -#pragma mark - Long Tasks API functions (https://w3c.github.io/longtasks/) - void reportLongTask(double startTime, double duration); private: @@ -121,7 +111,8 @@ class PerformanceEntryReporter { double getMarkTime(const std::string& markName) const; - inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { + const inline PerformanceEntryBuffer& getBuffer( + PerformanceEntryType entryType) const { switch (entryType) { case PerformanceEntryType::EVENT: return eventBuffer_; @@ -136,8 +127,7 @@ class PerformanceEntryReporter { } } - const inline PerformanceEntryBuffer& getBuffer( - PerformanceEntryType entryType) const { + inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { switch (entryType) { case PerformanceEntryType::EVENT: return eventBuffer_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 077e20132dd..f36f87016f3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -45,7 +45,7 @@ void PerformanceObserver::observe( if (options.buffered) { auto& reporter = PerformanceEntryReporter::getInstance(); - auto bufferedEntries = reporter->getEntriesByType(type); + auto bufferedEntries = reporter->getEntries(type); for (auto& bufferedEntry : bufferedEntries) { handleEntry(bufferedEntry); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec index a8dbcbe7a01..de83e0df5e2 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec +++ b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec @@ -51,6 +51,7 @@ Pod::Spec.new do |s| s.header_mappings_dir = "../../.." end + s.dependency "React-featureflags" s.dependency "React-timing" s.dependency "React-cxxreact" s.dependency "RCT-Folly", folly_version 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 3f4670a44c3..d11642a6876 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -11,6 +11,8 @@ #include "../PerformanceEntryReporter.h" +using namespace facebook::react; + namespace facebook::react { [[maybe_unused]] static bool operator==( @@ -37,27 +39,26 @@ namespace facebook::react { << ", .startTime = " << entry.startTime << ", .duration = " << entry.duration << " }"; } +} // namespace facebook::react -static std::vector toSorted( - const std::vector& originalEntries) { - std::vector entries = originalEntries; +namespace { +std::vector toSorted( + std::vector&& entries) { std::stable_sort(entries.begin(), entries.end(), PerformanceEntrySorter{}); return entries; } -} // namespace facebook::react - -using namespace facebook::react; +} // namespace TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); - reporter->reportMark("mark0", 0.0); - reporter->reportMark("mark1", 1.0); - reporter->reportMark("mark2", 2.0); + reporter->reportMark("mark0", 0); + reporter->reportMark("mark1", 1); + reporter->reportMark("mark2", 2); // Report mark0 again - reporter->reportMark("mark0", 3.0); + reporter->reportMark("mark0", 3); const auto entries = toSorted(reporter->getEntries()); @@ -66,17 +67,20 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { const std::vector expected = { {.name = "mark0", .entryType = PerformanceEntryType::MARK, - .startTime = 0.0}, + .startTime = 0, + .duration = 0}, {.name = "mark1", .entryType = PerformanceEntryType::MARK, - .startTime = 1.0}, + .startTime = 1, + .duration = 0}, {.name = "mark2", .entryType = PerformanceEntryType::MARK, - .startTime = 2.0}, + .startTime = 2, + .duration = 0}, {.name = "mark0", .entryType = PerformanceEntryType::MARK, - .startTime = 3.0}, - }; + .startTime = 3, + .duration = 0}}; ASSERT_EQ(expected, entries); } @@ -85,26 +89,26 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); - reporter->reportMark("mark0", 0.0); - reporter->reportMark("mark1", 1.0); - reporter->reportMark("mark2", 2.0); + reporter->reportMark("mark0", 0); + reporter->reportMark("mark1", 1); + reporter->reportMark("mark2", 2); - reporter->reportMeasure("measure0", 0.0, 2.0); - reporter->reportMeasure("measure1", 0.0, 2.0, 4.0); - reporter->reportMeasure("measure2", 0.0, 0.0, std::nullopt, "mark1", "mark2"); - reporter->reportMeasure("measure3", 0.0, 0.0, 5.0, "mark1"); + reporter->reportMeasure("measure0", 0, 2); + reporter->reportMeasure("measure1", 0, 2, 4); + reporter->reportMeasure("measure2", 0, 0, std::nullopt, "mark1", "mark2"); + reporter->reportMeasure("measure3", 0, 0, 5, "mark1"); reporter->reportMeasure( - "measure4", 1.5, 0.0, std::nullopt, std::nullopt, "mark2"); + "measure4", 1.5, 0, std::nullopt, std::nullopt, "mark2"); reporter->setTimeStampProvider([]() { return 3.5; }); - reporter->reportMeasure("measure5", 0.0, 0.0, std::nullopt, "mark2"); + reporter->reportMeasure("measure5", 0, 0, std::nullopt, "mark2"); reporter->reportMark("mark3", 2.5); reporter->reportMeasure("measure6", 2.0, 2.0); reporter->reportMark("mark4", 2.1); reporter->reportMark("mark4", 3.0); // Uses the last reported time for mark4 - reporter->reportMeasure("measure7", 0.0, 0.0, std::nullopt, "mark1", "mark4"); + reporter->reportMeasure("measure7", 0, 0, std::nullopt, "mark1", "mark4"); const auto entries = toSorted(reporter->getEntries()); @@ -178,16 +182,16 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { ASSERT_EQ(0, entries.size()); } - reporter->reportMark("common_name", 0.0); - reporter->reportMark("mark1", 1.0); - reporter->reportMark("mark2", 2.0); + reporter->reportMark("common_name", 0); + reporter->reportMark("mark1", 1); + reporter->reportMark("mark2", 2); - reporter->reportMeasure("common_name", 0.0, 2.0); - reporter->reportMeasure("measure1", 0.0, 2.0, 4.0); - reporter->reportMeasure("measure2", 0.0, 0.0, std::nullopt, "mark1", "mark2"); - reporter->reportMeasure("measure3", 0.0, 0.0, 5.0, "mark1"); + reporter->reportMeasure("common_name", 0, 2); + reporter->reportMeasure("measure1", 0, 2, 4); + reporter->reportMeasure("measure2", 0, 0, std::nullopt, "mark1", "mark2"); + reporter->reportMeasure("measure3", 0, 0, 5, "mark1"); reporter->reportMeasure( - "measure4", 1.5, 0.0, std::nullopt, std::nullopt, "mark2"); + "measure4", 1.5, 0, std::nullopt, std::nullopt, "mark2"); { const auto allEntries = toSorted(reporter->getEntries()); @@ -229,7 +233,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { { const auto marks = - toSorted(reporter->getEntriesByType(PerformanceEntryType::MARK)); + toSorted(reporter->getEntries(PerformanceEntryType::MARK)); const std::vector expected = { {.name = "common_name", .entryType = PerformanceEntryType::MARK, @@ -248,7 +252,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { { const auto measures = - toSorted(reporter->getEntriesByType(PerformanceEntryType::MEASURE)); + toSorted(reporter->getEntries(PerformanceEntryType::MEASURE)); const std::vector expected = { {.name = "common_name", .entryType = PerformanceEntryType::MEASURE, @@ -269,8 +273,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { {.name = "measure4", .entryType = PerformanceEntryType::MEASURE, .startTime = 1.5, - .duration = 0.5}, - }; + .duration = 0.5}}; ASSERT_EQ(expected, measures); } @@ -278,113 +281,72 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { const std::vector expected = { {.name = "common_name", .entryType = PerformanceEntryType::MARK, - .startTime = 0, - .duration = 0}, + .startTime = 0}}; + const auto commonName = + reporter->getEntries(PerformanceEntryType::MARK, "common_name"); + ASSERT_EQ(expected, commonName); + } + + { + const std::vector expected = { {.name = "common_name", .entryType = PerformanceEntryType::MEASURE, .startTime = 0, .duration = 2}}; - const auto commonName = toSorted(reporter->getEntriesByName("common_name")); + const auto commonName = + reporter->getEntries(PerformanceEntryType::MEASURE, "common_name"); ASSERT_EQ(expected, commonName); } } -TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearEntries) { +TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) { auto reporter = PerformanceEntryReporter::getInstance(); reporter->clearEntries(); - reporter->reportMark("common_name", 0.0); - reporter->reportMark("mark1", 1.0); - reporter->reportMark("mark2", 2.0); + reporter->reportMark("common_name", 0); + reporter->reportMark("mark1", 1); + reporter->reportMark("mark1", 2.1); + reporter->reportMark("mark2", 2); - reporter->reportMeasure("common_name", 0.0, 2.0); - reporter->reportMeasure("measure1", 0.0, 2.0, 4.0); - reporter->reportMeasure("measure2", 0.0, 0.0, std::nullopt, "mark1", "mark2"); - reporter->reportMeasure("measure3", 0.0, 0.0, 5.0, "mark1"); + reporter->reportMeasure("common_name", 0, 2); + reporter->reportMeasure("measure1", 0, 2, 4); + reporter->reportMeasure("measure2", 0, 0, std::nullopt, "mark1", "mark2"); + reporter->reportMeasure("measure3", 0, 0, 5, "mark1"); reporter->reportMeasure( - "measure4", 1.5, 0.0, std::nullopt, std::nullopt, "mark2"); + "measure4", 1.5, 0, std::nullopt, std::nullopt, "mark2"); + + reporter->clearEntries(PerformanceEntryType::MARK, "common_name"); { - reporter->clearEntries(std::nullopt, "common_name"); - auto entriesWithoutCommonName = toSorted(reporter->getEntries()); + auto entries = toSorted(reporter->getEntries(PerformanceEntryType::MARK)); std::vector expected = { - {.name = "measure1", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 4}, {.name = "mark1", .entryType = PerformanceEntryType::MARK, .startTime = 1, .duration = 0}, - {.name = "measure2", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 1}, - {.name = "measure3", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 5}, - {.name = "measure4", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1.5, - .duration = 0.5}, {.name = "mark2", .entryType = PerformanceEntryType::MARK, .startTime = 2, - .duration = 0}}; - - ASSERT_EQ(6, entriesWithoutCommonName.size()); - ASSERT_EQ(expected, entriesWithoutCommonName); + .duration = 0}, + {.name = "mark1", + .entryType = PerformanceEntryType::MARK, + .startTime = 2.1, + .duration = 0}, + }; + ASSERT_EQ(expected, entries); } - { - reporter->clearEntries(PerformanceEntryType::MARK, "mark1"); - auto entriesWithoutMark1 = toSorted(reporter->getEntries()); + reporter->clearEntries(PerformanceEntryType::MARK); - ASSERT_EQ(5, entriesWithoutMark1.size()); - ASSERT_EQ( - std::vector( - {{.name = "measure1", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 0, - .duration = 4}, - {.name = "measure2", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 1}, - {.name = "measure3", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1, - .duration = 5}, - {.name = "measure4", - .entryType = PerformanceEntryType::MEASURE, - .startTime = 1.5, - .duration = 0.5}, - {.name = "mark2", - .entryType = PerformanceEntryType::MARK, - .startTime = 2, - .duration = 0}}), - entriesWithoutMark1); + { + auto entries = reporter->getEntries(PerformanceEntryType::MARK); + ASSERT_EQ(entries.size(), 0); } - { - reporter->clearEntries(PerformanceEntryType::MEASURE); - auto entriesWithoutMeasures = toSorted(reporter->getEntries()); - - ASSERT_EQ(1, entriesWithoutMeasures.size()); - ASSERT_EQ( - std::vector( - {{.name = "mark2", - .entryType = PerformanceEntryType::MARK, - .startTime = 2, - .duration = 0}}), - entriesWithoutMeasures); - } + reporter->clearEntries(); { - reporter->clearEntries(); - auto emptyEntries = reporter->getEntries(); - - ASSERT_EQ(0, emptyEntries.size()); + auto entries = reporter->getEntries(); + ASSERT_EQ(entries.size(), 0); } } diff --git a/packages/react-native/src/private/webapis/performance/EventTiming.js b/packages/react-native/src/private/webapis/performance/EventTiming.js index 1a485f2bd25..a2f0cf505e0 100644 --- a/packages/react-native/src/private/webapis/performance/EventTiming.js +++ b/packages/react-native/src/private/webapis/performance/EventTiming.js @@ -16,8 +16,8 @@ import type { } from './PerformanceEntry'; import {PerformanceEntry} from './PerformanceEntry'; -import {warnNoNativePerformanceObserver} from './Utilities'; -import NativePerformanceObserver from './specs/NativePerformanceObserver'; +import {warnNoNativePerformance} from './Utilities'; +import NativePerformance from './specs/NativePerformance'; export type PerformanceEventTimingJSON = { ...PerformanceEntryJSON, @@ -85,14 +85,18 @@ function getCachedEventCounts(): Map { if (cachedEventCounts) { return cachedEventCounts; } - if (!NativePerformanceObserver) { - warnNoNativePerformanceObserver(); - return new Map(); + + if (!NativePerformance || !NativePerformance?.getEventCounts) { + warnNoNativePerformance(); + cachedEventCounts = new Map(); + return cachedEventCounts; } - cachedEventCounts = new Map( - NativePerformanceObserver.getEventCounts(), + const eventCounts = new Map( + NativePerformance.getEventCounts?.() ?? [], ); + cachedEventCounts = eventCounts; + // $FlowFixMe[incompatible-call] global.queueMicrotask(() => { // To be consistent with the calls to the API from the same task, @@ -101,7 +105,8 @@ function getCachedEventCounts(): Map { // after the current task is guaranteed to have finished. cachedEventCounts = null; }); - return cachedEventCounts ?? new Map(); + + return eventCounts; } /** diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 8546493c222..f2496ef58d4 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -13,22 +13,18 @@ import type { DOMHighResTimeStamp, PerformanceEntryType, + PerformanceEntryList, } from './PerformanceEntry'; -import type {PerformanceEntryList} from './PerformanceObserver'; import type {DetailType, PerformanceMarkOptions} from './UserTiming'; import {EventCounts} from './EventTiming'; import MemoryInfo from './MemoryInfo'; -import {ALWAYS_LOGGED_ENTRY_TYPES} from './PerformanceEntry'; -import {warnNoNativePerformanceObserver} from './Utilities'; import { performanceEntryTypeToRaw, rawToPerformanceEntry, } from './RawPerformanceEntry'; -import {RawPerformanceEntryTypeValues} from './RawPerformanceEntry'; import ReactNativeStartupTiming from './ReactNativeStartupTiming'; import NativePerformance from './specs/NativePerformance'; -import NativePerformanceObserver from './specs/NativePerformanceObserver'; import {PerformanceMark, PerformanceMeasure} from './UserTiming'; import {warnNoNativePerformance} from './Utilities'; @@ -47,6 +43,9 @@ export type PerformanceMeasureOptions = { end?: DOMHighResTimeStamp, }; +const ENTRY_TYPES_AVAILABLE_FROM_TIMELINE: $ReadOnlyArray = + ['mark', 'measure']; + /** * Partial implementation of the Performance interface for RN, * corresponding to the standard in @@ -122,15 +121,12 @@ export default class Performance { } clearMarks(markName?: string): void { - if (!NativePerformanceObserver?.clearEntries) { - warnNoNativePerformanceObserver(); + if (!NativePerformance?.clearMarks) { + warnNoNativePerformance(); return; } - NativePerformanceObserver.clearEntries( - RawPerformanceEntryTypeValues.MARK, - markName, - ); + NativePerformance.clearMarks(markName); } measure( @@ -209,15 +205,12 @@ export default class Performance { } clearMeasures(measureName?: string): void { - if (!NativePerformanceObserver?.clearEntries) { - warnNoNativePerformanceObserver(); + if (!NativePerformance?.clearMeasures) { + warnNoNativePerformance(); return; } - NativePerformanceObserver?.clearEntries( - RawPerformanceEntryTypeValues.MEASURE, - measureName, - ); + NativePerformance?.clearMeasures(measureName); } /** @@ -234,28 +227,28 @@ export default class Performance { * https://www.w3.org/TR/performance-timeline/#extensions-to-the-performance-interface */ getEntries(): PerformanceEntryList { - if (!NativePerformanceObserver?.getEntries) { - warnNoNativePerformanceObserver(); + if (!NativePerformance?.getEntries) { + warnNoNativePerformance(); return []; } - return NativePerformanceObserver.getEntries().map(rawToPerformanceEntry); + return NativePerformance.getEntries().map(rawToPerformanceEntry); } getEntriesByType(entryType: PerformanceEntryType): PerformanceEntryList { - if (!ALWAYS_LOGGED_ENTRY_TYPES.includes(entryType)) { - console.warn( - `Performance.getEntriesByType: Only valid for ${JSON.stringify( - ALWAYS_LOGGED_ENTRY_TYPES, - )} entry types, got ${entryType}`, - ); + if ( + entryType != null && + !ENTRY_TYPES_AVAILABLE_FROM_TIMELINE.includes(entryType) + ) { + console.warn('Deprecated API for given entry type.'); return []; } - if (!NativePerformanceObserver?.getEntries) { - warnNoNativePerformanceObserver(); + if (!NativePerformance?.getEntriesByType) { + warnNoNativePerformance(); return []; } - return NativePerformanceObserver.getEntries( + + return NativePerformance.getEntriesByType( performanceEntryTypeToRaw(entryType), ).map(rawToPerformanceEntry); } @@ -265,24 +258,21 @@ export default class Performance { entryType?: PerformanceEntryType, ): PerformanceEntryList { if ( - entryType !== undefined && - !ALWAYS_LOGGED_ENTRY_TYPES.includes(entryType) + entryType != null && + !ENTRY_TYPES_AVAILABLE_FROM_TIMELINE.includes(entryType) ) { - console.warn( - `Performance.getEntriesByName: Only valid for ${JSON.stringify( - ALWAYS_LOGGED_ENTRY_TYPES, - )} entry types, got ${entryType}`, - ); + console.warn('Deprecated API for given entry type.'); return []; } - if (!NativePerformanceObserver?.getEntries) { - warnNoNativePerformanceObserver(); + if (!NativePerformance?.getEntriesByName) { + warnNoNativePerformance(); return []; } - return NativePerformanceObserver.getEntries( - entryType != null ? performanceEntryTypeToRaw(entryType) : undefined, + + return NativePerformance.getEntriesByName( entryName, + entryType != null ? performanceEntryTypeToRaw(entryType) : undefined, ).map(rawToPerformanceEntry); } } diff --git a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js index 4eca7229847..5e5cd830d01 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceEntry.js @@ -21,11 +21,6 @@ export type PerformanceEntryJSON = { ... }; -export const ALWAYS_LOGGED_ENTRY_TYPES: $ReadOnlyArray = [ - 'mark', - 'measure', -]; - export class PerformanceEntry { #name: string; #entryType: PerformanceEntryType; @@ -69,3 +64,5 @@ export class PerformanceEntry { }; } } + +export type PerformanceEntryList = $ReadOnlyArray; diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index e0cd7ebf53f..98c76f92351 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -11,20 +11,18 @@ import type { DOMHighResTimeStamp, PerformanceEntryType, + PerformanceEntryList, } from './PerformanceEntry'; import {PerformanceEventTiming} from './EventTiming'; -import {PerformanceEntry} from './PerformanceEntry'; import { performanceEntryTypeToRaw, rawToPerformanceEntry, rawToPerformanceEntryType, } from './RawPerformanceEntry'; -import NativePerformanceObserver from './specs/NativePerformanceObserver'; -import type {OpaqueNativeObserverHandle} from './specs/NativePerformanceObserver'; -import {warnNoNativePerformanceObserver} from './Utilities'; - -export type PerformanceEntryList = $ReadOnlyArray; +import NativePerformance from './specs/NativePerformance'; +import type {OpaqueNativeObserverHandle} from './specs/NativePerformance'; +import {warnNoNativePerformance} from './Utilities'; export {PerformanceEntry} from './PerformanceEntry'; @@ -76,15 +74,15 @@ export type PerformanceObserverInit = { }; function getSupportedPerformanceEntryTypes(): $ReadOnlyArray { - if (!NativePerformanceObserver) { + if (!NativePerformance) { return Object.freeze([]); } - if (!NativePerformanceObserver.getSupportedPerformanceEntryTypes) { + if (!NativePerformance.getSupportedPerformanceEntryTypes) { // fallback if getSupportedPerformanceEntryTypes is not defined on native side return Object.freeze(['mark', 'measure', 'event']); } return Object.freeze( - NativePerformanceObserver.getSupportedPerformanceEntryTypes().map( + NativePerformance.getSupportedPerformanceEntryTypes().map( rawToPerformanceEntryType, ), ); @@ -121,11 +119,8 @@ export class PerformanceObserver { } observe(options: PerformanceObserverInit): void { - if ( - !NativePerformanceObserver || - NativePerformanceObserver.observe == null - ) { - warnNoNativePerformanceObserver(); + if (!NativePerformance || NativePerformance.observe == null) { + warnNoNativePerformance(); return; } @@ -137,12 +132,12 @@ export class PerformanceObserver { if (options.entryTypes) { this.#type = 'multiple'; - NativePerformanceObserver.observe?.(this.#nativeObserverHandle, { + NativePerformance.observe?.(this.#nativeObserverHandle, { entryTypes: options.entryTypes.map(performanceEntryTypeToRaw), }); } else if (options.type) { this.#type = 'single'; - NativePerformanceObserver.observe?.(this.#nativeObserverHandle, { + NativePerformance.observe?.(this.#nativeObserverHandle, { type: performanceEntryTypeToRaw(options.type), buffered: options.buffered, durationThreshold: options.durationThreshold, @@ -151,35 +146,28 @@ export class PerformanceObserver { } disconnect(): void { - if (!NativePerformanceObserver) { - warnNoNativePerformanceObserver(); + if (!NativePerformance) { + warnNoNativePerformance(); return; } - if ( - this.#nativeObserverHandle == null || - !NativePerformanceObserver.disconnect - ) { + if (this.#nativeObserverHandle == null || !NativePerformance.disconnect) { return; } - NativePerformanceObserver.disconnect(this.#nativeObserverHandle); + NativePerformance.disconnect(this.#nativeObserverHandle); } #createNativeObserver(): OpaqueNativeObserverHandle { - if ( - !NativePerformanceObserver || - !NativePerformanceObserver.createObserver - ) { - warnNoNativePerformanceObserver(); + if (!NativePerformance || !NativePerformance.createObserver) { + warnNoNativePerformance(); return; } this.#calledAtLeastOnce = false; - return NativePerformanceObserver.createObserver(() => { - // $FlowNotNull - const rawEntries = NativePerformanceObserver.takeRecords?.( + return NativePerformance.createObserver(() => { + const rawEntries = NativePerformance.takeRecords?.( this.#nativeObserverHandle, true, // sort records ); @@ -193,7 +181,7 @@ export class PerformanceObserver { let droppedEntriesCount = 0; if (!this.#calledAtLeastOnce) { droppedEntriesCount = - NativePerformanceObserver.getDroppedEntriesCount?.( + NativePerformance.getDroppedEntriesCount?.( this.#nativeObserverHandle, ) ?? 0; this.#calledAtLeastOnce = true; diff --git a/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js b/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js index 3a4e540e275..7fe59110fe6 100644 --- a/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js +++ b/packages/react-native/src/private/webapis/performance/RawPerformanceEntry.js @@ -12,7 +12,7 @@ import type {PerformanceEntryType} from './PerformanceEntry'; import type { RawPerformanceEntry, RawPerformanceEntryType, -} from './specs/NativePerformanceObserver'; +} from './specs/NativePerformance'; import {PerformanceEventTiming} from './EventTiming'; import {PerformanceLongTaskTiming} from './LongTasks'; diff --git a/packages/react-native/src/private/webapis/performance/Utilities.js b/packages/react-native/src/private/webapis/performance/Utilities.js index cf4632e9933..890a93b9058 100644 --- a/packages/react-native/src/private/webapis/performance/Utilities.js +++ b/packages/react-native/src/private/webapis/performance/Utilities.js @@ -16,10 +16,3 @@ export function warnNoNativePerformance() { 'Missing native implementation of Performance', ); } - -export function warnNoNativePerformanceObserver() { - warnOnce( - 'missing-native-performance-observer', - 'Missing native implementation of PerformanceObserver', - ); -} 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 deleted file mode 100644 index 33f1cfe2bf1..00000000000 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * 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. - * - * @flow strict-local - * @format - * @oncall react_native - */ - -import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry'; - -jest.mock( - '../specs/NativePerformance', - () => require('../specs/__mocks__/NativePerformance').default, -); - -jest.mock( - '../specs/NativePerformanceObserver', - () => require('../specs/__mocks__/NativePerformanceObserver').default, -); - -// 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 logMockEntry = - require('../specs/__mocks__/NativePerformanceObserver').logMockEntry; - -describe('EventCounts', () => { - it('defines EventCounts for Performance', () => { - const eventCounts = new Performance().eventCounts; - expect(eventCounts).not.toBeUndefined(); - }); - - it('consistently implements the API for EventCounts', async () => { - let interactionId = 0; - const eventDefaultValues = { - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 100, - processingStart: 0, - processingEnd: 100, - }; - - logMockEntry({ - name: 'click', - ...eventDefaultValues, - interactionId: interactionId++, - }); - logMockEntry({ - name: 'input', - ...eventDefaultValues, - interactionId: interactionId++, - }); - logMockEntry({ - name: 'input', - ...eventDefaultValues, - interactionId: interactionId++, - }); - logMockEntry({ - name: 'keyup', - ...eventDefaultValues, - interactionId: interactionId++, - }); - logMockEntry({ - name: 'keyup', - ...eventDefaultValues, - interactionId: interactionId++, - }); - logMockEntry({ - name: 'keyup', - ...eventDefaultValues, - interactionId: interactionId++, - }); - - const eventCounts = new Performance().eventCounts; - expect(eventCounts.size).toBe(3); - expect(Array.from(eventCounts.entries())).toStrictEqual([ - ['click', 1], - ['input', 2], - ['keyup', 3], - ]); - - expect(eventCounts.get('click')).toEqual(1); - expect(eventCounts.get('input')).toEqual(2); - expect(eventCounts.get('keyup')).toEqual(3); - - expect(eventCounts.has('click')).toEqual(true); - expect(eventCounts.has('input')).toEqual(true); - expect(eventCounts.has('keyup')).toEqual(true); - - expect(Array.from(eventCounts.keys())).toStrictEqual([ - 'click', - 'input', - 'keyup', - ]); - expect(Array.from(eventCounts.values())).toStrictEqual([1, 2, 3]); - - await jest.runAllTicks(); - logMockEntry({ - name: 'input', - ...eventDefaultValues, - interactionId: interactionId++, - }); - logMockEntry({ - name: 'keyup', - ...eventDefaultValues, - interactionId: interactionId++, - }); - logMockEntry({ - name: 'keyup', - ...eventDefaultValues, - interactionId: interactionId++, - }); - expect(Array.from(eventCounts.values())).toStrictEqual([1, 3, 5]); - - await jest.runAllTicks(); - logMockEntry({ - name: 'click', - ...eventDefaultValues, - interactionId: interactionId++, - }); - - await jest.runAllTicks(); - - logMockEntry({ - name: 'keyup', - ...eventDefaultValues, - interactionId: interactionId++, - }); - - expect(Array.from(eventCounts.values())).toStrictEqual([2, 3, 6]); - }); -}); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/Performance-test.js b/packages/react-native/src/private/webapis/performance/__tests__/Performance-test.js index 00534bb8545..5074ca99287 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/Performance-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/Performance-test.js @@ -9,21 +9,89 @@ * @oncall react_native */ -const Performance = require('../Performance').default; +import {performanceEntryTypeToRaw} from '../RawPerformanceEntry'; +import {reportEntry} from '../specs/__mocks__/NativePerformanceMock'; -jest.mock( - '../specs/NativePerformance', - () => require('../specs/__mocks__/NativePerformance').default, +jest.mock('../specs/NativePerformance', () => + require('../specs/__mocks__/NativePerformanceMock'), ); -jest.mock( - '../specs/NativePerformanceObserver', - () => require('../specs/__mocks__/NativePerformanceObserver').default, -); +const NativePerformanceMock = + require('../specs/__mocks__/NativePerformanceMock').default; describe('Performance', () => { - it('clearEntries removes correct entry types', async () => { - const performance = new Performance(); + beforeEach(() => { + jest.resetModules(); + + const Performance = require('../Performance').default; + // $FlowExpectedError[cannot-write] + global.performance = new Performance(); + }); + + it('reports marks and measures', () => { + NativePerformanceMock.setCurrentTime(25); + + performance.mark('mark-now'); + performance.mark('mark-in-the-past', { + startTime: 10, + }); + performance.mark('mark-in-the-future', { + startTime: 50, + }); + performance.measure('measure-with-specific-time', { + start: 30, + duration: 4, + }); + performance.measure('measure-now-with-start-mark', 'mark-in-the-past'); + performance.measure( + 'measure-with-start-and-end-mark', + 'mark-in-the-past', + 'mark-in-the-future', + ); + + const entries = performance.getEntries(); + expect(entries.length).toBe(6); + expect(entries.map(entry => entry.toJSON())).toEqual([ + { + duration: 0, + entryType: 'mark', + name: 'mark-in-the-past', + startTime: 10, + }, + { + duration: -10, + entryType: 'measure', + name: 'measure-now-with-start-mark', + startTime: 10, + }, + { + duration: 40, + entryType: 'measure', + name: 'measure-with-start-and-end-mark', + startTime: 10, + }, + { + duration: 0, + entryType: 'mark', + name: 'mark-now', + startTime: 25, + }, + { + duration: 4, + entryType: 'measure', + name: 'measure-with-specific-time', + startTime: 30, + }, + { + duration: 0, + entryType: 'mark', + name: 'mark-in-the-future', + startTime: 50, + }, + ]); + }); + + it('clearMarks and clearMeasures remove correct entry types', async () => { performance.mark('entry1', {startTime: 0}); performance.mark('mark2', {startTime: 0}); @@ -53,7 +121,6 @@ describe('Performance', () => { }); it('getEntries only works with allowed entry types', async () => { - const performance = new Performance(); performance.clearMarks(); performance.clearMeasures(); @@ -82,7 +149,6 @@ describe('Performance', () => { }); it('getEntries works with marks and measures', async () => { - const performance = new Performance(); performance.clearMarks(); performance.clearMeasures(); @@ -115,4 +181,108 @@ describe('Performance', () => { performance.getEntriesByName('entry1', 'measure').map(e => e.entryType), ).toStrictEqual(['measure']); }); + + it('defines EventCounts for Performance', () => { + expect(performance.eventCounts).not.toBeUndefined(); + }); + + it('consistently implements the API for EventCounts', async () => { + let interactionId = 0; + const eventDefaultValues = { + entryType: performanceEntryTypeToRaw('event'), + startTime: 0, // startTime + duration: 100, // duration + processingStart: 0, // processing start + processingEnd: 100, // processingEnd + }; + + reportEntry({ + name: 'click', + ...eventDefaultValues, + interactionId: interactionId++, + }); + reportEntry({ + name: 'input', + ...eventDefaultValues, + interactionId: interactionId++, + }); + reportEntry({ + name: 'input', + ...eventDefaultValues, + interactionId: interactionId++, + }); + reportEntry({ + name: 'keyup', + ...eventDefaultValues, + interactionId: interactionId++, + }); + reportEntry({ + name: 'keyup', + ...eventDefaultValues, + interactionId: interactionId++, + }); + reportEntry({ + name: 'keyup', + ...eventDefaultValues, + interactionId: interactionId++, + }); + + const eventCounts = performance.eventCounts; + expect(eventCounts.size).toBe(3); + expect(Array.from(eventCounts.entries())).toStrictEqual([ + ['click', 1], + ['input', 2], + ['keyup', 3], + ]); + + expect(eventCounts.get('click')).toEqual(1); + expect(eventCounts.get('input')).toEqual(2); + expect(eventCounts.get('keyup')).toEqual(3); + + expect(eventCounts.has('click')).toEqual(true); + expect(eventCounts.has('input')).toEqual(true); + expect(eventCounts.has('keyup')).toEqual(true); + + expect(Array.from(eventCounts.keys())).toStrictEqual([ + 'click', + 'input', + 'keyup', + ]); + expect(Array.from(eventCounts.values())).toStrictEqual([1, 2, 3]); + + await jest.runAllTicks(); + reportEntry({ + name: 'input', + ...eventDefaultValues, + interactionId: interactionId++, + }); + reportEntry({ + name: 'keyup', + ...eventDefaultValues, + interactionId: interactionId++, + }); + reportEntry({ + name: 'keyup', + ...eventDefaultValues, + interactionId: interactionId++, + }); + expect(Array.from(eventCounts.values())).toStrictEqual([1, 3, 5]); + + await jest.runAllTicks(); + reportEntry({ + name: 'click', + ...eventDefaultValues, + interactionId: interactionId++, + }); + + await jest.runAllTicks(); + + reportEntry({ + name: 'keyup', + ...eventDefaultValues, + interactionId: 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 4c26bc0ca04..cdd73dcfe6b 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 @@ -9,30 +9,26 @@ * @oncall react_native */ -import type {PerformanceEntryList} from '../PerformanceObserver'; - jest.mock( '../specs/NativePerformance', - () => require('../specs/__mocks__/NativePerformance').default, + () => require('../specs/__mocks__/NativePerformanceMock').default, ); -jest.mock( - '../specs/NativePerformanceObserver', - () => require('../specs/__mocks__/NativePerformanceObserver').default, -); - -// // 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', () => { + beforeEach(() => { + jest.resetModules(); + + const Performance = require('../Performance').default; + // $FlowExpectedError[cannot-write] + global.performance = new Performance(); + global.PerformanceObserver = + require('../PerformanceObserver').PerformanceObserver; + }); + it('prevents durationThreshold to be used together with entryTypes', async () => { const observer = new PerformanceObserver((list, _observer) => {}); expect(() => - // $FlowExpectedError[incompatible-call] observer.observe({entryTypes: ['event', 'mark'], durationThreshold: 100}), ).toThrow(); }); @@ -46,7 +42,10 @@ describe('PerformanceObserver', () => { observer.observe({type: 'measure', durationThreshold: 100}); - NativePerformanceMock?.measure('measure1', 0, 200); + performance.measure('measure1', { + start: 0, + duration: 10, + }); await jest.runAllTicks(); expect(entries).toHaveLength(1); 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 b05f2e74e4e..49ef8985270 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -16,6 +16,31 @@ export type NativeMemoryInfo = {[key: string]: ?number}; export type ReactNativeStartupTiming = {[key: string]: ?number}; +export type RawPerformanceEntryType = number; + +export type RawPerformanceEntry = { + name: string, + entryType: RawPerformanceEntryType, + startTime: number, + duration: number, + + // For "event" entries only: + processingStart?: number, + processingEnd?: number, + interactionId?: number, +}; + +export type OpaqueNativeObserverHandle = mixed; + +export type NativeBatchedObserverCallback = () => void; + +export type PerformanceObserverInit = { + entryTypes?: $ReadOnlyArray, + type?: number, + buffered?: boolean, + durationThreshold?: number, +}; + export interface Spec extends TurboModule { +now?: () => number; +mark: (name: string, startTime: number) => void; @@ -27,8 +52,36 @@ export interface Spec extends TurboModule { startMark?: string, endMark?: string, ) => void; + +clearMarks?: (entryName?: string) => void; + +clearMeasures?: (entryName?: string) => void; + +getEntries?: () => $ReadOnlyArray; + +getEntriesByName?: ( + entryName: string, + entryType?: ?RawPerformanceEntryType, + ) => $ReadOnlyArray; + +getEntriesByType?: ( + entryType: RawPerformanceEntryType, + ) => $ReadOnlyArray; + +getEventCounts?: () => $ReadOnlyArray<[string, number]>; +getSimpleMemoryInfo: () => NativeMemoryInfo; +getReactNativeStartupTiming: () => ReactNativeStartupTiming; + + +createObserver?: ( + callback: NativeBatchedObserverCallback, + ) => OpaqueNativeObserverHandle; + +getDroppedEntriesCount?: (observer: OpaqueNativeObserverHandle) => number; + + +observe?: ( + observer: OpaqueNativeObserverHandle, + options: PerformanceObserverInit, + ) => void; + +disconnect?: (observer: OpaqueNativeObserverHandle) => void; + +takeRecords?: ( + observer: OpaqueNativeObserverHandle, + sort: boolean, + ) => $ReadOnlyArray; + + +getSupportedPerformanceEntryTypes?: () => $ReadOnlyArray; } export default (TurboModuleRegistry.get('NativePerformanceCxx'): ?Spec); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js deleted file mode 100644 index f06cacd104a..00000000000 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * 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. - * - * @flow strict - * @format - */ - -import type {TurboModule} from '../../../../../Libraries/TurboModule/RCTExport'; - -import * as TurboModuleRegistry from '../../../../../Libraries/TurboModule/TurboModuleRegistry'; - -export type RawPerformanceEntryType = number; - -export type OpaqueNativeObserverHandle = mixed; - -export type NativeBatchedObserverCallback = () => void; - -export type RawPerformanceEntry = {| - name: string, - entryType: RawPerformanceEntryType, - startTime: number, - duration: number, - // For "event" entries only: - processingStart?: number, - processingEnd?: number, - interactionId?: number, -|}; - -export type PerformanceObserverInit = { - entryTypes?: $ReadOnlyArray, - type?: number, - buffered?: boolean, - durationThreshold?: number, -}; - -export interface Spec extends TurboModule { - +getEventCounts: () => $ReadOnlyArray<[string, number]>; - +createObserver?: ( - callback: NativeBatchedObserverCallback, - ) => OpaqueNativeObserverHandle; - +getDroppedEntriesCount?: (observer: OpaqueNativeObserverHandle) => number; - - +observe?: ( - observer: OpaqueNativeObserverHandle, - options: PerformanceObserverInit, - ) => void; - +disconnect?: (observer: OpaqueNativeObserverHandle) => void; - +takeRecords?: ( - observer: OpaqueNativeObserverHandle, - sort: boolean, - ) => $ReadOnlyArray; - - +clearEntries: ( - entryType?: RawPerformanceEntryType, - entryName?: string, - ) => void; - +getEntries: ( - entryType?: RawPerformanceEntryType, - entryName?: string, - ) => $ReadOnlyArray; - +getSupportedPerformanceEntryTypes: () => $ReadOnlyArray; -} - -export default (TurboModuleRegistry.get( - 'NativePerformanceObserverCxx', -): ?Spec); 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 deleted file mode 100644 index 3389da339be..00000000000 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * 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. - * - * @flow strict - * @format - */ - -import type { - NativeMemoryInfo, - ReactNativeStartupTiming, -} from '../NativePerformance'; - -import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; -import NativePerformance from '../NativePerformance'; -import {logMockEntry} from './NativePerformanceObserver'; - -const marks: Map = new Map(); - -const NativePerformanceMock: typeof NativePerformance = { - mark: (name: string, startTime: number): void => { - NativePerformance?.mark(name, startTime); - marks.set(name, startTime); - logMockEntry({ - entryType: RawPerformanceEntryTypeValues.MARK, - name, - startTime, - duration: 0, - }); - }, - - measure: ( - name: string, - startTime: number, - endTime: number, - duration?: number, - startMark?: string, - endMark?: string, - ): void => { - const start = startMark != null ? marks.get(startMark) ?? 0 : startTime; - const end = endMark != null ? marks.get(endMark) ?? 0 : endTime; - NativePerformance?.measure(name, start, end); - logMockEntry({ - entryType: RawPerformanceEntryTypeValues.MEASURE, - name, - startTime: start, - duration: duration ?? end - start, - }); - }, - - getSimpleMemoryInfo: (): NativeMemoryInfo => { - return {}; - }, - - getReactNativeStartupTiming: (): ReactNativeStartupTiming => { - return { - startTime: 0, - endTime: 0, - executeJavaScriptBundleEntryPointStart: 0, - executeJavaScriptBundleEntryPointEnd: 0, - initializeRuntimeStart: 0, - initializeRuntimeEnd: 0, - }; - }, -}; - -export default NativePerformanceMock; diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceMock.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceMock.js new file mode 100644 index 00000000000..3195a42bc57 --- /dev/null +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceMock.js @@ -0,0 +1,247 @@ +/** + * 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. + * + * @flow strict + * @format + */ + +import type { + NativeBatchedObserverCallback, + NativeMemoryInfo, + RawPerformanceEntry, + ReactNativeStartupTiming, + PerformanceObserverInit, + OpaqueNativeObserverHandle, + RawPerformanceEntryType, +} from '../NativePerformance'; + +import typeof NativePerformance from '../NativePerformance'; + +import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; + +type MockObserver = { + handleEntry: (entry: RawPerformanceEntry) => void, + callback: NativeBatchedObserverCallback, + didScheduleFlushBuffer: boolean, + entries: Array, + options: PerformanceObserverInit, + droppedEntriesCount: number, +}; + +const eventCounts: Map = new Map(); +const observers: Set = new Set(); +const marks: Map = new Map(); +let entries: Array = []; + +function getMockObserver( + opaqueNativeObserverHandle: OpaqueNativeObserverHandle, +): MockObserver { + return opaqueNativeObserverHandle as $FlowFixMe as MockObserver; +} + +function createMockObserver(callback: NativeBatchedObserverCallback) { + const observer: MockObserver = { + callback, + didScheduleFlushBuffer: false, + entries: [], + options: {}, + droppedEntriesCount: 0, + handleEntry: (entry: RawPerformanceEntry) => { + if ( + observer.options.type !== entry.entryType && + !observer.options.entryTypes?.includes(entry.entryType) + ) { + return; + } + + if ( + entry.entryType === RawPerformanceEntryTypeValues.EVENT && + entry.duration < (observer.options?.durationThreshold ?? 0) + ) { + return; + } + + observer.entries.push(entry); + + if (!observer.didScheduleFlushBuffer) { + observer.didScheduleFlushBuffer = true; + // $FlowFixMe[incompatible-call] + global.queueMicrotask(() => { + observer.didScheduleFlushBuffer = false; + // We want to emulate the way it's done in native (i.e. async/batched) + observer.callback(); + }); + } + }, + }; + + return observer; +} + +export function reportEntry(entry: RawPerformanceEntry) { + entries.push(entry); + + switch (entry.entryType) { + case RawPerformanceEntryTypeValues.MARK: + marks.set(entry.name, entry.startTime); + break; + case RawPerformanceEntryTypeValues.MEASURE: + break; + case RawPerformanceEntryTypeValues.EVENT: + eventCounts.set(entry.name, (eventCounts.get(entry.name) ?? 0) + 1); + break; + } + + for (const observer of observers) { + observer.handleEntry(entry); + } +} + +let currentTime: number = 12; + +const NativePerformanceMock = { + setCurrentTime: (time: number): void => { + currentTime = time; + }, + + now: (): number => currentTime, + + mark: (name: string, startTime: number): void => { + marks.set(name, startTime); + reportEntry({ + entryType: RawPerformanceEntryTypeValues.MARK, + name, + startTime, + duration: 0, + }); + }, + + measure: ( + name: string, + startTime: number, + endTime: number, + duration?: number, + startMark?: string, + endMark?: string, + ): void => { + const start = startMark != null ? marks.get(startMark) ?? 0 : startTime; + const end = endMark != null ? marks.get(endMark) ?? 0 : endTime; + reportEntry({ + entryType: RawPerformanceEntryTypeValues.MEASURE, + name, + startTime: start, + duration: duration ?? end - start, + }); + }, + + getSimpleMemoryInfo: (): NativeMemoryInfo => { + return {}; + }, + + getReactNativeStartupTiming: (): ReactNativeStartupTiming => { + return { + startTime: 0, + endTime: 0, + executeJavaScriptBundleEntryPointStart: 0, + executeJavaScriptBundleEntryPointEnd: 0, + initializeRuntimeStart: 0, + initializeRuntimeEnd: 0, + }; + }, + + getEventCounts: (): $ReadOnlyArray<[string, number]> => { + return Array.from(eventCounts.entries()); + }, + + createObserver: ( + callback: NativeBatchedObserverCallback, + ): OpaqueNativeObserverHandle => { + return createMockObserver(callback); + }, + + getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle): number => { + return getMockObserver(observer).droppedEntriesCount; + }, + + observe: ( + observer: OpaqueNativeObserverHandle, + options: PerformanceObserverInit, + ): void => { + const mockObserver = getMockObserver(observer); + mockObserver.options = options; + observers.add(mockObserver); + }, + + disconnect: (observer: OpaqueNativeObserverHandle): void => { + const mockObserver = getMockObserver(observer); + observers.delete(mockObserver); + }, + + takeRecords: ( + observer: OpaqueNativeObserverHandle, + ): $ReadOnlyArray => { + const mockObserver = getMockObserver(observer); + const observerEntries = mockObserver.entries; + mockObserver.entries = []; + return observerEntries.sort((a, b) => a.startTime - b.startTime); + }, + + clearMarks: (entryName?: string) => { + if (entryName != null) { + marks.delete(entryName); + } else { + marks.clear(); + } + + entries = entries.filter( + entry => + entry.entryType !== RawPerformanceEntryTypeValues.MARK || + (entryName != null && entry.name !== entryName), + ); + }, + + clearMeasures: (entryName?: string) => { + entries = entries.filter( + entry => + entry.entryType !== RawPerformanceEntryTypeValues.MEASURE || + (entryName != null && entry.name !== entryName), + ); + }, + + getEntries: (): $ReadOnlyArray => { + return [...entries].sort((a, b) => a.startTime - b.startTime); + }, + + getEntriesByName: ( + entryName: string, + entryType?: ?RawPerformanceEntryType, + ): $ReadOnlyArray => { + return NativePerformanceMock.getEntries().filter( + entry => + (entryType == null || entry.entryType === entryType) && + entry.name === entryName, + ); + }, + + getEntriesByType: ( + entryType: RawPerformanceEntryType, + ): $ReadOnlyArray => { + return entries.filter(entry => entry.entryType === entryType); + }, + + getSupportedPerformanceEntryTypes: + (): $ReadOnlyArray => { + return [ + RawPerformanceEntryTypeValues.MARK, + RawPerformanceEntryTypeValues.MEASURE, + RawPerformanceEntryTypeValues.EVENT, + ]; + }, +}; + +(NativePerformanceMock: NativePerformance); + +export default NativePerformanceMock; 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 deleted file mode 100644 index a9f95ecea4a..00000000000 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * 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. - * - * @flow strict - * @format - */ - -import type { - NativeBatchedObserverCallback, - RawPerformanceEntry, - RawPerformanceEntryType, - OpaqueNativeObserverHandle, - PerformanceObserverInit, - Spec as NativePerformanceObserver, -} from '../NativePerformanceObserver'; - -import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; - -jest.mock( - '../NativePerformance', - () => require('../__mocks__/NativePerformance').default, -); - -jest.mock( - '../NativePerformanceObserver', - () => require('../__mocks__/NativePerformanceObserver').default, -); - -const eventCounts: Map = new Map(); -let observers: MockObserver[] = []; -let entries: Array = []; - -export function logMockEntry(entry: RawPerformanceEntry) { - entries.push(entry); - - if (entry.entryType === RawPerformanceEntryTypeValues.EVENT) { - eventCounts.set(entry.name, (eventCounts.get(entry.name) ?? 0) + 1); - } - - for (const observer of observers) { - if ( - observer.options.type !== entry.entryType && - !observer.options.entryTypes?.includes(entry.entryType) - ) { - continue; - } - - if (entry.entryType === RawPerformanceEntryTypeValues.EVENT) { - const {durationThreshold = 0} = observer.options; - if (durationThreshold > 0 && entry.duration < durationThreshold) { - continue; - } - } - - observer.entries.push(entry); - - // $FlowFixMe[incompatible-call] - global.queueMicrotask(() => { - // We want to emulate the way it's done in native (i.e. async/batched) - observer.callback(); - }); - } -} - -type MockObserver = { - callback: NativeBatchedObserverCallback, - entries: Array, - options: PerformanceObserverInit, - droppedEntriesCount: number, -}; - -const NativePerformanceObserverMock: NativePerformanceObserver = { - getEventCounts: (): $ReadOnlyArray<[string, number]> => { - return Array.from(eventCounts.entries()); - }, - - createObserver: ( - callback: NativeBatchedObserverCallback, - ): OpaqueNativeObserverHandle => { - const observer: MockObserver = { - callback, - entries: [], - options: {}, - droppedEntriesCount: 0, - }; - - return observer; - }, - - getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle): number => { - // $FlowFixMe - const mockObserver = (observer: any) as MockObserver; - return mockObserver.droppedEntriesCount; - }, - - observe: ( - observer: OpaqueNativeObserverHandle, - options: PerformanceObserverInit, - ): void => { - // $FlowFixMe - const mockObserver = (observer: any) as MockObserver; - mockObserver.options = options; - observers.push(mockObserver); - }, - - disconnect: (observer: OpaqueNativeObserverHandle): void => { - // $FlowFixMe - const mockObserver = (observer: any) as MockObserver; - observers = observers.filter(e => e !== mockObserver); - }, - - takeRecords: ( - observer: OpaqueNativeObserverHandle, - ): $ReadOnlyArray => { - // $FlowFixMe - const mockObserver = (observer: any) as MockObserver; - const observerEntries = mockObserver.entries; - mockObserver.entries = []; - return observerEntries; - }, - - clearEntries: (entryType?: RawPerformanceEntryType, entryName?: string) => { - entries = entries.filter( - e => - (entryType != null && e.entryType !== entryType) || - (entryName != null && e.name !== entryName), - ); - }, - - getEntries: ( - entryType?: RawPerformanceEntryType, - entryName?: string, - ): $ReadOnlyArray => { - return entries.filter( - e => - (entryType == null || e.entryType === entryType) && - (entryName == null || e.name === entryName), - ); - }, - - getSupportedPerformanceEntryTypes: - (): $ReadOnlyArray => { - return [ - RawPerformanceEntryTypeValues.MARK, - RawPerformanceEntryTypeValues.MEASURE, - RawPerformanceEntryTypeValues.EVENT, - ]; - }, -}; - -export default NativePerformanceObserverMock; 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 deleted file mode 100644 index ac41cbc9b48..00000000000 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * 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. - * - * @flow strict-local - * @format - * @oncall react_native - */ - -import type {PerformanceEntryList} from '../../PerformanceObserver'; - -jest.mock( - '../NativePerformanceObserver', - () => require('../__mocks__/NativePerformanceObserver').default, -); - -const NativePerformanceMock = require('../__mocks__/NativePerformance').default; -const PerformanceObserver = - require('../../PerformanceObserver').PerformanceObserver; - -describe('NativePerformanceMock', () => { - it('marks get reported', async () => { - let entries: PerformanceEntryList = []; - const observer = new PerformanceObserver((list, _observer) => { - entries = [...entries, ...list.getEntries()]; - }); - - observer.observe({type: 'mark'}); - - NativePerformanceMock?.mark('mark1', 0); - NativePerformanceMock?.mark('mark2', 5); - NativePerformanceMock?.mark('mark3', 10); - - await jest.runAllTicks(); - expect(entries).toHaveLength(3); - expect(entries.map(e => e.name)).toStrictEqual(['mark1', 'mark2', 'mark3']); - expect(entries.map(e => e.startTime)).toStrictEqual([0, 5, 10]); - }); - - it('measures get reported', async () => { - let entries: PerformanceEntryList = []; - const observer = new PerformanceObserver((list, _observer) => { - entries = [...entries, ...list.getEntries()]; - }); - - observer.observe({entryTypes: ['measure']}); - - 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( - 'measure2', - 0, - 0, - undefined, - 'mark1', - 'mark2', - ); - NativePerformanceMock?.measure('measure3', 0, 0, 5, 'mark1'); - NativePerformanceMock?.measure( - 'measure4', - 1.5, - 0, - undefined, - undefined, - 'mark2', - ); - - await jest.runAllTicks(); - expect(entries).toHaveLength(5); - expect(entries.map(e => e.name)).toStrictEqual([ - 'measure0', - 'measure1', - 'measure2', - 'measure3', - 'measure4', - ]); - expect(entries.map(e => e.startTime)).toStrictEqual([0, 0, 1, 1, 1.5]); - expect(entries.map(e => e.duration)).toStrictEqual([2, 4, 1, 5, 0.5]); - }); -}); 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 deleted file mode 100644 index 6753251ec3c..00000000000 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 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. - * - * @flow strict-local - * @format - * @oncall react_native - */ - -import NativePerformanceObserverMock, { - logMockEntry, -} from '../__mocks__/NativePerformanceObserver'; -import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; - -describe('NativePerformanceObserver', () => { - it('correctly clears/gets entries', async () => { - logMockEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 0, - }); - - logMockEntry({ - name: 'event1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 0, - }); - - expect( - NativePerformanceObserverMock.getEntries().map(e => e.name), - ).toStrictEqual(['mark1', 'event1']); - - NativePerformanceObserverMock.clearEntries(); - - expect(NativePerformanceObserverMock.getEntries()).toStrictEqual([]); - - logMockEntry({ - name: 'entry1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 0, - }); - - logMockEntry({ - name: 'entry2', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 0, - }); - - logMockEntry({ - name: 'entry1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 0, - }); - - NativePerformanceObserverMock.clearEntries(undefined, 'entry1'); - - expect( - NativePerformanceObserverMock.getEntries().map(e => e.name), - ).toStrictEqual(['entry2']); - }); -});