mirror of
https://github.com/facebook/react-native.git
synced 2024-11-21 12:39:27 +00:00
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
This commit is contained in:
parent
b62ae0d02b
commit
559c6029fd
@ -63,7 +63,6 @@ module.exports = class ReactNativeEnvironment extends NodeEnv {
|
||||
Networking: {},
|
||||
ImageLoader: {},
|
||||
NativePerformanceCxx: {},
|
||||
NativePerformanceObserverCxx: {},
|
||||
LogBox: {},
|
||||
SettingsManager: {
|
||||
getConstants: () => ({settings: {}}),
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include <cxxreact/ReactMarker.h>
|
||||
#include <jsi/instrumentation.h>
|
||||
#include <react/performance/timeline/PerformanceEntryReporter.h>
|
||||
#include <react/performance/timeline/PerformanceObserver.h>
|
||||
|
||||
#if __has_include(<reactperflogger/fusebox/FuseboxTracer.h>)
|
||||
#include <reactperflogger/fusebox/FuseboxTracer.h>
|
||||
#define HAS_FUSEBOX
|
||||
@ -71,6 +73,40 @@ std::tuple<std::string, std::string_view> parseTrackName(
|
||||
return std::make_tuple(trackNameRef, eventName);
|
||||
}
|
||||
|
||||
class PerformanceObserverWrapper : public jsi::NativeState {
|
||||
public:
|
||||
explicit PerformanceObserverWrapper(
|
||||
const std::shared_ptr<PerformanceObserver> observer)
|
||||
: observer(observer) {}
|
||||
|
||||
const std::shared_ptr<PerformanceObserver> observer;
|
||||
};
|
||||
|
||||
void sortEntries(std::vector<PerformanceEntry>& entries) {
|
||||
return std::stable_sort(
|
||||
entries.begin(), entries.end(), PerformanceEntrySorter{});
|
||||
}
|
||||
|
||||
const std::array<PerformanceEntryType, 2> ENTRY_TYPES_AVAILABLE_FROM_TIMELINE{
|
||||
{PerformanceEntryType::MARK, PerformanceEntryType::MEASURE}};
|
||||
|
||||
bool isAvailableFromTimeline(PerformanceEntryType entryType) {
|
||||
return entryType == PerformanceEntryType::MARK ||
|
||||
entryType == PerformanceEntryType::MEASURE;
|
||||
}
|
||||
|
||||
std::shared_ptr<PerformanceObserver> tryGetObserver(
|
||||
jsi::Runtime& rt,
|
||||
jsi::Object& observerObj) {
|
||||
if (!observerObj.hasNativeState(rt)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
|
||||
observerObj.getNativeState(rt));
|
||||
return observerWrapper ? observerWrapper->observer : nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
|
||||
@ -137,6 +173,87 @@ void NativePerformance::measure(
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativePerformance::clearMarks(
|
||||
jsi::Runtime& /*rt*/,
|
||||
std::optional<std::string> entryName) {
|
||||
if (entryName) {
|
||||
PerformanceEntryReporter::getInstance()->clearEntries(
|
||||
PerformanceEntryType::MARK, *entryName);
|
||||
} else {
|
||||
PerformanceEntryReporter::getInstance()->clearEntries(
|
||||
PerformanceEntryType::MARK);
|
||||
}
|
||||
}
|
||||
|
||||
void NativePerformance::clearMeasures(
|
||||
jsi::Runtime& /*rt*/,
|
||||
std::optional<std::string> entryName) {
|
||||
if (entryName) {
|
||||
PerformanceEntryReporter::getInstance()->clearEntries(
|
||||
PerformanceEntryType::MEASURE, *entryName);
|
||||
} else {
|
||||
PerformanceEntryReporter::getInstance()->clearEntries(
|
||||
PerformanceEntryType::MEASURE);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> NativePerformance::getEntries(
|
||||
jsi::Runtime& /*rt*/) {
|
||||
std::vector<PerformanceEntry> entries;
|
||||
|
||||
for (auto entryType : ENTRY_TYPES_AVAILABLE_FROM_TIMELINE) {
|
||||
PerformanceEntryReporter::getInstance()->getEntries(entries, entryType);
|
||||
}
|
||||
|
||||
sortEntries(entries);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> NativePerformance::getEntriesByName(
|
||||
jsi::Runtime& /*rt*/,
|
||||
std::string entryName,
|
||||
std::optional<PerformanceEntryType> entryType) {
|
||||
std::vector<PerformanceEntry> 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<PerformanceEntry> NativePerformance::getEntriesByType(
|
||||
jsi::Runtime& /*rt*/,
|
||||
PerformanceEntryType entryType) {
|
||||
std::vector<PerformanceEntry> entries;
|
||||
|
||||
if (isAvailableFromTimeline(entryType)) {
|
||||
PerformanceEntryReporter::getInstance()->getEntries(entries, entryType);
|
||||
}
|
||||
|
||||
sortEntries(entries);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, uint32_t>> NativePerformance::getEventCounts(
|
||||
jsi::Runtime& /*rt*/) {
|
||||
const auto& eventCounts =
|
||||
PerformanceEntryReporter::getInstance()->getEventCounts();
|
||||
return {eventCounts.begin(), eventCounts.end()};
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, double> 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<PerformanceObserverWrapper>(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<PerformanceEntryType> entryTypes;
|
||||
auto rawTypes = options.entryTypes.value();
|
||||
|
||||
for (auto rawType : rawTypes) {
|
||||
entryTypes.insert(Bridging<PerformanceEntryType>::fromJs(rt, rawType));
|
||||
}
|
||||
|
||||
observer->observe(entryTypes);
|
||||
} else { // single
|
||||
auto buffered = options.buffered.value_or(false);
|
||||
if (options.type.has_value()) {
|
||||
observer->observe(
|
||||
static_cast<PerformanceEntryType>(options.type.value()),
|
||||
{.buffered = buffered, .durationThreshold = durationThreshold});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativePerformance::disconnect(jsi::Runtime& rt, jsi::Object observerObj) {
|
||||
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
|
||||
observerObj.getNativeState(rt));
|
||||
|
||||
if (!observerWrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto observer = observerWrapper->observer;
|
||||
observer->disconnect();
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> NativePerformance::takeRecords(
|
||||
jsi::Runtime& rt,
|
||||
jsi::Object observerObj,
|
||||
bool sort) {
|
||||
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
|
||||
observerObj.getNativeState(rt));
|
||||
|
||||
if (!observerWrapper) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto observer = observerWrapper->observer;
|
||||
auto records = observer->takeRecords();
|
||||
if (sort) {
|
||||
sortEntries(records);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntryType>
|
||||
NativePerformance::getSupportedPerformanceEntryTypes(jsi::Runtime& /*rt*/) {
|
||||
auto supportedEntryTypes = PerformanceEntryReporter::getSupportedEntryTypes();
|
||||
return {supportedEntryTypes.begin(), supportedEntryTypes.end()};
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
@ -14,19 +14,65 @@
|
||||
#else
|
||||
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
|
||||
#endif
|
||||
|
||||
#include <react/performance/timeline/PerformanceEntry.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
using NativePerformancePerformanceObserverCallback = AsyncCallback<>;
|
||||
using NativePerformancePerformanceObserverObserveOptions =
|
||||
NativePerformancePerformanceObserverInit<
|
||||
// entryTypes
|
||||
std::optional<std::vector<int>>,
|
||||
// type
|
||||
std::optional<int>,
|
||||
// buffered
|
||||
std::optional<bool>,
|
||||
// durationThreshold
|
||||
std::optional<double>>;
|
||||
|
||||
template <>
|
||||
struct Bridging<PerformanceEntryType> {
|
||||
static PerformanceEntryType fromJs(
|
||||
jsi::Runtime& /*rt*/,
|
||||
const jsi::Value& value) {
|
||||
return static_cast<PerformanceEntryType>(value.asNumber());
|
||||
}
|
||||
|
||||
static jsi::Value toJs(
|
||||
jsi::Runtime& /*rt*/,
|
||||
const PerformanceEntryType& value) {
|
||||
return {static_cast<int>(value)};
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Bridging<PerformanceEntry>
|
||||
: NativePerformanceRawPerformanceEntryBridging<PerformanceEntry> {};
|
||||
|
||||
template <>
|
||||
struct Bridging<NativePerformancePerformanceObserverObserveOptions>
|
||||
: NativePerformancePerformanceObserverInitBridging<
|
||||
NativePerformancePerformanceObserverObserveOptions> {};
|
||||
|
||||
class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
|
||||
public:
|
||||
NativePerformance(std::shared_ptr<CallInvoker> 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<NativePerformance> {
|
||||
std::optional<std::string> startMark,
|
||||
std::optional<std::string> endMark);
|
||||
|
||||
// https://w3c.github.io/user-timing/#clearmarks-method
|
||||
void clearMarks(
|
||||
jsi::Runtime& rt,
|
||||
std::optional<std::string> entryName = std::nullopt);
|
||||
|
||||
// https://w3c.github.io/user-timing/#clearmeasures-method
|
||||
void clearMeasures(
|
||||
jsi::Runtime& rt,
|
||||
std::optional<std::string> 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<PerformanceEntry> getEntries(jsi::Runtime& rt);
|
||||
|
||||
// https://www.w3.org/TR/performance-timeline/#getentriesbytype-method
|
||||
std::vector<PerformanceEntry> getEntriesByType(
|
||||
jsi::Runtime& rt,
|
||||
PerformanceEntryType entryType);
|
||||
|
||||
// https://www.w3.org/TR/performance-timeline/#getentriesbyname-method
|
||||
std::vector<PerformanceEntry> getEntriesByName(
|
||||
jsi::Runtime& rt,
|
||||
std::string entryName,
|
||||
std::optional<PerformanceEntryType> 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<PerformanceEntry> 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<PerformanceEntryType> 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<std::pair<std::string, uint32_t>> 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<NativePerformance> {
|
||||
// for heap size information, as double's 2^53 sig bytes is large enough.
|
||||
std::unordered_map<std::string, double> 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<std::string, double> getReactNativeStartupTiming(
|
||||
|
@ -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 <jsi/jsi.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#include <react/performance/timeline/PerformanceEntryReporter.h>
|
||||
#include <react/performance/timeline/PerformanceObserver.h>
|
||||
#include <react/renderer/uimanager/UIManagerBinding.h>
|
||||
#include <react/utils/CoreFeatures.h>
|
||||
#include <memory>
|
||||
|
||||
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
|
||||
#include "Plugins.h"
|
||||
#endif
|
||||
|
||||
std::shared_ptr<facebook::react::TurboModule>
|
||||
NativePerformanceObserverModuleProvider(
|
||||
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
||||
return std::make_shared<facebook::react::NativePerformanceObserver>(
|
||||
std::move(jsInvoker));
|
||||
}
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class PerformanceObserverWrapper : public jsi::NativeState {
|
||||
public:
|
||||
explicit PerformanceObserverWrapper(
|
||||
const std::shared_ptr<PerformanceObserver> observer)
|
||||
: observer(observer) {}
|
||||
|
||||
std::shared_ptr<PerformanceObserver> observer;
|
||||
};
|
||||
|
||||
NativePerformanceObserver::NativePerformanceObserver(
|
||||
std::shared_ptr<CallInvoker> 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<PerformanceObserverWrapper>(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<PerformanceObserverWrapper>(
|
||||
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<PerformanceObserverWrapper>(
|
||||
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<PerformanceEntryType> entryTypes;
|
||||
auto rawTypes = options.entryTypes.value();
|
||||
|
||||
for (auto rawType : rawTypes) {
|
||||
entryTypes.insert(Bridging<PerformanceEntryType>::fromJs(rt, rawType));
|
||||
}
|
||||
|
||||
observer->observe(entryTypes);
|
||||
} else { // single
|
||||
auto buffered = options.buffered.value_or(false);
|
||||
if (options.type.has_value()) {
|
||||
observer->observe(
|
||||
static_cast<PerformanceEntryType>(options.type.value()),
|
||||
{.buffered = buffered, .durationThreshold = durationThreshold});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NativePerformanceObserver::disconnect(
|
||||
jsi::Runtime& rt,
|
||||
jsi::Object observerObj) {
|
||||
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
|
||||
observerObj.getNativeState(rt));
|
||||
|
||||
if (!observerWrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto observer = observerWrapper->observer;
|
||||
observer->disconnect();
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> NativePerformanceObserver::takeRecords(
|
||||
jsi::Runtime& rt,
|
||||
jsi::Object observerObj,
|
||||
bool sort) {
|
||||
auto observerWrapper = std::dynamic_pointer_cast<PerformanceObserverWrapper>(
|
||||
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<std::string> entryName) {
|
||||
PerformanceEntryReporter::getInstance()->clearEntries(entryType, entryName);
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> NativePerformanceObserver::getEntries(
|
||||
jsi::Runtime& /*rt*/,
|
||||
std::optional<PerformanceEntryType> entryType,
|
||||
std::optional<std::string> entryName) {
|
||||
const auto reporter = PerformanceEntryReporter::getInstance();
|
||||
|
||||
std::vector<PerformanceEntry> 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<PerformanceEntryType>
|
||||
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<std::pair<std::string, uint32_t>>
|
||||
NativePerformanceObserver::getEventCounts(jsi::Runtime& /*rt*/) {
|
||||
const auto& eventCounts =
|
||||
PerformanceEntryReporter::getInstance()->getEventCounts();
|
||||
return {eventCounts.begin(), eventCounts.end()};
|
||||
}
|
||||
} // namespace facebook::react
|
@ -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 <FBReactNativeSpec/FBReactNativeSpecJSI.h>
|
||||
#endif
|
||||
|
||||
#include <react/performance/timeline/PerformanceEntryReporter.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
using NativePerformanceObserverCallback = AsyncCallback<>;
|
||||
using NativePerformanceObserverObserveOptions =
|
||||
NativePerformanceObserverPerformanceObserverInit<
|
||||
// entryTypes
|
||||
std::optional<std::vector<int>>,
|
||||
// type
|
||||
std::optional<int>,
|
||||
// buffered
|
||||
std::optional<bool>,
|
||||
// durationThreshold
|
||||
std::optional<double>>;
|
||||
|
||||
#pragma mark - Structs
|
||||
|
||||
template <>
|
||||
struct Bridging<PerformanceEntryType> {
|
||||
static PerformanceEntryType fromJs(
|
||||
jsi::Runtime& /*rt*/,
|
||||
const jsi::Value& value) {
|
||||
return static_cast<PerformanceEntryType>(value.asNumber());
|
||||
}
|
||||
|
||||
static jsi::Value toJs(
|
||||
jsi::Runtime& /*rt*/,
|
||||
const PerformanceEntryType& value) {
|
||||
return {static_cast<int>(value)};
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Bridging<NativePerformanceObserverObserveOptions>
|
||||
: NativePerformanceObserverPerformanceObserverInitBridging<
|
||||
NativePerformanceObserverObserveOptions> {};
|
||||
|
||||
template <>
|
||||
struct Bridging<PerformanceEntry>
|
||||
: NativePerformanceObserverRawPerformanceEntryBridging<PerformanceEntry> {};
|
||||
|
||||
#pragma mark - implementation
|
||||
|
||||
class NativePerformanceObserver
|
||||
: public NativePerformanceObserverCxxSpec<NativePerformanceObserver> {
|
||||
public:
|
||||
explicit NativePerformanceObserver(std::shared_ptr<CallInvoker> 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<PerformanceEntry> 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<std::pair<std::string, uint32_t>> getEventCounts(
|
||||
jsi::Runtime& rt);
|
||||
|
||||
void clearEntries(
|
||||
jsi::Runtime& rt,
|
||||
PerformanceEntryType entryType,
|
||||
std::optional<std::string> entryName);
|
||||
|
||||
std::vector<PerformanceEntry> getEntries(
|
||||
jsi::Runtime& rt,
|
||||
std::optional<PerformanceEntryType> entryType,
|
||||
std::optional<std::string> entryName);
|
||||
|
||||
std::vector<PerformanceEntryType> getSupportedPerformanceEntryTypes(
|
||||
jsi::Runtime& rt);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
@ -10,7 +10,6 @@
|
||||
#include <react/timing/primitives.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace facebook::react {
|
||||
|
@ -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<PerformanceEntry>& target) const = 0;
|
||||
virtual void getEntries(std::vector<PerformanceEntry>& target) const = 0;
|
||||
virtual void getEntries(
|
||||
std::vector<PerformanceEntry>& 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
|
||||
|
@ -21,8 +21,8 @@ void PerformanceEntryCircularBuffer::getEntries(
|
||||
}
|
||||
|
||||
void PerformanceEntryCircularBuffer::getEntries(
|
||||
std::string_view name,
|
||||
std::vector<PerformanceEntry>& target) const {
|
||||
std::vector<PerformanceEntry>& 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; });
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,12 @@ class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer {
|
||||
void add(const PerformanceEntry& entry) override;
|
||||
|
||||
void getEntries(std::vector<PerformanceEntry>& target) const override;
|
||||
void getEntries(std::string_view name, std::vector<PerformanceEntry>& target)
|
||||
const override;
|
||||
void getEntries(
|
||||
std::vector<PerformanceEntry>& 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<PerformanceEntry> buffer_;
|
||||
|
@ -28,8 +28,8 @@ void PerformanceEntryKeyedBuffer::getEntries(
|
||||
}
|
||||
|
||||
void PerformanceEntryKeyedBuffer::getEntries(
|
||||
std::string_view name,
|
||||
std::vector<PerformanceEntry>& target) const {
|
||||
std::vector<PerformanceEntry>& 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<PerformanceEntry> PerformanceEntryKeyedBuffer::find(
|
||||
|
@ -23,11 +23,12 @@ class PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer {
|
||||
|
||||
void getEntries(std::vector<PerformanceEntry>& target) const override;
|
||||
|
||||
void getEntries(std::string_view name, std::vector<PerformanceEntry>& target)
|
||||
const override;
|
||||
void getEntries(
|
||||
std::vector<PerformanceEntry>& 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<PerformanceEntry> find(const std::string& name) const;
|
||||
|
||||
|
@ -8,9 +8,26 @@
|
||||
#include "PerformanceEntryReporter.h"
|
||||
|
||||
#include <cxxreact/JSExecutor.h>
|
||||
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
namespace {
|
||||
std::vector<PerformanceEntryType> getSupportedEntryTypesInternal() {
|
||||
std::vector<PerformanceEntryType> supportedEntryTypes{
|
||||
PerformanceEntryType::MARK,
|
||||
PerformanceEntryType::MEASURE,
|
||||
PerformanceEntryType::EVENT,
|
||||
};
|
||||
|
||||
if (ReactNativeFeatureFlags::enableLongTaskAPI()) {
|
||||
supportedEntryTypes.emplace_back(PerformanceEntryType::LONGTASK);
|
||||
}
|
||||
|
||||
return supportedEntryTypes;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<PerformanceEntryReporter>&
|
||||
PerformanceEntryReporter::getInstance() {
|
||||
static auto instance = std::make_shared<PerformanceEntryReporter>();
|
||||
@ -20,90 +37,93 @@ PerformanceEntryReporter::getInstance() {
|
||||
PerformanceEntryReporter::PerformanceEntryReporter()
|
||||
: observerRegistry_(std::make_unique<PerformanceObserverRegistry>()) {}
|
||||
|
||||
#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<PerformanceEntryType>
|
||||
PerformanceEntryReporter::getSupportedEntryTypes() {
|
||||
static std::vector<PerformanceEntryType> supportedEntries =
|
||||
getSupportedEntryTypesInternal();
|
||||
return supportedEntries;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::clearEntries(
|
||||
std::optional<PerformanceEntryType> entryType,
|
||||
std::optional<std::string_view> 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<PerformanceEntry> PerformanceEntryReporter::getEntries() const {
|
||||
std::vector<PerformanceEntry> res;
|
||||
// Collect all entry types
|
||||
for (int i = 1; i <= NUM_PERFORMANCE_ENTRY_TYPES; i++) {
|
||||
getBuffer(static_cast<PerformanceEntryType>(i)).getEntries(res);
|
||||
std::vector<PerformanceEntry> entries;
|
||||
getEntries(entries);
|
||||
return entries;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::getEntries(
|
||||
std::vector<PerformanceEntry>& dest) const {
|
||||
std::lock_guard lock(buffersMutex_);
|
||||
|
||||
for (auto entryType : getSupportedEntryTypes()) {
|
||||
getBuffer(entryType).getEntries(dest);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> PerformanceEntryReporter::getEntriesByType(
|
||||
std::vector<PerformanceEntry> PerformanceEntryReporter::getEntries(
|
||||
PerformanceEntryType entryType) const {
|
||||
std::vector<PerformanceEntry> res;
|
||||
getEntriesByType(entryType, res);
|
||||
return res;
|
||||
std::vector<PerformanceEntry> dest;
|
||||
getEntries(dest, entryType);
|
||||
return dest;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::getEntriesByType(
|
||||
void PerformanceEntryReporter::getEntries(
|
||||
std::vector<PerformanceEntry>& dest,
|
||||
PerformanceEntryType entryType) const {
|
||||
std::lock_guard lock(buffersMutex_);
|
||||
|
||||
getBuffer(entryType).getEntries(dest);
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> PerformanceEntryReporter::getEntries(
|
||||
PerformanceEntryType entryType,
|
||||
std::vector<PerformanceEntry>& target) const {
|
||||
getBuffer(entryType).getEntries(target);
|
||||
const std::string& entryName) const {
|
||||
std::vector<PerformanceEntry> entries;
|
||||
getEntries(entries, entryType, entryName);
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::vector<PerformanceEntry> PerformanceEntryReporter::getEntriesByName(
|
||||
std::string_view entryName) const {
|
||||
std::vector<PerformanceEntry> res;
|
||||
// Collect all entry types
|
||||
for (int i = 1; i <= NUM_PERFORMANCE_ENTRY_TYPES; i++) {
|
||||
getBuffer(static_cast<PerformanceEntryType>(i)).getEntries(entryName, res);
|
||||
void PerformanceEntryReporter::getEntries(
|
||||
std::vector<PerformanceEntry>& 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<PerformanceEntry> PerformanceEntryReporter::getEntriesByName(
|
||||
std::string_view entryName,
|
||||
PerformanceEntryType entryType) const {
|
||||
std::vector<PerformanceEntry> 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) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#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<PerformanceEntry> getEntries() const;
|
||||
void getEntries(std::vector<PerformanceEntry>& dest) const;
|
||||
|
||||
std::vector<PerformanceEntry> getEntries(
|
||||
PerformanceEntryType entryType) const;
|
||||
void getEntries(
|
||||
std::vector<PerformanceEntry>& dest,
|
||||
PerformanceEntryType entryType) const;
|
||||
|
||||
std::vector<PerformanceEntry> getEntries(
|
||||
PerformanceEntryType entryType,
|
||||
const std::string& entryName) const;
|
||||
void getEntries(
|
||||
std::vector<PerformanceEntry>& 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<DOMHighResTimeStamp()> provider) {
|
||||
timeStampProvider_ = std::move(provider);
|
||||
}
|
||||
|
||||
#pragma mark - Performance Timeline (https://w3c.github.io/performance-timeline/)
|
||||
static std::vector<PerformanceEntryType> 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<PerformanceEntry> getEntries() const;
|
||||
std::vector<PerformanceEntry> getEntriesByType(
|
||||
PerformanceEntryType entryType) const;
|
||||
void getEntriesByType(
|
||||
PerformanceEntryType entryType,
|
||||
std::vector<PerformanceEntry>& target) const;
|
||||
std::vector<PerformanceEntry> getEntriesByName(
|
||||
std::string_view entryName) const;
|
||||
std::vector<PerformanceEntry> getEntriesByName(
|
||||
std::string_view entryName,
|
||||
PerformanceEntryType entryType) const;
|
||||
const std::unordered_map<std::string, uint32_t>& 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<DOMHighResTimeStamp>& 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<std::string>& startMark = std::nullopt,
|
||||
const std::optional<std::string>& endMark = std::nullopt);
|
||||
|
||||
// https://w3c.github.io/user-timing/#clearmarks-method
|
||||
// https://w3c.github.io/user-timing/#clearmeasures-method
|
||||
void clearEntries(
|
||||
std::optional<PerformanceEntryType> entryType = std::nullopt,
|
||||
std::optional<std::string_view> 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<std::string, uint32_t>& 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_;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<PerformanceEntry> toSorted(
|
||||
const std::vector<PerformanceEntry>& originalEntries) {
|
||||
std::vector<PerformanceEntry> entries = originalEntries;
|
||||
namespace {
|
||||
std::vector<PerformanceEntry> toSorted(
|
||||
std::vector<PerformanceEntry>&& 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<PerformanceEntry> 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<PerformanceEntry> 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<PerformanceEntry> 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<PerformanceEntry> 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<PerformanceEntry> 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<PerformanceEntry> 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<PerformanceEntry>(
|
||||
{{.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<PerformanceEntry>(
|
||||
{{.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);
|
||||
}
|
||||
}
|
||||
|
@ -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<string, number> {
|
||||
if (cachedEventCounts) {
|
||||
return cachedEventCounts;
|
||||
}
|
||||
if (!NativePerformanceObserver) {
|
||||
warnNoNativePerformanceObserver();
|
||||
return new Map();
|
||||
|
||||
if (!NativePerformance || !NativePerformance?.getEventCounts) {
|
||||
warnNoNativePerformance();
|
||||
cachedEventCounts = new Map();
|
||||
return cachedEventCounts;
|
||||
}
|
||||
|
||||
cachedEventCounts = new Map<string, number>(
|
||||
NativePerformanceObserver.getEventCounts(),
|
||||
const eventCounts = new Map<string, number>(
|
||||
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<string, number> {
|
||||
// after the current task is guaranteed to have finished.
|
||||
cachedEventCounts = null;
|
||||
});
|
||||
return cachedEventCounts ?? new Map();
|
||||
|
||||
return eventCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<PerformanceEntryType> =
|
||||
['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);
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,6 @@ export type PerformanceEntryJSON = {
|
||||
...
|
||||
};
|
||||
|
||||
export const ALWAYS_LOGGED_ENTRY_TYPES: $ReadOnlyArray<PerformanceEntryType> = [
|
||||
'mark',
|
||||
'measure',
|
||||
];
|
||||
|
||||
export class PerformanceEntry {
|
||||
#name: string;
|
||||
#entryType: PerformanceEntryType;
|
||||
@ -69,3 +64,5 @@ export class PerformanceEntry {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type PerformanceEntryList = $ReadOnlyArray<PerformanceEntry>;
|
||||
|
@ -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<PerformanceEntry>;
|
||||
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<PerformanceEntryType> {
|
||||
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;
|
||||
|
@ -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';
|
||||
|
@ -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',
|
||||
);
|
||||
}
|
||||
|
@ -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]);
|
||||
});
|
||||
});
|
@ -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]);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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<number>,
|
||||
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<RawPerformanceEntry>;
|
||||
+getEntriesByName?: (
|
||||
entryName: string,
|
||||
entryType?: ?RawPerformanceEntryType,
|
||||
) => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+getEntriesByType?: (
|
||||
entryType: RawPerformanceEntryType,
|
||||
) => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+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<RawPerformanceEntry>;
|
||||
|
||||
+getSupportedPerformanceEntryTypes?: () => $ReadOnlyArray<RawPerformanceEntryType>;
|
||||
}
|
||||
|
||||
export default (TurboModuleRegistry.get<Spec>('NativePerformanceCxx'): ?Spec);
|
||||
|
@ -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<number>,
|
||||
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<RawPerformanceEntry>;
|
||||
|
||||
+clearEntries: (
|
||||
entryType?: RawPerformanceEntryType,
|
||||
entryName?: string,
|
||||
) => void;
|
||||
+getEntries: (
|
||||
entryType?: RawPerformanceEntryType,
|
||||
entryName?: string,
|
||||
) => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+getSupportedPerformanceEntryTypes: () => $ReadOnlyArray<RawPerformanceEntryType>;
|
||||
}
|
||||
|
||||
export default (TurboModuleRegistry.get<Spec>(
|
||||
'NativePerformanceObserverCxx',
|
||||
): ?Spec);
|
@ -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<string, number> = 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;
|
247
packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceMock.js
vendored
Normal file
247
packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceMock.js
vendored
Normal file
@ -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<RawPerformanceEntry>,
|
||||
options: PerformanceObserverInit,
|
||||
droppedEntriesCount: number,
|
||||
};
|
||||
|
||||
const eventCounts: Map<string, number> = new Map();
|
||||
const observers: Set<MockObserver> = new Set();
|
||||
const marks: Map<string, number> = new Map();
|
||||
let entries: Array<RawPerformanceEntry> = [];
|
||||
|
||||
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<RawPerformanceEntry> => {
|
||||
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<RawPerformanceEntry> => {
|
||||
return [...entries].sort((a, b) => a.startTime - b.startTime);
|
||||
},
|
||||
|
||||
getEntriesByName: (
|
||||
entryName: string,
|
||||
entryType?: ?RawPerformanceEntryType,
|
||||
): $ReadOnlyArray<RawPerformanceEntry> => {
|
||||
return NativePerformanceMock.getEntries().filter(
|
||||
entry =>
|
||||
(entryType == null || entry.entryType === entryType) &&
|
||||
entry.name === entryName,
|
||||
);
|
||||
},
|
||||
|
||||
getEntriesByType: (
|
||||
entryType: RawPerformanceEntryType,
|
||||
): $ReadOnlyArray<RawPerformanceEntry> => {
|
||||
return entries.filter(entry => entry.entryType === entryType);
|
||||
},
|
||||
|
||||
getSupportedPerformanceEntryTypes:
|
||||
(): $ReadOnlyArray<RawPerformanceEntryType> => {
|
||||
return [
|
||||
RawPerformanceEntryTypeValues.MARK,
|
||||
RawPerformanceEntryTypeValues.MEASURE,
|
||||
RawPerformanceEntryTypeValues.EVENT,
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
(NativePerformanceMock: NativePerformance);
|
||||
|
||||
export default NativePerformanceMock;
|
@ -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<string, number> = new Map();
|
||||
let observers: MockObserver[] = [];
|
||||
let entries: Array<RawPerformanceEntry> = [];
|
||||
|
||||
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<RawPerformanceEntry>,
|
||||
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<RawPerformanceEntry> => {
|
||||
// $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<RawPerformanceEntry> => {
|
||||
return entries.filter(
|
||||
e =>
|
||||
(entryType == null || e.entryType === entryType) &&
|
||||
(entryName == null || e.name === entryName),
|
||||
);
|
||||
},
|
||||
|
||||
getSupportedPerformanceEntryTypes:
|
||||
(): $ReadOnlyArray<RawPerformanceEntryType> => {
|
||||
return [
|
||||
RawPerformanceEntryTypeValues.MARK,
|
||||
RawPerformanceEntryTypeValues.MEASURE,
|
||||
RawPerformanceEntryTypeValues.EVENT,
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default NativePerformanceObserverMock;
|
@ -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]);
|
||||
});
|
||||
});
|
@ -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']);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user