From 5129b0764f49d84290ed0870452aa091849cd8c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 27 Jun 2024 15:27:45 +0200 Subject: [PATCH 01/60] refactor: extract performance observers to native layer and push entries to each observer refactor: use map for user timing API for better performance --- .../NativePerformanceObserver.h | 7 - .../timeline/BoundedConsumableBuffer.h | 23 +-- .../react/performance/timeline/CMakeLists.txt | 6 +- .../performance/timeline/ConsumableEntryMap.h | 110 ++++++++++ .../performance/timeline/PerformanceEntry.h | 70 +++++++ .../timeline/PerformanceEntryBuffer.cpp | 69 +++++++ .../timeline/PerformanceEntryBuffer.h | 82 ++++++++ .../timeline/PerformanceEntryReporter.cpp | 185 +++-------------- .../timeline/PerformanceEntryReporter.h | 189 +++++++----------- .../timeline/PerformanceObserver.cpp | 95 +++++++++ .../timeline/PerformanceObserver.h | 71 +++++++ .../timeline/PerformanceObserverRegistry.cpp | 41 ++++ .../timeline/PerformanceObserverRegistry.h | 31 +++ .../React-performancetimeline.podspec | 8 +- .../specs/NativePerformanceObserver.js | 24 ++- 15 files changed, 696 insertions(+), 315 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 5c57e2ca83a90d..9f3733afdf7859 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -48,18 +48,11 @@ class NativePerformanceObserver public: NativePerformanceObserver(std::shared_ptr jsInvoker); - void startReporting(jsi::Runtime& rt, PerformanceEntryType entryType); - - void stopReporting(jsi::Runtime& rt, PerformanceEntryType entryType); - void setIsBuffered( jsi::Runtime& rt, const std::vector entryTypes, bool isBuffered); - PerformanceEntryReporter::PopPendingEntriesResult popPendingEntries( - jsi::Runtime& rt); - void setOnPerformanceEntryCallback( jsi::Runtime& rt, std::optional> callback); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h index 2918436fccd14e..f62fe0697a6c24 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h @@ -10,6 +10,7 @@ #include #include #include +#include "PerformanceEntry.h" namespace facebook::react { @@ -31,23 +32,9 @@ constexpr size_t DEFAULT_MAX_SIZE = 1024; template class BoundedConsumableBuffer { public: - /** - * Status of the add/push operation for the `BoundedConsumableBuffer` - * container - */ - enum class PushStatus { - // There was free space in the buffer, element was successfully pushed: - OK = 0, - - // Element was pushed, but had to overwrite some already consumed elements: - OVERWRITE = 1, - - // Element wasn't pushed, as buffer size limit has been reached and it's - // not possible to overwrite already consumed elements anymore: - DROP = 2, - }; + using PushStatus = PerformanceEntryPushStatus; - BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE) + explicit BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE) : maxSize_(maxSize) { entries_.reserve(maxSize_); } @@ -57,10 +44,10 @@ class BoundedConsumableBuffer { * operation, which will depend on whether the buffer reached the max allowed * size and how many are there unconsumed elements. */ - PushStatus add(const T&& el) { + PushStatus add(const T& el) { if (entries_.size() < maxSize_) { // Haven't reached max buffer size yet, just add and grow the buffer - entries_.emplace_back(el); + entries_.push_back(el); cursorEnd_++; numToConsume_++; return PushStatus::OK; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index 2205b976ee7cbc..009f8b96829318 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -1,7 +1,7 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. +#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. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h b/packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h new file mode 100644 index 00000000000000..1940825ca6e665 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "PerformanceEntry.h" + +namespace facebook::react { + +class ConsumableEntryMap { + public: + ConsumableEntryMap() = default; + + void add(const PerformanceEntry& entry) { + auto& list = entryMap_.at(entry.name); + auto& toConsume = toConsumeMap_.at(entry.name); + list.push_back(entry); + totalEntryCount_ += 1; + totalToConsume_ += 1; + toConsume += 1; + } + + void clear() { + entryMap_.clear(); + toConsumeMap_.clear(); + totalEntryCount_ = 0; + totalToConsume_ = 0; + } + + void clear(const std::string& name) { + if (auto node = entryMap_.find(name); node != entryMap_.end()) { + totalEntryCount_ -= node->second.size(); + totalToConsume_ -= node->second.size(); + toConsumeMap_[name] = 0; + node->second.clear(); + } + } + + std::optional find(const std::string& name) const { + if (auto node = entryMap_.find(name); node != entryMap_.end()) { + if (!node->second.empty()) { + return std::make_optional(node->second.back()); + } + } + + return std::nullopt; + } + + void getEntries(std::vector& target) const { + if (allEntriesCache_.has_value()) { + auto& allEntries = allEntriesCache_.value(); + target.insert(target.end(), allEntries.begin(), allEntries.end()); + return; + } + + std::vector allEntries; + // pre-allocate result vector + allEntries.reserve(totalEntryCount_); + + for (const auto& [_, entries] : entryMap_) { + allEntries.insert(allEntries.end(), entries.begin(), entries.end()); + } + + std::stable_sort( + allEntries.begin(), allEntries.end(), PerformanceEntrySorter{}); + allEntriesCache_ = allEntries; + target.insert(target.end(), allEntries.begin(), allEntries.end()); + } + + /** + * Retrieves buffer entries, whether consumed or not, with predicate + */ + void consume(std::vector& target) { + for (const auto& [name, entries] : entryMap_) { + target.insert( + target.end(), entries.end() - toConsumeMap_[name], entries.end()); + toConsumeMap_[name] = 0; + } + } + + size_t getNumToConsume() const { + return totalToConsume_; + } + + void getEntries( + const std::string& name, + std::vector& target) const { + if (auto node = entryMap_.find(name); node != entryMap_.end()) { + target.insert(target.end(), node->second.begin(), node->second.end()); + } + } + + private: + std::unordered_map> entryMap_{}; + std::unordered_map toConsumeMap_{}; + mutable std::optional> allEntriesCache_; + size_t totalEntryCount_ = 0; + size_t totalToConsume_ = 0; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h new file mode 100644 index 00000000000000..008c1160aa744b --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +using DOMHighResTimeStamp = double; + +using PerformanceEntryInteractionId = uint32_t; + +enum class PerformanceEntryType { + // We need to preserve these values for backwards compatibility. + MARK = 1, + MEASURE = 2, + EVENT = 3, + LONGTASK = 4, + _NEXT = 5, +}; + +struct PerformanceEntry { + std::string name; + PerformanceEntryType entryType; + DOMHighResTimeStamp startTime; + DOMHighResTimeStamp duration = 0; + + // For "event" entries only: + std::optional processingStart; + std::optional processingEnd; + std::optional interactionId; +}; + +constexpr size_t NUM_PERFORMANCE_ENTRY_TYPES = + (size_t)PerformanceEntryType::_NEXT - 1; // Valid types start from 1. + +/** + * Status of the add/push operation for the `BoundedConsumableBuffer` + * container + */ +enum class PerformanceEntryPushStatus { + // There was free space in the buffer, element was successfully pushed: + OK = 0, + + // Element was pushed, but had to overwrite some already consumed elements: + OVERWRITE = 1, + + // Element wasn't pushed, as buffer size limit has been reached and it's + // not possible to overwrite already consumed elements anymore: + DROP = 2, +}; + +struct PerformanceEntrySorter { + bool operator()(const PerformanceEntry& lhs, const PerformanceEntry& rhs) { + if (lhs.startTime != rhs.startTime) { + return lhs.startTime < rhs.startTime; + } else { + return lhs.duration < rhs.duration; + } + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp new file mode 100644 index 00000000000000..bdbfdaf93b063e --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp @@ -0,0 +1,69 @@ +/* +* 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 "PerformanceEntryBuffer.h" + +namespace facebook::react { + +PerformanceEntryPushStatus PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry&& entry) { + return entries.add(std::move(entry)); +} + +void PerformanceEntryCircularBuffer::consume(std::vector& target) { + entries.consume(target); +} + +size_t PerformanceEntryCircularBuffer::pendingMessagesCount() const { + return entries.getNumToConsume(); +} + +void PerformanceEntryCircularBuffer::getEntries(std::optional name, std::vector& target) const { + entries.getEntries( + target, [&](const PerformanceEntry& e) { return e.name == name; }); +} + +void PerformanceEntryCircularBuffer::clear() { + entries.clear(); +} + + +void PerformanceEntryCircularBuffer::clear(std::string_view name) { + entries.clear([&](const PerformanceEntry& e) { return e.name == name; }); +} + +PerformanceEntryPushStatus PerformanceEntryKeyedBuffer::add(const facebook::react::PerformanceEntry&& entry) { + entries.add(entry); + return PerformanceEntryPushStatus::OK; +} + +void PerformanceEntryKeyedBuffer::consume(std::vector& target) { + entries.consume(target); +} + +size_t PerformanceEntryKeyedBuffer::pendingMessagesCount() const { + return entries.getNumToConsume(); +} + +void PerformanceEntryKeyedBuffer::getEntries(std::optional name, std::vector& target) const { + if (name.has_value()) { + std::string nameStr{name.value()}; + entries.getEntries(nameStr, target); + } else { + entries.getEntries(target); + } +} + +void PerformanceEntryKeyedBuffer::clear() { + entries.clear(); +} + +void PerformanceEntryKeyedBuffer::clear(std::string_view name) { + std::string nameStr{name}; + entries.clear(nameStr); +} + +} // namespace facebook::react \ No newline at end of file diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h new file mode 100644 index 00000000000000..d0039a03c64310 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "BoundedConsumableBuffer.h" +#include "ConsumableEntryMap.h" +#include "PerformanceEntry.h" + +namespace facebook::react { + +// Default duration threshold for reporting performance entries (0 means "report +// all") +constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; + +// Default buffer size limit, per entry type +constexpr size_t DEFAULT_MAX_BUFFER_SIZE = 1024; + +/** + * Abstract performance entry buffer with reporting flags. + * Subtypes differ on how entries are stored. + */ +struct PerformanceEntryBuffer { + bool isAlwaysLogged{false}; + double durationThreshold{DEFAULT_DURATION_THRESHOLD}; + + explicit PerformanceEntryBuffer() = default; + virtual ~PerformanceEntryBuffer() = default; + + virtual PerformanceEntryPushStatus add(const PerformanceEntry&& entry) = 0; + virtual void consume(std::vector& target) = 0; + virtual size_t pendingMessagesCount() const = 0; + virtual void getEntries( + std::optional name, + std::vector& target) const = 0; + virtual void clear() = 0; + virtual void clear(std::string_view name) = 0; +}; + +struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { + BoundedConsumableBuffer entries; + + explicit PerformanceEntryCircularBuffer(size_t size) : entries(size) {} + ~PerformanceEntryCircularBuffer() override = default; + + PerformanceEntryPushStatus add(const PerformanceEntry&& entry) override; + void consume(std::vector& target) override; + + size_t pendingMessagesCount() const override; + + void getEntries( + std::optional name, + std::vector& target) const override; + + void clear() override; + void clear(std::string_view name) override; +}; + +struct PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { + ConsumableEntryMap entries; + + explicit PerformanceEntryKeyedBuffer() = default; + ~PerformanceEntryKeyedBuffer() override = default; + + PerformanceEntryPushStatus add(const PerformanceEntry&& entry) override; + void consume(std::vector& target) override; + + size_t pendingMessagesCount() const override; + + void getEntries( + std::optional name, + std::vector& target) const override; + + void clear() override; + void clear(std::string_view name) override; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 79c47cf4a006e1..c03b06dc1f9946 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -17,28 +17,13 @@ PerformanceEntryReporter::getInstance() { return instance; } -PerformanceEntryReporter::PerformanceEntryReporter() { - // For mark entry types we also want to keep the lookup by name, to make - // sure that marks can be referenced by measures - getBuffer(PerformanceEntryType::MARK).hasNameLookup = true; -} - -void PerformanceEntryReporter::setReportingCallback( - std::function callback) { - callback_ = std::move(callback); -} +PerformanceEntryReporter::PerformanceEntryReporter(): observerRegistry_(std::make_unique()) {} DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() : JSExecutor::performanceNow(); } -void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) { - auto& buffer = getBuffer(entryType); - buffer.isReporting = true; - buffer.durationThreshold = DEFAULT_DURATION_THRESHOLD; -} - void PerformanceEntryReporter::setAlwaysLogged( PerformanceEntryType entryType, bool isAlwaysLogged) { @@ -46,103 +31,30 @@ void PerformanceEntryReporter::setAlwaysLogged( buffer.isAlwaysLogged = isAlwaysLogged; } -void PerformanceEntryReporter::setDurationThreshold( - PerformanceEntryType entryType, - DOMHighResTimeStamp durationThreshold) { - getBuffer(entryType).durationThreshold = durationThreshold; -} - -void PerformanceEntryReporter::stopReporting(PerformanceEntryType entryType) { - getBuffer(entryType).isReporting = false; -} - -void PerformanceEntryReporter::stopReporting() { - for (auto& buffer : buffers_) { - buffer.isReporting = false; - } -} - -PerformanceEntryReporter::PopPendingEntriesResult -PerformanceEntryReporter::popPendingEntries() { - std::lock_guard lock(entriesMutex_); - PopPendingEntriesResult res = { - .entries = std::vector(), - .droppedEntriesCount = droppedEntriesCount_}; - for (auto& buffer : buffers_) { - buffer.entries.consume(res.entries); - } - - // Sort by starting time (or ending time, if starting times are equal) - std::stable_sort( - res.entries.begin(), - res.entries.end(), - [](const PerformanceEntry& lhs, const PerformanceEntry& rhs) { - if (lhs.startTime != rhs.startTime) { - return lhs.startTime < rhs.startTime; - } else { - return lhs.duration < rhs.duration; - } - }); - - droppedEntriesCount_ = 0; - return res; -} - void PerformanceEntryReporter::logEntry(const PerformanceEntry& entry) { if (entry.entryType == PerformanceEntryType::EVENT) { eventCounts_[entry.name]++; } + { + std::lock_guard lock(entriesMutex_); + auto& buffer = getBuffer(entry.entryType); - if (!isReporting(entry.entryType) && !isAlwaysLogged(entry.entryType)) { - return; - } - - std::lock_guard lock(entriesMutex_); - - auto& buffer = getBuffer(entry.entryType); - - if (entry.duration < buffer.durationThreshold) { - // The entries duration is lower than the desired reporting threshold, skip - return; - } - - if (buffer.hasNameLookup) { - // If we need to remove an entry because the buffer is null, - // we also need to remove it from the name lookup. - auto overwriteCandidate = buffer.entries.getNextOverwriteCandidate(); - if (overwriteCandidate != nullptr) { - std::lock_guard lock2(nameLookupMutex_); - auto it = buffer.nameLookup.find(overwriteCandidate); - if (it != buffer.nameLookup.end() && *it == overwriteCandidate) { - buffer.nameLookup.erase(it); - } + if (entry.duration < buffer.durationThreshold) { + // The entries duration is lower than the desired reporting threshold, skip + return; } - } - auto pushResult = buffer.entries.add(std::move(entry)); - if (pushResult == - BoundedConsumableBuffer::PushStatus::DROP) { - // Start dropping entries once reached maximum buffer size. - // The number of dropped entries will be reported back to the corresponding - // PerformanceObserver callback. - droppedEntriesCount_ += 1; - } - - if (buffer.hasNameLookup) { - std::lock_guard lock2(nameLookupMutex_); - auto currentEntry = &buffer.entries.back(); - auto it = buffer.nameLookup.find(currentEntry); - if (it != buffer.nameLookup.end()) { - buffer.nameLookup.erase(it); + auto pushResult = buffer.add(std::move(entry)); + if (pushResult == + BoundedConsumableBuffer::PushStatus::DROP) { + // Start dropping entries once reached maximum buffer size. + // The number of dropped entries will be reported back to the corresponding + // PerformanceObserver callback. + droppedEntriesCount_ += 1; } - buffer.nameLookup.insert(currentEntry); } - if (buffer.entries.getNumToConsume() == 1) { - // If the buffer was empty, it signals that JS side just has possibly - // consumed it and is ready to get more - scheduleFlushBuffer(); - } + observerRegistry_.emit(entry); } void PerformanceEntryReporter::mark( @@ -157,44 +69,22 @@ void PerformanceEntryReporter::mark( void PerformanceEntryReporter::clearEntries( std::optional entryType, std::string_view entryName) { + // Clear all entry types if (!entryType) { - // Clear all entry types for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) { clearEntries(static_cast(i), entryName); } - } else { - auto& buffer = getBuffer(*entryType); - if (!entryName.empty()) { - if (buffer.hasNameLookup) { - std::lock_guard lock2(nameLookupMutex_); - buffer.nameLookup.clear(); - } - std::lock_guard lock(entriesMutex_); - buffer.entries.clear([entryName](const PerformanceEntry& entry) { - return entry.name == entryName; - }); + return; + } - if (buffer.hasNameLookup) { - std::lock_guard lock2(nameLookupMutex_); - // BoundedConsumableBuffer::clear() invalidates existing references; we - // need to rebuild the lookup table. If there are multiple entries with - // the same name, make sure the last one gets inserted. - for (int i = static_cast(buffer.entries.size()) - 1; i >= 0; i--) { - const auto& entry = buffer.entries[i]; - buffer.nameLookup.insert(&entry); - } - } - } else { - { - std::lock_guard lock(entriesMutex_); - buffer.entries.clear(); - } - { - std::lock_guard lock2(nameLookupMutex_); - buffer.nameLookup.clear(); - } - } + auto& buffer = getBuffer(*entryType); + if (!entryName.empty()) { + std::lock_guard lock(entriesMutex_); + buffer.clear(entryName); + } else { + std::lock_guard lock(entriesMutex_); + buffer.clear(); } } @@ -203,13 +93,12 @@ void PerformanceEntryReporter::getEntries( std::string_view entryName, std::vector& res) const { std::lock_guard lock(entriesMutex_); - const auto& entries = getBuffer(entryType).entries; + auto& buffer = getBuffer(entryType); + if (entryName.empty()) { - entries.getEntries(res); + buffer.getEntries(std::nullopt, res); } else { - entries.getEntries(res, [entryName](const PerformanceEntry& entry) { - return entry.name == entryName; - }); + buffer.getEntries(entryName, res); } } @@ -257,14 +146,10 @@ void PerformanceEntryReporter::measure( DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( const std::string& markName) const { - PerformanceEntry mark{ - .name = markName, .entryType = PerformanceEntryType::MARK}; + std::lock_guard lock(entriesMutex_); - std::lock_guard lock(nameLookupMutex_); - const auto& marksBuffer = getBuffer(PerformanceEntryType::MARK); - auto it = marksBuffer.nameLookup.find(&mark); - if (it != marksBuffer.nameLookup.end()) { - return (*it)->startTime; + if (auto it = markBuffer_.entries.find(markName); it) { + return it->startTime; } else { return 0.0; } @@ -297,10 +182,4 @@ void PerformanceEntryReporter::logLongTaskEntry( .duration = duration}); } -void PerformanceEntryReporter::scheduleFlushBuffer() { - if (callback_) { - callback_(); - } -} - } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 9f8bb0b79ffd87..05461116556b4d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -8,78 +8,18 @@ #pragma once #include -#include "BoundedConsumableBuffer.h" +#include "PerformanceObserver.h" +#include "PerformanceObserverRegistry.h" +#include "PerformanceEntryBuffer.h" -#include -#include +#include #include #include #include -#include -#include -#include namespace facebook::react { -using PerformanceEntryInteractionId = uint32_t; - -enum class PerformanceEntryType { - // We need to preserve these values for backwards compatibility. - MARK = 1, - MEASURE = 2, - EVENT = 3, - LONGTASK = 4, - _NEXT = 5, -}; - -struct PerformanceEntry { - std::string name; - PerformanceEntryType entryType; - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp duration = 0; - - // For "event" entries only: - std::optional processingStart; - std::optional processingEnd; - std::optional interactionId; -}; - -struct PerformanceEntryHash { - size_t operator()(const PerformanceEntry* entry) const { - return std::hash()(entry->name); - } -}; - -struct PerformanceEntryEqual { - bool operator()(const PerformanceEntry* lhs, const PerformanceEntry* rhs) - const { - return lhs->name == rhs->name; - } -}; - -using PerformanceEntryRegistryType = std::unordered_set< - const PerformanceEntry*, - PerformanceEntryHash, - PerformanceEntryEqual>; - -// Default duration threshold for reporting performance entries (0 means "report -// all") -constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; - -// Default buffer size limit, per entry type -constexpr size_t DEFAULT_MAX_BUFFER_SIZE = 1024; - -struct PerformanceEntryBuffer { - BoundedConsumableBuffer entries{DEFAULT_MAX_BUFFER_SIZE}; - bool isReporting{false}; - bool isAlwaysLogged{false}; - double durationThreshold{DEFAULT_DURATION_THRESHOLD}; - bool hasNameLookup{false}; - PerformanceEntryRegistryType nameLookup; -}; - -constexpr size_t NUM_PERFORMANCE_ENTRY_TYPES = - (size_t)PerformanceEntryType::_NEXT - 1; // Valid types start from 1. +constexpr size_t MAX_BUFFER_SIZE_EVENT = 150; constexpr DOMHighResTimeStamp LONG_TASK_DURATION_THRESHOLD_MS = 50.0; @@ -93,61 +33,21 @@ class PerformanceEntryReporter { // creation time instead of having the singleton. static std::shared_ptr& getInstance(); - struct PopPendingEntriesResult { - std::vector entries; - uint32_t droppedEntriesCount; - }; - - void setReportingCallback(std::function callback); - void startReporting(PerformanceEntryType entryType); - void stopReporting(PerformanceEntryType entryType); - void stopReporting(); + /** + * Marks performance entry buffer of type `entryType` to be buffered even if no observers are attached. + */ void setAlwaysLogged(PerformanceEntryType entryType, bool isAlwaysLogged); - void setDurationThreshold( - PerformanceEntryType entryType, - double durationThreshold); - - PopPendingEntriesResult popPendingEntries(); - - void logEntry(const PerformanceEntry& entry); - - PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) { - return buffers_[static_cast(entryType) - 1]; - } - - const PerformanceEntryBuffer& getBuffer( - PerformanceEntryType entryType) const { - return buffers_[static_cast(entryType) - 1]; - } - - bool isReporting(PerformanceEntryType entryType) const { - return getBuffer(entryType).isReporting; - } + /** + * Whenever performance entry buffer of type `entryType` is buffered. + */ bool isAlwaysLogged(PerformanceEntryType entryType) const { return getBuffer(entryType).isAlwaysLogged; } - uint32_t getDroppedEntriesCount() const { - return droppedEntriesCount_; - } - - void mark( - const std::string& name, - const std::optional& startTime = std::nullopt); - - void measure( - const std::string_view& name, - double startTime, - double endTime, - const std::optional& duration = std::nullopt, - const std::optional& startMark = std::nullopt, - const std::optional& endMark = std::nullopt); - - void clearEntries( - std::optional entryType = std::nullopt, - std::string_view entryName = {}); + void logEntry(const PerformanceEntry& entry); + // https://www.w3.org/TR/performance-timeline/#getentries-method std::vector getEntries( std::optional entryType = std::nullopt, std::string_view entryName = {}) const; @@ -172,26 +72,77 @@ class PerformanceEntryReporter { timeStampProvider_ = std::move(provider); } - private: - std::function callback_; + /** + * User Timing Level 3 functions + * https://w3c.github.io/user-timing/ + */ + // https://w3c.github.io/user-timing/#mark-method + void mark( + const std::string& name, + const std::optional& startTime = std::nullopt); + + // https://w3c.github.io/user-timing/#measure-method + void measure( + const std::string_view& name, + double startTime, + double endTime, + const std::optional& duration = std::nullopt, + const std::optional& startMark = std::nullopt, + const std::optional& endMark = std::nullopt); + + // https://w3c.github.io/user-timing/#clearmarks-method + // https://w3c.github.io/user-timing/#clearmeasures-method + void clearEntries( + std::optional entryType = std::nullopt, + std::string_view entryName = {}); + +private: + std::unique_ptr observerRegistry_; mutable std::mutex entriesMutex_; - std::array buffers_; + PerformanceEntryCircularBuffer eventBuffer_{MAX_BUFFER_SIZE_EVENT}; + PerformanceEntryKeyedBuffer markBuffer_; + PerformanceEntryKeyedBuffer measureBuffer_; + std::unordered_map eventCounts_; uint32_t droppedEntriesCount_{0}; std::function timeStampProvider_ = nullptr; - mutable std::mutex nameLookupMutex_; - double getMarkTime(const std::string& markName) const; - void scheduleFlushBuffer(); void getEntries( PerformanceEntryType entryType, std::string_view entryName, std::vector& res) const; + + PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) { + switch (entryType) { + case PerformanceEntryType::EVENT: + return eventBuffer_; + case PerformanceEntryType::MARK: + return markBuffer_; + case PerformanceEntryType::MEASURE: + return measureBuffer_; + default: + assert(0 && "Unhandled PerformanceEntryType"); + } + } + + const PerformanceEntryBuffer& getBuffer( + PerformanceEntryType entryType) const { + switch (entryType) { + case PerformanceEntryType::EVENT: + return eventBuffer_; + case PerformanceEntryType::MARK: + return markBuffer_; + case PerformanceEntryType::MEASURE: + return measureBuffer_; + default: + assert(0 && "Unhandled PerformanceEntryType"); + } + } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp new file mode 100644 index 00000000000000..8182e7bcf1f96c --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -0,0 +1,95 @@ +/* +* 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 "PerformanceObserver.h" +#include "PerformanceObserverRegistry.h" + +namespace facebook::react { + +PerformanceObserver::~PerformanceObserver() { + if (auto registry = registry_.lock()) { + registry->removeObserver(this); + } +} + +void PerformanceObserver::logEntry(const facebook::react::PerformanceEntry& entry) { + if (!isObserving(entry.entryType)) { + return; + } + + if (entry.duration < buffer_.durationThreshold) { + // The entries duration is lower than the desired reporting threshold, skip + return; + } + + auto pushResult = buffer_.add(entry); + if (pushResult == + BoundedConsumableBuffer::PushStatus::DROP) { + // Start dropping entries once reached maximum buffer size. + // The number of dropped entries will be reported back to the corresponding + // PerformanceObserver callback. + droppedEntriesCount_ += 1; + } + + if (buffer_.pendingMessagesCount() == 1) { + // If the buffer was empty, it signals that JS side just has possibly + // consumed it and is ready to get more + scheduleFlushBuffer(); + } +} + +PerformanceObserver::PopPendingEntriesResult +PerformanceObserver::popPendingEntries() { + PopPendingEntriesResult res = { + .entries = std::vector(), + .droppedEntriesCount = droppedEntriesCount_}; + + buffer_.consume(res.entries); + + // Sort by starting time (or ending time, if starting times are equal) + std::stable_sort( + res.entries.begin(), res.entries.end(), PerformanceEntrySorter{}); + + droppedEntriesCount_ = 0; + return res; +} + +void PerformanceObserver::clearEntries(std::optional entryType, std::string_view entryName) { + if (!entryName.empty()) { + buffer_.clear(entryName); + } else { + buffer_.clear(); + } +} + +bool PerformanceObserver::isObserving(facebook::react::PerformanceEntryType type) const { + return observedTypes_.contains(type); +} + +PerformanceObserverEventFilter& PerformanceObserver::getEventFilter() { + return observedTypes_; +} + +const PerformanceObserverEventFilter& PerformanceObserver::getEventFilter() const { + return observedTypes_; +} + +void PerformanceObserver::setEntryBuffering(bool isBuffered) { + buffer_.isAlwaysLogged = isBuffered; +} + +void PerformanceObserver::setDurationThreshold(DOMHighResTimeStamp durationThreshold) { + buffer_.durationThreshold = durationThreshold; +} + +void PerformanceObserver::scheduleFlushBuffer() { + if (callback_) { + callback_(droppedEntriesCount_); + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h new file mode 100644 index 00000000000000..d5c22da995fb0e --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include "PerformanceEntryBuffer.h" + +namespace facebook::react { + +using PerformanceObserverEventFilter = std::unordered_set; +using PerformanceObserverCallback = std::function; + +class PerformanceObserverRegistry; + +/** + * Represents native counterpart of performance timeline PerformanceObserver + * class. Each instance has its own entry buffer and can listen for different + * performance entry types. + * + * Entries are pushed to the observer by the `PerformanceEntryReporter` class, + * which acts as a central hub. + */ +class PerformanceObserver { + public: + struct PopPendingEntriesResult { + std::vector entries; + uint32_t droppedEntriesCount; + }; + + PerformanceObserver( + PerformanceObserverCallback callback, + PerformanceEntryType type) + : callback_(std::move(callback)), observedTypes_({type}) {} + PerformanceObserver( + PerformanceObserverCallback callback, + PerformanceObserverEventFilter&& types) + : callback_(std::move(callback)), observedTypes_(std::move(types)) {} + + virtual ~PerformanceObserver(); + + void logEntry(const PerformanceEntry& entry); + PopPendingEntriesResult popPendingEntries(); + void clearEntries( + std::optional entryType = std::nullopt, + std::string_view entryName = {}); + + bool isObserving(PerformanceEntryType type) const; + PerformanceObserverEventFilter& getEventFilter(); + const PerformanceObserverEventFilter& getEventFilter() const; + + void setEntryBuffering(bool isBuffered); + void setDurationThreshold(double durationThreshold); + + private: + void scheduleFlushBuffer(); + + std::weak_ptr registry_; + PerformanceObserverCallback callback_; + PerformanceObserverEventFilter observedTypes_; + size_t droppedEntriesCount_{0}; + PerformanceEntryCircularBuffer buffer_{DEFAULT_MAX_BUFFER_SIZE}; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp new file mode 100644 index 00000000000000..410748ca063d60 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -0,0 +1,41 @@ +/* +* 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 "PerformanceObserverRegistry.h" + +namespace facebook::react { + +void PerformanceObserverRegistry::addObserver(const std::weak_ptr& observer) { + std::lock_guard guard{observersMutex_}; + observers_.insert(observer); +} + +void PerformanceObserverRegistry::removeObserver(const std::weak_ptr& observer) { + std::lock_guard guard{observersMutex_}; + observers_.erase(observer); +} + +void PerformanceObserverRegistry::emit(const facebook::react::PerformanceEntry& entry) { + std::lock_guard lock(observersMutex_); + + // filter dead observers + for (auto first = observers_.begin(), last = observers_.end(); + first != last;) { + if (first->expired()) + first = observers_.erase(first); + else + ++first; + } + + for (auto& observer_ptr : observers_) { + if (auto observer = observer_ptr.lock()) { + observer->logEntry(entry); + } + } +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h new file mode 100644 index 00000000000000..6d772793795ea0 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include "PerformanceObserver.h" + +namespace facebook::react { + +class PerformanceObserverRegistry { + public: + PerformanceObserverRegistry() = default; + + void addObserver(const std::weak_ptr& observer); + void removeObserver(const std::weak_ptr& observer); + + void emit(const PerformanceEntry& entry); + + private: + mutable std::mutex observersMutex_; + std::set> observers_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec index a8dbcbe7a01407..41b3520c53330d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec +++ b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec @@ -1,7 +1,7 @@ -# Copyright (c) Meta Platforms, Inc. and affiliates. +#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. +#This source code is licensed under the MIT license found in the +#LICENSE file in the root directory of this source tree. require "json" @@ -10,7 +10,7 @@ version = package['version'] source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' - # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + # This is an unpublished version, use the latest commit hash of the react - native repo, which we’re presumably in. source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") else source[:tag] = "v#{version}" diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index 503d4d812690b8..61c480e9eb32eb 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -30,21 +30,23 @@ export type GetPendingEntriesResult = {| droppedEntriesCount: number, |}; +export type PerformanceObserverInit = { + entryTypes: $ReadOnlyArray; + type: string; + buffered: boolean; +}; + +export type PerformanceObserver = { + observe(options: PerformanceObserverInit): void; + disconnect(): void; + takeRecords(): $ReadOnlyArray; +}; + export interface Spec extends TurboModule { - +startReporting: (entryType: RawPerformanceEntryType) => void; - +stopReporting: (entryType: RawPerformanceEntryType) => void; - +setIsBuffered: ( - entryTypes: $ReadOnlyArray, - isBuffered: boolean, - ) => void; - +popPendingEntries: () => GetPendingEntriesResult; + +createObserver: (callback: () => void) => PerformanceObserver; +setOnPerformanceEntryCallback: (callback?: () => void) => void; +logRawEntry: (entry: RawPerformanceEntry) => void; +getEventCounts: () => $ReadOnlyArray<[string, number]>; - +setDurationThreshold: ( - entryType: RawPerformanceEntryType, - durationThreshold: number, - ) => void; +clearEntries: ( entryType?: RawPerformanceEntryType, entryName?: string, From df5cadebc1e60e8aafa2ad1d6c714b584dbe94cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 24 Jul 2024 11:17:54 +0200 Subject: [PATCH 02/60] update JS code to reflect native changes --- .../nativemodule/defaults/CMakeLists.txt | 1 + .../defaults/DefaultTurboModules.cpp | 10 ++ .../React-defaultsnativemodule.podspec | 1 + .../webperformance/CMakeLists.txt | 26 ++++ .../webperformance/NativePerformance.cpp | 4 +- .../webperformance/NativePerformance.h | 6 + .../NativePerformanceObserver.cpp | 108 +++++++------- .../NativePerformanceObserver.h | 33 ++--- .../React-webperformancemodule.podspec | 49 ++++++ .../timeline/PerformanceEntryBuffer.cpp | 6 +- .../timeline/PerformanceEntryBuffer.h | 6 +- .../timeline/PerformanceEntryReporter.cpp | 2 +- .../timeline/PerformanceEntryReporter.h | 4 + .../timeline/PerformanceObserver.cpp | 4 +- .../timeline/PerformanceObserver.h | 15 +- .../timeline/PerformanceObserverRegistry.cpp | 9 +- .../timeline/PerformanceObserverRegistry.h | 5 +- .../react-native/scripts/react_native_pods.rb | 1 + .../performance/PerformanceObserver.js | 140 ++---------------- .../specs/NativePerformanceObserver.js | 18 +-- 20 files changed, 217 insertions(+), 231 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt create mode 100644 packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt index 952ffe520cc589..4f152700b193e9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt @@ -24,4 +24,5 @@ target_link_libraries(react_nativemodule_defaults react_nativemodule_featureflags react_nativemodule_microtasks react_nativemodule_idlecallbacks + react_nativemodule_webperformance ) diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp index 1e0201666d2c59..b36a111e0b45f1 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include namespace facebook::react { @@ -28,6 +30,14 @@ namespace facebook::react { return std::make_shared(jsInvoker); } + if (name == NativePerformance::kModuleName) { + return std::make_shared(jsInvoker); + } + + if (name == NativePerformanceObserver::kModuleName) { + return std::make_shared(jsInvoker); + } + if (name == NativeDOM::kModuleName) { return std::make_shared(jsInvoker); } diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec index 77cc249c1bfec5..cca9b663a35079 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec @@ -48,4 +48,5 @@ Pod::Spec.new do |s| s.dependency "React-featureflagsnativemodule" s.dependency "React-microtasksnativemodule" s.dependency "React-idlecallbacksnativemodule" + s.dependency "React-webperformancemodule" end diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt new file mode 100644 index 00000000000000..f6907e073c4520 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt @@ -0,0 +1,26 @@ +# 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. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options( + -fexceptions + -frtti + -std=c++20 + -Wall + -Wpedantic + -DLOG_TAG=\"ReactNative\") + +file(GLOB react_nativemodule_webperformance_SRC CONFIGURE_DEPENDS *.cpp) +add_library(react_nativemodule_webperformance STATIC ${react_nativemodule_webperformance_SRC}) + +target_include_directories(react_nativemodule_webperformance PUBLIC ${REACT_COMMON_DIR}) + +target_link_libraries(react_nativemodule_webperformance + react_codegen_rncore + react_cxxreact + react_render_runtimescheduler +) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 7592ad3cacf88c..a3f361658e802a 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -15,12 +15,12 @@ #include #include #include -#include #include "NativePerformance.h" -#include "Plugins.h" +#include #ifdef WITH_PERFETTO #include +#include "Plugins.h" #endif std::shared_ptr NativePerformanceModuleProvider( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index 1c95d80abd4118..fae3bf6c0dc4f9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -7,7 +7,13 @@ #pragma once +#if __has_include("rncoreJSI.h") // Cmake headers on Android +#include "rncoreJSI.h" +#elif __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple +#include "FBReactNativeSpecJSI.h" +#else #include +#endif #include #include diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 6ec448ad426b6e..d28b08fb12d1b4 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -14,8 +14,6 @@ #include #include -#include "Plugins.h" - std::shared_ptr NativePerformanceObserverModuleProvider( std::shared_ptr jsInvoker) { @@ -29,70 +27,76 @@ NativePerformanceObserver::NativePerformanceObserver( std::shared_ptr jsInvoker) : NativePerformanceObserverCxxSpec(std::move(jsInvoker)) {} -void NativePerformanceObserver::startReporting( - jsi::Runtime& /*rt*/, - PerformanceEntryType entryType) { - auto reporter = PerformanceEntryReporter::getInstance(); +jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, AsyncCallback<> callback) { + PerformanceObserverCallback cb = [callback = std::move(callback)](size_t _) -> void { + callback.callWithPriority(SchedulerPriority::IdlePriority); + }; - reporter->startReporting(entryType); + auto observer = std::make_shared(std::move(cb)); + jsi::Object observerObj {rt}; + observerObj.setNativeState(rt, std::move(observer)); + return observerObj; } -void NativePerformanceObserver::stopReporting( - jsi::Runtime& /*rt*/, - PerformanceEntryType entryType) { - auto reporter = PerformanceEntryReporter::getInstance(); +void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerObj, jsi::Object options) { + auto observer = + std::dynamic_pointer_cast(observerObj.getNativeState(rt)); - reporter->stopReporting(entryType); -} + if (!observer) { + return; + } -void NativePerformanceObserver::setIsBuffered( - jsi::Runtime& /*rt*/, - const std::vector entryTypes, - bool isBuffered) { - for (const PerformanceEntryType entryType : entryTypes) { - PerformanceEntryReporter::getInstance()->setAlwaysLogged( - entryType, isBuffered); + std::set entryTypes; + + // observer of type multiple + if (options.hasProperty(rt, "entryTypes")) { + auto types = options.getPropertyAsObject(rt, "entryTypes").asArray(rt); + for (auto i = 0; i < types.size(rt); ++i) { + entryTypes.insert(types.getValueAtIndex(rt, i).asNumber()); + } + } + else { + auto buffered = options.getProperty(rt, "buffered").asBool(); + auto type = options.getProperty(rt, "type").asNumber(); + entryTypes.insert(type); + observer->setEntryBuffering(buffered); } -} -PerformanceEntryReporter::PopPendingEntriesResult -NativePerformanceObserver::popPendingEntries(jsi::Runtime& /*rt*/) { - return PerformanceEntryReporter::getInstance()->popPendingEntries(); -} + // apply collected entryTypes into observer eventFilter + for (auto entryType : entryTypes) { + if (entryType < 0 || entryType >= NUM_PERFORMANCE_ENTRY_TYPES) { + continue; + } -void NativePerformanceObserver::setOnPerformanceEntryCallback( - jsi::Runtime& /*rt*/, - std::optional> callback) { - if (callback) { - PerformanceEntryReporter::getInstance()->setReportingCallback( - [callback = std::move(callback)]() { - callback->callWithPriority(SchedulerPriority::IdlePriority); - }); - } else { - PerformanceEntryReporter::getInstance()->setReportingCallback(nullptr); + observer->getEventFilter().insert(static_cast(entryType)); } -} -void NativePerformanceObserver::logRawEntry( - jsi::Runtime& /*rt*/, - const PerformanceEntry entry) { - PerformanceEntryReporter::getInstance()->logEntry(entry); + auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); + registry.addObserver(observer); } -std::vector> -NativePerformanceObserver::getEventCounts(jsi::Runtime& /*rt*/) { - const auto& eventCounts = - PerformanceEntryReporter::getInstance()->getEventCounts(); - return std::vector>( - eventCounts.begin(), eventCounts.end()); +void NativePerformanceObserver::disconnect(jsi::Runtime& rt, jsi::Object observerObj) { + auto observer = + std::dynamic_pointer_cast(observerObj.getNativeState(rt)); + + if (!observer) { + return; + } + + auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); + registry.removeObserver(observer); } -void NativePerformanceObserver::setDurationThreshold( - jsi::Runtime& /*rt*/, - PerformanceEntryType entryType, - double durationThreshold) { - PerformanceEntryReporter::getInstance()->setDurationThreshold( - entryType, durationThreshold); +std::vector NativePerformanceObserver::takeRecords(jsi::Runtime& rt, jsi::Object observerObj) { + auto observer = + std::dynamic_pointer_cast( + observerObj.getNativeState(rt)); + + if (!observer) { + return {}; + } + + return observer->popPendingEntries().entries; } void NativePerformanceObserver::clearEntries( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 9f3733afdf7859..a4a2c951dfb489 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -7,7 +7,14 @@ #pragma once +#if __has_include("rncoreJSI.h") // Cmake headers on Android +#include "rncoreJSI.h" +#elif __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple +#include "FBReactNativeSpecJSI.h" +#else #include +#endif + #include #include #include @@ -36,37 +43,21 @@ template <> struct Bridging : NativePerformanceObserverRawPerformanceEntryBridging {}; -template <> -struct Bridging - : NativePerformanceObserverGetPendingEntriesResultBridging< - PerformanceEntryReporter::PopPendingEntriesResult> {}; - #pragma mark - implementation class NativePerformanceObserver : public NativePerformanceObserverCxxSpec { public: NativePerformanceObserver(std::shared_ptr jsInvoker); - - void setIsBuffered( - jsi::Runtime& rt, - const std::vector entryTypes, - bool isBuffered); - - void setOnPerformanceEntryCallback( - jsi::Runtime& rt, - std::optional> callback); - - void logRawEntry(jsi::Runtime& rt, const PerformanceEntry entry); + + jsi::Object createObserver(jsi::Runtime& rt, AsyncCallback<> callback); + void observe(jsi::Runtime& rt, jsi::Object observer, jsi::Object options); + void disconnect(jsi::Runtime& rt, jsi::Object observer); + std::vector takeRecords(jsi::Runtime& rt, jsi::Object observerObj); std::vector> getEventCounts( jsi::Runtime& rt); - void setDurationThreshold( - jsi::Runtime& rt, - PerformanceEntryType entryType, - DOMHighResTimeStamp durationThreshold); - void clearEntries( jsi::Runtime& rt, PerformanceEntryType entryType, diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec new file mode 100644 index 00000000000000..4fa41d5adfbf69 --- /dev/null +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec @@ -0,0 +1,49 @@ +# 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. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +header_search_paths = [] + +if ENV['USE_FRAMEWORKS'] + header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the module access its own files +end + +Pod::Spec.new do |s| + s.name = "React-webperformancemodule" + s.version = version + s.summary = "React Native web performance native module" + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = min_supported_versions + s.source = source + s.source_files = "*.{cpp,h}" + s.header_dir = "react/nativemodule/webperformance" + s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", + "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), + "DEFINES_MODULE" => "YES" } + + if ENV['USE_FRAMEWORKS'] + s.module_name = "webperformancemodule" + s.header_mappings_dir = "../.." + end + + install_modules_dependencies(s) + + s.dependency "ReactCommon/turbomodule/core" + s.dependency "React-runtimescheduler" +end diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp index bdbfdaf93b063e..e04b674730612b 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp @@ -9,7 +9,7 @@ namespace facebook::react { -PerformanceEntryPushStatus PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry&& entry) { +PerformanceEntryPushStatus PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { return entries.add(std::move(entry)); } @@ -35,7 +35,7 @@ void PerformanceEntryCircularBuffer::clear(std::string_view name) { entries.clear([&](const PerformanceEntry& e) { return e.name == name; }); } -PerformanceEntryPushStatus PerformanceEntryKeyedBuffer::add(const facebook::react::PerformanceEntry&& entry) { +PerformanceEntryPushStatus PerformanceEntryKeyedBuffer::add(const facebook::react::PerformanceEntry& entry) { entries.add(entry); return PerformanceEntryPushStatus::OK; } @@ -66,4 +66,4 @@ void PerformanceEntryKeyedBuffer::clear(std::string_view name) { entries.clear(nameStr); } -} // namespace facebook::react \ No newline at end of file +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index d0039a03c64310..818cbbce213194 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -31,7 +31,7 @@ struct PerformanceEntryBuffer { explicit PerformanceEntryBuffer() = default; virtual ~PerformanceEntryBuffer() = default; - virtual PerformanceEntryPushStatus add(const PerformanceEntry&& entry) = 0; + virtual PerformanceEntryPushStatus add(const PerformanceEntry& entry) = 0; virtual void consume(std::vector& target) = 0; virtual size_t pendingMessagesCount() const = 0; virtual void getEntries( @@ -47,7 +47,7 @@ struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { explicit PerformanceEntryCircularBuffer(size_t size) : entries(size) {} ~PerformanceEntryCircularBuffer() override = default; - PerformanceEntryPushStatus add(const PerformanceEntry&& entry) override; + PerformanceEntryPushStatus add(const PerformanceEntry& entry) override; void consume(std::vector& target) override; size_t pendingMessagesCount() const override; @@ -66,7 +66,7 @@ struct PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { explicit PerformanceEntryKeyedBuffer() = default; ~PerformanceEntryKeyedBuffer() override = default; - PerformanceEntryPushStatus add(const PerformanceEntry&& entry) override; + PerformanceEntryPushStatus add(const PerformanceEntry& entry) override; void consume(std::vector& target) override; size_t pendingMessagesCount() const override; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index c03b06dc1f9946..ff421b30aec4aa 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -54,7 +54,7 @@ void PerformanceEntryReporter::logEntry(const PerformanceEntry& entry) { } } - observerRegistry_.emit(entry); + observerRegistry_->emit(entry); } void PerformanceEntryReporter::mark( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 05461116556b4d..e06964af748aa1 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -72,6 +72,10 @@ class PerformanceEntryReporter { timeStampProvider_ = std::move(provider); } + PerformanceObserverRegistry& getObserverRegistry() { + return *observerRegistry_; + } + /** * User Timing Level 3 functions * https://w3c.github.io/user-timing/ diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 8182e7bcf1f96c..4a105f86d3ff51 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -12,7 +12,7 @@ namespace facebook::react { PerformanceObserver::~PerformanceObserver() { if (auto registry = registry_.lock()) { - registry->removeObserver(this); + registry->removeObserver(*this); } } @@ -46,7 +46,7 @@ PerformanceObserver::PopPendingEntriesResult PerformanceObserver::popPendingEntries() { PopPendingEntriesResult res = { .entries = std::vector(), - .droppedEntriesCount = droppedEntriesCount_}; + .droppedEntriesCount = (uint32_t)droppedEntriesCount_}; buffer_.consume(res.entries); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index d5c22da995fb0e..4bc44eb7247bf8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -34,14 +34,9 @@ class PerformanceObserver { uint32_t droppedEntriesCount; }; - PerformanceObserver( - PerformanceObserverCallback callback, - PerformanceEntryType type) - : callback_(std::move(callback)), observedTypes_({type}) {} - PerformanceObserver( - PerformanceObserverCallback callback, - PerformanceObserverEventFilter&& types) - : callback_(std::move(callback)), observedTypes_(std::move(types)) {} + explicit PerformanceObserver( + PerformanceObserverCallback&& callback) + : callback_(std::move(callback)) {} virtual ~PerformanceObserver(); @@ -68,4 +63,8 @@ class PerformanceObserver { PerformanceEntryCircularBuffer buffer_{DEFAULT_MAX_BUFFER_SIZE}; }; +inline bool operator==(const PerformanceObserver& lhs, const PerformanceObserver& rhs) { + return &lhs == &rhs; +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 410748ca063d60..b5a3ec4a5a5ebd 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -14,7 +14,14 @@ void PerformanceObserverRegistry::addObserver(const std::weak_ptr& observer) { +void PerformanceObserverRegistry::removeObserver(const PerformanceObserver& observer) { + std::lock_guard guard{observersMutex_}; + erase_if(observers_, [&](auto e) -> bool { + return !e.expired() && *e.lock() == observer; + }); +} + +void PerformanceObserverRegistry::removeObserver(const std::shared_ptr observer) { std::lock_guard guard{observersMutex_}; observers_.erase(observer); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index 6d772793795ea0..3a32fd6bbcd5d3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -19,13 +19,14 @@ class PerformanceObserverRegistry { PerformanceObserverRegistry() = default; void addObserver(const std::weak_ptr& observer); - void removeObserver(const std::weak_ptr& observer); + void removeObserver(const std::shared_ptr observer); + void removeObserver(const PerformanceObserver& observer); void emit(const PerformanceEntry& entry); private: mutable std::mutex observersMutex_; - std::set> observers_; + std::set, std::owner_less>> observers_; }; } // namespace facebook::react diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 221635d4edd1de..00274aade43f69 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -124,6 +124,7 @@ def use_react_native! ( pod 'React-featureflagsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/featureflags" pod 'React-microtasksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/microtasks" pod 'React-idlecallbacksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/idlecallbacks" + pod 'React-webperformancemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/webperformance" pod 'React-domnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/dom" pod 'React-defaultsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/defaults" pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon" diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 4f1d90f080ffa0..cf6d26d2733cbe 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -78,52 +78,6 @@ type PerformanceObserverConfig = {| durationThreshold: ?number, |}; -const observerCountPerEntryType: Map = new Map(); -const registeredObservers: Map = - new Map(); -let isOnPerformanceEntryCallbackSet: boolean = false; - -// This is a callback that gets scheduled and periodically called from the native side -const onPerformanceEntry = () => { - if (!NativePerformanceObserver) { - return; - } - const entryResult = NativePerformanceObserver.popPendingEntries(); - const rawEntries = entryResult?.entries ?? []; - const droppedEntriesCount = entryResult?.droppedEntriesCount; - if (rawEntries.length === 0) { - return; - } - const entries = rawEntries.map(rawToPerformanceEntry); - for (const [observer, observerConfig] of registeredObservers.entries()) { - const entriesForObserver: PerformanceEntryList = entries.filter(entry => { - if (!observerConfig.entryTypes.has(entry.entryType)) { - return false; - } - - if ( - entry.entryType === 'event' && - observerConfig.durationThreshold != null - ) { - return entry.duration >= observerConfig.durationThreshold; - } - - return true; - }); - if (entriesForObserver.length !== 0) { - try { - observerConfig.callback( - new PerformanceObserverEntryList(entriesForObserver), - observer, - droppedEntriesCount, - ); - } catch (error) { - console.error(error); - } - } - } -}; - export function warnNoNativePerformanceObserver() { warnOnce( 'missing-native-performance-observer', @@ -131,14 +85,6 @@ export function warnNoNativePerformanceObserver() { ); } -function applyDurationThresholds() { - const durationThresholds = Array.from(registeredObservers.values()) - .map(observerConfig => observerConfig.durationThreshold) - .filter(Boolean); - - return Math.min(...durationThresholds); -} - function getSupportedPerformanceEntryTypes(): $ReadOnlyArray { if (!NativePerformanceObserver) { return Object.freeze([]); @@ -175,6 +121,7 @@ function getSupportedPerformanceEntryTypes(): $ReadOnlyArray { + const entryList = new PerformanceObserverEntryList(NativePerformanceObserver.takeRecords(this.#observerHandle)); + this.#callback(entryList, this, { droppedEntriesCount }); + }); } #validateObserveOptions(options: PerformanceObserverInit): void { @@ -320,12 +218,4 @@ export class PerformanceObserver { getSupportedPerformanceEntryTypes(); } -function union(a: $ReadOnlySet, b: $ReadOnlySet): Set { - return new Set([...a, ...b]); -} - -function difference(a: $ReadOnlySet, b: $ReadOnlySet): Set { - return new Set([...a].filter(x => !b.has(x))); -} - export {PerformanceEventTiming}; diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index 61c480e9eb32eb..0cf92f2191ed4b 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -32,21 +32,17 @@ export type GetPendingEntriesResult = {| export type PerformanceObserverInit = { entryTypes: $ReadOnlyArray; - type: string; + type: number; buffered: boolean; }; -export type PerformanceObserver = { - observe(options: PerformanceObserverInit): void; - disconnect(): void; - takeRecords(): $ReadOnlyArray; -}; - export interface Spec extends TurboModule { - +createObserver: (callback: () => void) => PerformanceObserver; - +setOnPerformanceEntryCallback: (callback?: () => void) => void; - +logRawEntry: (entry: RawPerformanceEntry) => void; - +getEventCounts: () => $ReadOnlyArray<[string, number]>; + +createObserver: (callback: () => void) => mixed; + + +observe: (observer: mixed, options: PerformanceObserverInit) => void; + +disconnect: (observer: mixed) => void; + +takeRecords: (observer: mixed) => $ReadOnlyArray; + +clearEntries: ( entryType?: RawPerformanceEntryType, entryName?: string, From 2a47fdc677a93943aa724705534bc7a54c2d64c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 1 Aug 2024 11:55:31 +0200 Subject: [PATCH 03/60] chore: undo CMakeLists change --- .../ReactCommon/react/performance/timeline/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt index 009f8b96829318..2205b976ee7cbc 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/performance/timeline/CMakeLists.txt @@ -1,7 +1,7 @@ -#Copyright(c) Meta Platforms, Inc.and affiliates. +# 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. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. cmake_minimum_required(VERSION 3.13) set(CMAKE_VERBOSE_MAKEFILE on) From cecdd2f90ce8f661134d29dc6a7b68bf19513e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 1 Aug 2024 11:56:16 +0200 Subject: [PATCH 04/60] fix: wrap Plugins.h include into ifdef fix: clear Observer native state fix: reset observer entryTypes on change --- .../nativemodule/webperformance/NativePerformance.cpp | 7 +++++-- .../webperformance/NativePerformanceObserver.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index a3f361658e802a..9698781bab3414 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -15,12 +15,15 @@ #include #include #include -#include "NativePerformance.h" #include +#include "NativePerformance.h" + +#ifdef RN_DISABLE_OSS_PLUGIN_HEADER +#include "Plugins.h" +#endif #ifdef WITH_PERFETTO #include -#include "Plugins.h" #endif std::shared_ptr NativePerformanceModuleProvider( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index d28b08fb12d1b4..0601a8e50596d9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -14,6 +14,10 @@ #include #include +#ifdef RN_DISABLE_OSS_PLUGIN_HEADER +#include "Plugins.h" +#endif + std::shared_ptr NativePerformanceObserverModuleProvider( std::shared_ptr jsInvoker) { @@ -63,6 +67,7 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb } // apply collected entryTypes into observer eventFilter + observer->getEventFilter().clear(); for (auto entryType : entryTypes) { if (entryType < 0 || entryType >= NUM_PERFORMANCE_ENTRY_TYPES) { continue; @@ -82,6 +87,7 @@ void NativePerformanceObserver::disconnect(jsi::Runtime& rt, jsi::Object observe if (!observer) { return; } + observerObj.setNativeState(rt, nullptr); auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); registry.removeObserver(observer); From b4c02b383c4d779ff792ade5c9726a5de141380a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 1 Aug 2024 11:56:48 +0200 Subject: [PATCH 05/60] chore: add comment on PerformanceObserver TurboModule inclusion --- .../react/nativemodule/defaults/DefaultTurboModules.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp index b36a111e0b45f1..15b97979aa7558 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp @@ -34,6 +34,7 @@ namespace facebook::react { return std::make_shared(jsInvoker); } + // TEMPORARY (PR only, for testing) if (name == NativePerformanceObserver::kModuleName) { return std::make_shared(jsInvoker); } From cc46c945ccd79da7c1ca061609d174f08383db69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 1 Aug 2024 12:01:11 +0200 Subject: [PATCH 06/60] fix: use type from timing/Primitives --- .../ReactCommon/react/performance/timeline/PerformanceEntry.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index 008c1160aa744b..f6cb6f1f08771d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -10,11 +10,10 @@ #include #include #include +#include namespace facebook::react { -using DOMHighResTimeStamp = double; - using PerformanceEntryInteractionId = uint32_t; enum class PerformanceEntryType { From ae9c73b3ca588c066f24b15684c7935a280e4428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 1 Aug 2024 12:12:17 +0200 Subject: [PATCH 07/60] fix reporting logic for entries of event type --- .../performance/timeline/PerformanceEntryReporter.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index ff421b30aec4aa..418ae0b1a4d895 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -35,13 +35,17 @@ void PerformanceEntryReporter::logEntry(const PerformanceEntry& entry) { if (entry.entryType == PerformanceEntryType::EVENT) { eventCounts_[entry.name]++; } + { std::lock_guard lock(entriesMutex_); auto& buffer = getBuffer(entry.entryType); - if (entry.duration < buffer.durationThreshold) { - // The entries duration is lower than the desired reporting threshold, skip - return; + + if (entry.entryType == PerformanceEntryType::EVENT) { + if (entry.duration < buffer.durationThreshold) { + // The entries duration is lower than the desired reporting threshold, skip + return; + } } auto pushResult = buffer.add(std::move(entry)); From b8705e406541a976eeee5bef1c8b0204061544e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 1 Aug 2024 12:13:46 +0200 Subject: [PATCH 08/60] fix: handle LongTask in getBuffer() switch --- .../react/performance/timeline/PerformanceEntryReporter.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index e06964af748aa1..51cec2e34faaa6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -105,6 +105,7 @@ class PerformanceEntryReporter { mutable std::mutex entriesMutex_; PerformanceEntryCircularBuffer eventBuffer_{MAX_BUFFER_SIZE_EVENT}; + PerformanceEntryCircularBuffer longTaskBuffer_{MAX_BUFFER_SIZE_EVENT}; PerformanceEntryKeyedBuffer markBuffer_; PerformanceEntryKeyedBuffer measureBuffer_; @@ -129,6 +130,8 @@ class PerformanceEntryReporter { return markBuffer_; case PerformanceEntryType::MEASURE: return measureBuffer_; + case PerformanceEntryType::LONGTASK: + return longTaskBuffer_; default: assert(0 && "Unhandled PerformanceEntryType"); } @@ -143,6 +146,8 @@ class PerformanceEntryReporter { return markBuffer_; case PerformanceEntryType::MEASURE: return measureBuffer_; + case PerformanceEntryType::LONGTASK: + return longTaskBuffer_; default: assert(0 && "Unhandled PerformanceEntryType"); } From 7819498d23ea137ceb4d55ce477c7f5863e234aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 1 Aug 2024 12:17:21 +0200 Subject: [PATCH 09/60] fix: use DOMHighResTimestamp --- .../react/performance/timeline/PerformanceEntryReporter.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 51cec2e34faaa6..170df75f34a478 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -83,7 +83,7 @@ class PerformanceEntryReporter { // https://w3c.github.io/user-timing/#mark-method void mark( const std::string& name, - const std::optional& startTime = std::nullopt); + const std::optional& startTime = std::nullopt); // https://w3c.github.io/user-timing/#measure-method void measure( From a4e404d2d74dfc5b6a0fc0c3f5b9d884b4256102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 5 Aug 2024 10:59:30 +0200 Subject: [PATCH 10/60] fix: remove checking for duration threshold in observer and move filtering of observer event types to observer registry --- .../performance/timeline/PerformanceEntryReporter.cpp | 5 ++--- .../performance/timeline/PerformanceEntryReporter.h | 2 +- .../performance/timeline/PerformanceObserver.cpp | 11 +---------- .../react/performance/timeline/PerformanceObserver.h | 2 +- .../timeline/PerformanceObserverRegistry.cpp | 5 ++++- 5 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 418ae0b1a4d895..c70a9610875c23 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -31,7 +31,7 @@ void PerformanceEntryReporter::setAlwaysLogged( buffer.isAlwaysLogged = isAlwaysLogged; } -void PerformanceEntryReporter::logEntry(const PerformanceEntry& entry) { +void PerformanceEntryReporter::pushEntry(const PerformanceEntry& entry) { if (entry.entryType == PerformanceEntryType::EVENT) { eventCounts_[entry.name]++; } @@ -40,7 +40,6 @@ void PerformanceEntryReporter::logEntry(const PerformanceEntry& entry) { std::lock_guard lock(entriesMutex_); auto& buffer = getBuffer(entry.entryType); - if (entry.entryType == PerformanceEntryType::EVENT) { if (entry.duration < buffer.durationThreshold) { // The entries duration is lower than the desired reporting threshold, skip @@ -64,7 +63,7 @@ void PerformanceEntryReporter::logEntry(const PerformanceEntry& entry) { void PerformanceEntryReporter::mark( const std::string& name, const std::optional& startTime) { - logEntry(PerformanceEntry{ + pushEntry(PerformanceEntry{ .name = name, .entryType = PerformanceEntryType::MARK, .startTime = startTime ? *startTime : getCurrentTimeStamp()}); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 170df75f34a478..0a648ef054f8b1 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -45,7 +45,7 @@ class PerformanceEntryReporter { return getBuffer(entryType).isAlwaysLogged; } - void logEntry(const PerformanceEntry& entry); + void pushEntry(const PerformanceEntry& entry); // https://www.w3.org/TR/performance-timeline/#getentries-method std::vector getEntries( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 4a105f86d3ff51..fcb492e3726147 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -16,16 +16,7 @@ PerformanceObserver::~PerformanceObserver() { } } -void PerformanceObserver::logEntry(const facebook::react::PerformanceEntry& entry) { - if (!isObserving(entry.entryType)) { - return; - } - - if (entry.duration < buffer_.durationThreshold) { - // The entries duration is lower than the desired reporting threshold, skip - return; - } - +void PerformanceObserver::pushEntry(const facebook::react::PerformanceEntry& entry) { auto pushResult = buffer_.add(entry); if (pushResult == BoundedConsumableBuffer::PushStatus::DROP) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 4bc44eb7247bf8..1f48684a6a61b1 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -40,7 +40,7 @@ class PerformanceObserver { virtual ~PerformanceObserver(); - void logEntry(const PerformanceEntry& entry); + void pushEntry(const PerformanceEntry& entry); PopPendingEntriesResult popPendingEntries(); void clearEntries( std::optional entryType = std::nullopt, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index b5a3ec4a5a5ebd..f723d89cf8a01e 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -40,7 +40,10 @@ void PerformanceObserverRegistry::emit(const facebook::react::PerformanceEntry& for (auto& observer_ptr : observers_) { if (auto observer = observer_ptr.lock()) { - observer->logEntry(entry); + // push to observer if it is contained within its entry type filter + if (observer->isObserving(entry.entryType)) { + observer->pushEntry(entry); + } } } } From 2b0f1078c42084cb75f12d55199a2256351ed29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 5 Aug 2024 13:32:24 +0200 Subject: [PATCH 11/60] fix: remove const ref getEventFilter and add [[nodiscard]] attributes --- .../react/performance/timeline/PerformanceObserver.cpp | 4 ---- .../react/performance/timeline/PerformanceObserver.h | 7 +++---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index fcb492e3726147..e6f2f8ab2d960a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -65,10 +65,6 @@ PerformanceObserverEventFilter& PerformanceObserver::getEventFilter() { return observedTypes_; } -const PerformanceObserverEventFilter& PerformanceObserver::getEventFilter() const { - return observedTypes_; -} - void PerformanceObserver::setEntryBuffering(bool isBuffered) { buffer_.isAlwaysLogged = isBuffered; } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 1f48684a6a61b1..501fceb43502a0 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -41,14 +41,13 @@ class PerformanceObserver { virtual ~PerformanceObserver(); void pushEntry(const PerformanceEntry& entry); - PopPendingEntriesResult popPendingEntries(); + [[nodiscard]] PopPendingEntriesResult popPendingEntries(); void clearEntries( std::optional entryType = std::nullopt, std::string_view entryName = {}); - bool isObserving(PerformanceEntryType type) const; - PerformanceObserverEventFilter& getEventFilter(); - const PerformanceObserverEventFilter& getEventFilter() const; + [[nodiscard]] bool isObserving(PerformanceEntryType type) const; + [[nodiscard]] PerformanceObserverEventFilter& getEventFilter(); void setEntryBuffering(bool isBuffered); void setDurationThreshold(double durationThreshold); From 284971ee7ab7990be1bdf7fe12bbbe1a5fa2f5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 5 Aug 2024 13:36:10 +0200 Subject: [PATCH 12/60] feat: add [[nodiscard]] attributes --- .../react/performance/timeline/PerformanceEntryReporter.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 0a648ef054f8b1..2f99da4e4a5def 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -48,7 +48,7 @@ class PerformanceEntryReporter { void pushEntry(const PerformanceEntry& entry); // https://www.w3.org/TR/performance-timeline/#getentries-method - std::vector getEntries( + [[nodiscard]] std::vector getEntries( std::optional entryType = std::nullopt, std::string_view entryName = {}) const; @@ -62,17 +62,17 @@ class PerformanceEntryReporter { void logLongTaskEntry(double startTime, double duration); - const std::unordered_map& getEventCounts() const { + [[nodiscard]] const std::unordered_map& getEventCounts() const { return eventCounts_; } - DOMHighResTimeStamp getCurrentTimeStamp() const; + [[nodiscard]] DOMHighResTimeStamp getCurrentTimeStamp() const; void setTimeStampProvider(std::function provider) { timeStampProvider_ = std::move(provider); } - PerformanceObserverRegistry& getObserverRegistry() { + [[nodiscard]] PerformanceObserverRegistry& getObserverRegistry() { return *observerRegistry_; } From 751aba1c2c7e2ba64168695edbf3a4371cb17d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 5 Aug 2024 15:18:15 +0200 Subject: [PATCH 13/60] feat: add [[nodiscard]] attributes; clean up data flow to make it simpler --- .../NativePerformanceObserver.cpp | 41 ++++++---- .../NativePerformanceObserver.h | 4 +- .../timeline/PerformanceEntryBuffer.cpp | 10 ++- .../timeline/PerformanceEntryBuffer.h | 11 +-- .../timeline/PerformanceEntryReporter.cpp | 21 ++--- .../timeline/PerformanceEntryReporter.h | 34 ++++---- .../timeline/PerformanceObserver.cpp | 81 +++++++++---------- .../timeline/PerformanceObserver.h | 33 ++++---- 8 files changed, 113 insertions(+), 122 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 0601a8e50596d9..f5d2f2e4e43e33 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -27,13 +27,21 @@ NativePerformanceObserverModuleProvider( namespace facebook::react { +std::optional parseEntryType(int rawValue) { + if (rawValue < 0 || rawValue >= NUM_PERFORMANCE_ENTRY_TYPES) { + return std::nullopt; + } + + return static_cast(rawValue); +} + NativePerformanceObserver::NativePerformanceObserver( std::shared_ptr jsInvoker) : NativePerformanceObserverCxxSpec(std::move(jsInvoker)) {} -jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, AsyncCallback<> callback) { - PerformanceObserverCallback cb = [callback = std::move(callback)](size_t _) -> void { - callback.callWithPriority(SchedulerPriority::IdlePriority); +jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, NativePerformanceObserverCallback callback) { + PerformanceObserverCallback cb = [callback = std::move(callback)](std::vector&& entries, size_t droppedEntriesCount) -> void { + callback.callWithPriority(SchedulerPriority::IdlePriority, std::move(entries), droppedEntriesCount); }; auto observer = std::make_shared(std::move(cb)); @@ -49,32 +57,31 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb if (!observer) { return; } + observer->setRequiresDroppedEntries(buffered); + - std::set entryTypes; + std::unordered_set entryTypes; // observer of type multiple if (options.hasProperty(rt, "entryTypes")) { auto types = options.getPropertyAsObject(rt, "entryTypes").asArray(rt); for (auto i = 0; i < types.size(rt); ++i) { - entryTypes.insert(types.getValueAtIndex(rt, i).asNumber()); + auto rawValue = types.getValueAtIndex(rt, i).asNumber(); + if (auto entryType = parseEntryType(rawValue); entryType) { + entryTypes.insert(*entryType); + } } } - else { + else { // single auto buffered = options.getProperty(rt, "buffered").asBool(); auto type = options.getProperty(rt, "type").asNumber(); - entryTypes.insert(type); - observer->setEntryBuffering(buffered); + if (auto entryType = parseEntryType(type); entryType) { + entryTypes.insert(*entryType); + } } // apply collected entryTypes into observer eventFilter - observer->getEventFilter().clear(); - for (auto entryType : entryTypes) { - if (entryType < 0 || entryType >= NUM_PERFORMANCE_ENTRY_TYPES) { - continue; - } - - observer->getEventFilter().insert(static_cast(entryType)); - } + observer->setEntryTypeFilter(entryTypes); auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); registry.addObserver(observer); @@ -102,7 +109,7 @@ std::vector NativePerformanceObserver::takeRecords(jsi::Runtim return {}; } - return observer->popPendingEntries().entries; + return observer->takeRecords(); } void NativePerformanceObserver::clearEntries( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index a4a2c951dfb489..fd01bb895796dc 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -22,6 +22,8 @@ namespace facebook::react { +using NativePerformanceObserverCallback = AsyncCallback&&, size_t>; + #pragma mark - Structs template <> @@ -50,7 +52,7 @@ class NativePerformanceObserver public: NativePerformanceObserver(std::shared_ptr jsInvoker); - jsi::Object createObserver(jsi::Runtime& rt, AsyncCallback<> callback); + jsi::Object createObserver(jsi::Runtime& rt, NativePerformanceObserverCallback callback); void observe(jsi::Runtime& rt, jsi::Object observer, jsi::Object options); void disconnect(jsi::Runtime& rt, jsi::Object observer); std::vector takeRecords(jsi::Runtime& rt, jsi::Object observerObj); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp index e04b674730612b..4d36b8f309dcea 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp @@ -9,8 +9,11 @@ namespace facebook::react { -PerformanceEntryPushStatus PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { - return entries.add(std::move(entry)); +void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { + auto result = entries.add(entry); + if (result == PerformanceEntryPushStatus::DROP) { + droppedEntriesCount += 1; + } } void PerformanceEntryCircularBuffer::consume(std::vector& target) { @@ -35,9 +38,8 @@ void PerformanceEntryCircularBuffer::clear(std::string_view name) { entries.clear([&](const PerformanceEntry& e) { return e.name == name; }); } -PerformanceEntryPushStatus PerformanceEntryKeyedBuffer::add(const facebook::react::PerformanceEntry& entry) { +void PerformanceEntryKeyedBuffer::add(const facebook::react::PerformanceEntry& entry) { entries.add(entry); - return PerformanceEntryPushStatus::OK; } void PerformanceEntryKeyedBuffer::consume(std::vector& target) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 818cbbce213194..f10de9e2539fe5 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -25,13 +25,14 @@ constexpr size_t DEFAULT_MAX_BUFFER_SIZE = 1024; * Subtypes differ on how entries are stored. */ struct PerformanceEntryBuffer { - bool isAlwaysLogged{false}; double durationThreshold{DEFAULT_DURATION_THRESHOLD}; + size_t droppedEntriesCount{0}; + bool isAlwaysLogged{false}; explicit PerformanceEntryBuffer() = default; virtual ~PerformanceEntryBuffer() = default; - virtual PerformanceEntryPushStatus add(const PerformanceEntry& entry) = 0; + virtual void add(const PerformanceEntry& entry) = 0; virtual void consume(std::vector& target) = 0; virtual size_t pendingMessagesCount() const = 0; virtual void getEntries( @@ -47,10 +48,10 @@ struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { explicit PerformanceEntryCircularBuffer(size_t size) : entries(size) {} ~PerformanceEntryCircularBuffer() override = default; - PerformanceEntryPushStatus add(const PerformanceEntry& entry) override; + void add(const PerformanceEntry& entry) override; void consume(std::vector& target) override; - size_t pendingMessagesCount() const override; + [[nodiscard]] size_t pendingMessagesCount() const override; void getEntries( std::optional name, @@ -66,7 +67,7 @@ struct PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { explicit PerformanceEntryKeyedBuffer() = default; ~PerformanceEntryKeyedBuffer() override = default; - PerformanceEntryPushStatus add(const PerformanceEntry& entry) override; + void add(const PerformanceEntry& entry) override; void consume(std::vector& target) override; size_t pendingMessagesCount() const override; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index c70a9610875c23..8dbcae93479d78 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -27,7 +27,7 @@ DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { void PerformanceEntryReporter::setAlwaysLogged( PerformanceEntryType entryType, bool isAlwaysLogged) { - auto& buffer = getBuffer(entryType); + auto& buffer = getBufferRef(entryType); buffer.isAlwaysLogged = isAlwaysLogged; } @@ -38,7 +38,7 @@ void PerformanceEntryReporter::pushEntry(const PerformanceEntry& entry) { { std::lock_guard lock(entriesMutex_); - auto& buffer = getBuffer(entry.entryType); + auto& buffer = getBufferRef(entry.entryType); if (entry.entryType == PerformanceEntryType::EVENT) { if (entry.duration < buffer.durationThreshold) { @@ -47,14 +47,7 @@ void PerformanceEntryReporter::pushEntry(const PerformanceEntry& entry) { } } - auto pushResult = buffer.add(std::move(entry)); - if (pushResult == - BoundedConsumableBuffer::PushStatus::DROP) { - // Start dropping entries once reached maximum buffer size. - // The number of dropped entries will be reported back to the corresponding - // PerformanceObserver callback. - droppedEntriesCount_ += 1; - } + buffer.add(entry); } observerRegistry_->emit(entry); @@ -81,7 +74,7 @@ void PerformanceEntryReporter::clearEntries( return; } - auto& buffer = getBuffer(*entryType); + auto& buffer = getBufferRef(*entryType); if (!entryName.empty()) { std::lock_guard lock(entriesMutex_); buffer.clear(entryName); @@ -140,7 +133,7 @@ void PerformanceEntryReporter::measure( DOMHighResTimeStamp durationVal = duration ? *duration : endTimeVal - startTimeVal; - logEntry( + pushEntry( {.name = std::string(name), .entryType = PerformanceEntryType::MEASURE, .startTime = startTimeVal, @@ -165,7 +158,7 @@ void PerformanceEntryReporter::logEventEntry( DOMHighResTimeStamp processingStart, DOMHighResTimeStamp processingEnd, uint32_t interactionId) { - logEntry( + pushEntry( {.name = std::move(name), .entryType = PerformanceEntryType::EVENT, .startTime = startTime, @@ -178,7 +171,7 @@ void PerformanceEntryReporter::logEventEntry( void PerformanceEntryReporter::logLongTaskEntry( DOMHighResTimeStamp startTime, DOMHighResTimeStamp duration) { - logEntry( + pushEntry( {.name = std::string{"self"}, .entryType = PerformanceEntryType::LONGTASK, .startTime = startTime, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 2f99da4e4a5def..75ecbf40cd6261 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -100,6 +100,22 @@ class PerformanceEntryReporter { std::optional entryType = std::nullopt, std::string_view entryName = {}); + [[nodiscard]] const PerformanceEntryBuffer& getBuffer( + PerformanceEntryType entryType) const { + switch (entryType) { + case PerformanceEntryType::EVENT: + return eventBuffer_; + case PerformanceEntryType::MARK: + return markBuffer_; + case PerformanceEntryType::MEASURE: + return measureBuffer_; + case PerformanceEntryType::LONGTASK: + return longTaskBuffer_; + default: + assert(0 && "Unhandled PerformanceEntryType"); + } + } + private: std::unique_ptr observerRegistry_; @@ -122,23 +138,7 @@ class PerformanceEntryReporter { std::string_view entryName, std::vector& res) const; - PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) { - switch (entryType) { - case PerformanceEntryType::EVENT: - return eventBuffer_; - case PerformanceEntryType::MARK: - return markBuffer_; - case PerformanceEntryType::MEASURE: - return measureBuffer_; - case PerformanceEntryType::LONGTASK: - return longTaskBuffer_; - default: - assert(0 && "Unhandled PerformanceEntryType"); - } - } - - const PerformanceEntryBuffer& getBuffer( - PerformanceEntryType entryType) const { + PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { switch (entryType) { case PerformanceEntryType::EVENT: return eventBuffer_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index e6f2f8ab2d960a..5e6d6681985fdb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -7,6 +7,7 @@ #include "PerformanceObserver.h" #include "PerformanceObserverRegistry.h" +#include "PerformanceEntryReporter.h" namespace facebook::react { @@ -17,66 +18,56 @@ PerformanceObserver::~PerformanceObserver() { } void PerformanceObserver::pushEntry(const facebook::react::PerformanceEntry& entry) { - auto pushResult = buffer_.add(entry); - if (pushResult == - BoundedConsumableBuffer::PushStatus::DROP) { - // Start dropping entries once reached maximum buffer size. - // The number of dropped entries will be reported back to the corresponding - // PerformanceObserver callback. - droppedEntriesCount_ += 1; - } - - if (buffer_.pendingMessagesCount() == 1) { - // If the buffer was empty, it signals that JS side just has possibly - // consumed it and is ready to get more - scheduleFlushBuffer(); - } -} - -PerformanceObserver::PopPendingEntriesResult -PerformanceObserver::popPendingEntries() { - PopPendingEntriesResult res = { - .entries = std::vector(), - .droppedEntriesCount = (uint32_t)droppedEntriesCount_}; - - buffer_.consume(res.entries); - - // Sort by starting time (or ending time, if starting times are equal) - std::stable_sort( - res.entries.begin(), res.entries.end(), PerformanceEntrySorter{}); - - droppedEntriesCount_ = 0; - return res; + entries_.push_back(entry); } -void PerformanceObserver::clearEntries(std::optional entryType, std::string_view entryName) { - if (!entryName.empty()) { - buffer_.clear(entryName); - } else { - buffer_.clear(); - } +std::vector PerformanceObserver::takeRecords() { + auto copy = entries_; + entries_.clear(); + return copy; } bool PerformanceObserver::isObserving(facebook::react::PerformanceEntryType type) const { return observedTypes_.contains(type); } -PerformanceObserverEventFilter& PerformanceObserver::getEventFilter() { - return observedTypes_; -} +void PerformanceObserver::observe(facebook::react::PerformanceEntryType type, bool buffered) { + // we assume that `type` was checked on JS side and is correct + observedTypes_.clear(); + observedTypes_.insert(type); + + requiresDroppedEntries_ = true; -void PerformanceObserver::setEntryBuffering(bool isBuffered) { - buffer_.isAlwaysLogged = isBuffered; + if (buffered) { + auto& reporter = PerformanceEntryReporter::getInstance(); + reporter->getBuffer(type).getEntries(std::nullopt, entries_); + scheduleFlushBuffer(); + } } -void PerformanceObserver::setDurationThreshold(DOMHighResTimeStamp durationThreshold) { - buffer_.durationThreshold = durationThreshold; +void PerformanceObserver::observe(std::unordered_set types) { + observedTypes_ = types; + requiresDroppedEntries_ = true; } void PerformanceObserver::scheduleFlushBuffer() { - if (callback_) { - callback_(droppedEntriesCount_); + if (!callback_) { + return; + } + + auto droppedEntriesCount = 0; + + if (requiresDroppedEntries_) { + auto reporter = PerformanceEntryReporter::getInstance(); + + for (auto& entry : observedTypes_) { + droppedEntriesCount += reporter->getBuffer(entry).droppedEntriesCount; + } + + requiresDroppedEntries_ = false; } + + callback_(takeRecords(), droppedEntriesCount); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 501fceb43502a0..34dbbe966816cc 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -14,8 +14,8 @@ namespace facebook::react { -using PerformanceObserverEventFilter = std::unordered_set; -using PerformanceObserverCallback = std::function; +using PerformanceObserverEntryTypeFilter = std::unordered_set; +using PerformanceObserverCallback = std::function, size_t)>; class PerformanceObserverRegistry; @@ -25,41 +25,36 @@ class PerformanceObserverRegistry; * performance entry types. * * Entries are pushed to the observer by the `PerformanceEntryReporter` class, - * which acts as a central hub. + * through the `PerformanceObserverRegistry` class which acts as a central hub. */ class PerformanceObserver { public: - struct PopPendingEntriesResult { - std::vector entries; - uint32_t droppedEntriesCount; - }; - explicit PerformanceObserver( PerformanceObserverCallback&& callback) : callback_(std::move(callback)) {} virtual ~PerformanceObserver(); + [[nodiscard]] bool shouldAdd(PerformanceEntryType type) const; void pushEntry(const PerformanceEntry& entry); - [[nodiscard]] PopPendingEntriesResult popPendingEntries(); - void clearEntries( - std::optional entryType = std::nullopt, - std::string_view entryName = {}); - [[nodiscard]] bool isObserving(PerformanceEntryType type) const; - [[nodiscard]] PerformanceObserverEventFilter& getEventFilter(); + /** + * Returns current observer buffer and clears it. + */ + [[nodiscard]] std::vector takeRecords(); - void setEntryBuffering(bool isBuffered); - void setDurationThreshold(double durationThreshold); + [[nodiscard]] bool isObserving(PerformanceEntryType type) const; + void observe(PerformanceEntryType type, bool buffered); + void observe(std::unordered_set types); private: void scheduleFlushBuffer(); std::weak_ptr registry_; PerformanceObserverCallback callback_; - PerformanceObserverEventFilter observedTypes_; - size_t droppedEntriesCount_{0}; - PerformanceEntryCircularBuffer buffer_{DEFAULT_MAX_BUFFER_SIZE}; + PerformanceObserverEntryTypeFilter observedTypes_; + std::vector entries_; + bool requiresDroppedEntries_ = false; }; inline bool operator==(const PerformanceObserver& lhs, const PerformanceObserver& rhs) { From 30642ece98be08643e7add1213a5197e9c3f5027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 8 Aug 2024 18:06:11 +0200 Subject: [PATCH 14/60] reorganize files; clean up logic; split PerformanceEntry buffers --- .../NativePerformanceObserver.cpp | 21 ++-- .../NativePerformanceObserver.h | 3 +- .../timeline/BoundedConsumableBuffer.h | 2 +- .../performance/timeline/ConsumableEntryMap.h | 110 ------------------ .../timeline/PerformanceEntryBuffer.cpp | 71 ----------- .../timeline/PerformanceEntryBuffer.h | 50 ++------ .../PerformanceEntryCircularBuffer.cpp | 41 +++++++ .../timeline/PerformanceEntryCircularBuffer.h | 34 ++++++ .../timeline/PerformanceEntryKeyedBuffer.cpp | 95 +++++++++++++++ .../timeline/PerformanceEntryKeyedBuffer.h | 46 ++++++++ .../timeline/PerformanceEntryLinearBuffer.cpp | 65 +++++++++++ .../timeline/PerformanceEntryLinearBuffer.h | 34 ++++++ .../timeline/PerformanceEntryReporter.cpp | 16 +-- .../timeline/PerformanceEntryReporter.h | 55 +++++---- .../timeline/PerformanceObserver.cpp | 24 ++-- .../timeline/PerformanceObserver.h | 11 +- .../timeline/PerformanceObserverRegistry.cpp | 16 +-- .../timeline/PerformanceObserverRegistry.h | 2 +- .../specs/NativePerformanceObserver.js | 2 + 19 files changed, 407 insertions(+), 291 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h delete mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index f5d2f2e4e43e33..77c917038d1046 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -46,7 +46,7 @@ jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, NativePe auto observer = std::make_shared(std::move(cb)); jsi::Object observerObj {rt}; - observerObj.setNativeState(rt, std::move(observer)); + observerObj.setNativeState(rt, observer); return observerObj; } @@ -57,13 +57,11 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb if (!observer) { return; } - observer->setRequiresDroppedEntries(buffered); - - - std::unordered_set entryTypes; // observer of type multiple if (options.hasProperty(rt, "entryTypes")) { + std::unordered_set entryTypes; + auto types = options.getPropertyAsObject(rt, "entryTypes").asArray(rt); for (auto i = 0; i < types.size(rt); ++i) { auto rawValue = types.getValueAtIndex(rt, i).asNumber(); @@ -71,18 +69,17 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb entryTypes.insert(*entryType); } } + + observer->observe(types); } else { // single auto buffered = options.getProperty(rt, "buffered").asBool(); auto type = options.getProperty(rt, "type").asNumber(); if (auto entryType = parseEntryType(type); entryType) { - entryTypes.insert(*entryType); + observer->observe(entryType, buffered); } } - // apply collected entryTypes into observer eventFilter - observer->setEntryTypeFilter(entryTypes); - auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); registry.addObserver(observer); } @@ -144,4 +141,10 @@ NativePerformanceObserver::getSupportedPerformanceEntryTypes( return supportedEntries; } +std::vector> +NativePerformanceObserver::getEventCounts(jsi::Runtime& /*rt*/) { + const auto& eventCounts = + PerformanceEntryReporter::getInstance()->getEventCounts(); + return { eventCounts.begin(), eventCounts.end() }; +} } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index fd01bb895796dc..4cb13930e3f341 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -57,8 +57,7 @@ class NativePerformanceObserver void disconnect(jsi::Runtime& rt, jsi::Object observer); std::vector takeRecords(jsi::Runtime& rt, jsi::Object observerObj); - std::vector> getEventCounts( - jsi::Runtime& rt); + std::vector> getEventCounts(jsi::Runtime& rt); void clearEntries( jsi::Runtime& rt, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h index f62fe0697a6c24..dde9efaf96f519 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h @@ -44,7 +44,7 @@ class BoundedConsumableBuffer { * operation, which will depend on whether the buffer reached the max allowed * size and how many are there unconsumed elements. */ - PushStatus add(const T& el) { + PushStatus add(const T&& el) { if (entries_.size() < maxSize_) { // Haven't reached max buffer size yet, just add and grow the buffer entries_.push_back(el); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h b/packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h deleted file mode 100644 index 1940825ca6e665..00000000000000 --- a/packages/react-native/ReactCommon/react/performance/timeline/ConsumableEntryMap.h +++ /dev/null @@ -1,110 +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 - -#include -#include -#include -#include -#include -#include "PerformanceEntry.h" - -namespace facebook::react { - -class ConsumableEntryMap { - public: - ConsumableEntryMap() = default; - - void add(const PerformanceEntry& entry) { - auto& list = entryMap_.at(entry.name); - auto& toConsume = toConsumeMap_.at(entry.name); - list.push_back(entry); - totalEntryCount_ += 1; - totalToConsume_ += 1; - toConsume += 1; - } - - void clear() { - entryMap_.clear(); - toConsumeMap_.clear(); - totalEntryCount_ = 0; - totalToConsume_ = 0; - } - - void clear(const std::string& name) { - if (auto node = entryMap_.find(name); node != entryMap_.end()) { - totalEntryCount_ -= node->second.size(); - totalToConsume_ -= node->second.size(); - toConsumeMap_[name] = 0; - node->second.clear(); - } - } - - std::optional find(const std::string& name) const { - if (auto node = entryMap_.find(name); node != entryMap_.end()) { - if (!node->second.empty()) { - return std::make_optional(node->second.back()); - } - } - - return std::nullopt; - } - - void getEntries(std::vector& target) const { - if (allEntriesCache_.has_value()) { - auto& allEntries = allEntriesCache_.value(); - target.insert(target.end(), allEntries.begin(), allEntries.end()); - return; - } - - std::vector allEntries; - // pre-allocate result vector - allEntries.reserve(totalEntryCount_); - - for (const auto& [_, entries] : entryMap_) { - allEntries.insert(allEntries.end(), entries.begin(), entries.end()); - } - - std::stable_sort( - allEntries.begin(), allEntries.end(), PerformanceEntrySorter{}); - allEntriesCache_ = allEntries; - target.insert(target.end(), allEntries.begin(), allEntries.end()); - } - - /** - * Retrieves buffer entries, whether consumed or not, with predicate - */ - void consume(std::vector& target) { - for (const auto& [name, entries] : entryMap_) { - target.insert( - target.end(), entries.end() - toConsumeMap_[name], entries.end()); - toConsumeMap_[name] = 0; - } - } - - size_t getNumToConsume() const { - return totalToConsume_; - } - - void getEntries( - const std::string& name, - std::vector& target) const { - if (auto node = entryMap_.find(name); node != entryMap_.end()) { - target.insert(target.end(), node->second.begin(), node->second.end()); - } - } - - private: - std::unordered_map> entryMap_{}; - std::unordered_map toConsumeMap_{}; - mutable std::optional> allEntriesCache_; - size_t totalEntryCount_ = 0; - size_t totalToConsume_ = 0; -}; - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp deleted file mode 100644 index 4d36b8f309dcea..00000000000000 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.cpp +++ /dev/null @@ -1,71 +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 "PerformanceEntryBuffer.h" - -namespace facebook::react { - -void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { - auto result = entries.add(entry); - if (result == PerformanceEntryPushStatus::DROP) { - droppedEntriesCount += 1; - } -} - -void PerformanceEntryCircularBuffer::consume(std::vector& target) { - entries.consume(target); -} - -size_t PerformanceEntryCircularBuffer::pendingMessagesCount() const { - return entries.getNumToConsume(); -} - -void PerformanceEntryCircularBuffer::getEntries(std::optional name, std::vector& target) const { - entries.getEntries( - target, [&](const PerformanceEntry& e) { return e.name == name; }); -} - -void PerformanceEntryCircularBuffer::clear() { - entries.clear(); -} - - -void PerformanceEntryCircularBuffer::clear(std::string_view name) { - entries.clear([&](const PerformanceEntry& e) { return e.name == name; }); -} - -void PerformanceEntryKeyedBuffer::add(const facebook::react::PerformanceEntry& entry) { - entries.add(entry); -} - -void PerformanceEntryKeyedBuffer::consume(std::vector& target) { - entries.consume(target); -} - -size_t PerformanceEntryKeyedBuffer::pendingMessagesCount() const { - return entries.getNumToConsume(); -} - -void PerformanceEntryKeyedBuffer::getEntries(std::optional name, std::vector& target) const { - if (name.has_value()) { - std::string nameStr{name.value()}; - entries.getEntries(nameStr, target); - } else { - entries.getEntries(target); - } -} - -void PerformanceEntryKeyedBuffer::clear() { - entries.clear(); -} - -void PerformanceEntryKeyedBuffer::clear(std::string_view name) { - std::string nameStr{name}; - entries.clear(nameStr); -} - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index f10de9e2539fe5..793d6ba75dd510 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -7,8 +7,6 @@ #pragma once -#include "BoundedConsumableBuffer.h" -#include "ConsumableEntryMap.h" #include "PerformanceEntry.h" namespace facebook::react { @@ -34,50 +32,26 @@ struct PerformanceEntryBuffer { virtual void add(const PerformanceEntry& entry) = 0; virtual void consume(std::vector& target) = 0; - virtual size_t pendingMessagesCount() const = 0; + [[nodiscard]] virtual size_t pendingMessagesCount() const = 0; virtual void getEntries( std::optional name, std::vector& target) const = 0; virtual void clear() = 0; virtual void clear(std::string_view name) = 0; -}; - -struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { - BoundedConsumableBuffer entries; - - explicit PerformanceEntryCircularBuffer(size_t size) : entries(size) {} - ~PerformanceEntryCircularBuffer() override = default; - - void add(const PerformanceEntry& entry) override; - void consume(std::vector& target) override; - [[nodiscard]] size_t pendingMessagesCount() const override; - void getEntries( - std::optional name, - std::vector& target) const override; - - void clear() override; - void clear(std::string_view name) override; -}; - -struct PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { - ConsumableEntryMap entries; - - explicit PerformanceEntryKeyedBuffer() = default; - ~PerformanceEntryKeyedBuffer() override = default; - - void add(const PerformanceEntry& entry) override; - void consume(std::vector& target) override; - - size_t pendingMessagesCount() const override; - - void getEntries( - std::optional name, - std::vector& target) const override; + // https://www.w3.org/TR/event-timing/#sec-should-add-performanceeventtiming + // TODO: perhaps move it to a better place + [[nodiscard]] bool shouldAdd(const PerformanceEntry& entry) const { + if (entry.entryType == PerformanceEntryType::EVENT) { + if (entry.duration < durationThreshold) { + // The entries duration is lower than the desired reporting threshold, skip + return false; + } + } - void clear() override; - void clear(std::string_view name) override; + return true; + } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp new file mode 100644 index 00000000000000..7aa68d92712fff --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -0,0 +1,41 @@ +/* + * 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 "PerformanceEntryCircularBuffer.h" + +namespace facebook::react { + +void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { + auto result = entries.add(std::move(entry)); + if (result == PerformanceEntryPushStatus::DROP) { + droppedEntriesCount += 1; + } +} + +void PerformanceEntryCircularBuffer::consume(std::vector& target) { + entries.consume(target); +} + +size_t PerformanceEntryCircularBuffer::pendingMessagesCount() const { + return entries.getNumToConsume(); +} + +void PerformanceEntryCircularBuffer::getEntries(std::optional name, std::vector& target) const { + entries.getEntries( + target, [&](const PerformanceEntry& e) { return e.name == name; }); +} + +void PerformanceEntryCircularBuffer::clear() { + entries.clear(); +} + + +void PerformanceEntryCircularBuffer::clear(std::string_view name) { + entries.clear([&](const PerformanceEntry& e) { return e.name == name; }); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h new file mode 100644 index 00000000000000..ad3a6a1ad59d98 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "PerformanceEntryBuffer.h" +#include "BoundedConsumableBuffer.h" + +namespace facebook::react { + +struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { + BoundedConsumableBuffer entries; + + explicit PerformanceEntryCircularBuffer(size_t size) : entries(size) {} + ~PerformanceEntryCircularBuffer() override = default; + + void add(const PerformanceEntry& entry) override; + void consume(std::vector& target) override; + + [[nodiscard]] size_t pendingMessagesCount() const override; + + void getEntries( + std::optional name, + std::vector& target) const override; + + void clear() override; + void clear(std::string_view name) override; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp new file mode 100644 index 00000000000000..2e6f170f32df29 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -0,0 +1,95 @@ +/* + * 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 "PerformanceEntryKeyedBuffer.h" +#include + +namespace facebook::react { + +void PerformanceEntryKeyedBuffer::add(const PerformanceEntry& entry) { + auto& list = entryMap_.at(entry.name); + auto& toConsume = toConsumeMap_.at(entry.name); + list.push_back(entry); + totalEntryCount_ += 1; + totalToConsume_ += 1; + toConsume += 1; +} + +void PerformanceEntryKeyedBuffer::consume(std::vector& target) { + for (const auto& [name, entries] : entryMap_) { + target.insert( + target.end(), entries.end() - toConsumeMap_[name], entries.end()); + toConsumeMap_[name] = 0; + } +} + +size_t PerformanceEntryKeyedBuffer::pendingMessagesCount() const { + return totalToConsume_; +} + +void PerformanceEntryKeyedBuffer::getEntries(std::optional name, std::vector& target) const { + if (name.has_value()) { + std::string nameStr{name.value()}; + + if (auto node = entryMap_.find(nameStr); node != entryMap_.end()) { + target.insert(target.end(), node->second.begin(), node->second.end()); + } + } else { + getEntries(target); + } +} + +void PerformanceEntryKeyedBuffer::clear() { + entryMap_.clear(); + toConsumeMap_.clear(); + totalEntryCount_ = 0; + totalToConsume_ = 0; +} + +void PerformanceEntryKeyedBuffer::clear(std::string_view nameView) { + std::string name { nameView }; + + if (auto node = entryMap_.find(name); node != entryMap_.end()) { + totalEntryCount_ -= node->second.size(); + totalToConsume_ -= node->second.size(); + toConsumeMap_[name] = 0; + node->second.clear(); + } +} + +std::optional PerformanceEntryKeyedBuffer::find(const std::string& name) const { + if (auto node = entryMap_.find(name); node != entryMap_.end()) { + if (!node->second.empty()) { + return std::make_optional(node->second.back()); + } + } + + return std::nullopt; +} + +void PerformanceEntryKeyedBuffer::getEntries(std::vector& target) const { + if (allEntriesCache_.has_value()) { + auto& allEntries = allEntriesCache_.value(); + target.insert(target.end(), allEntries.begin(), allEntries.end()); + return; + } + + std::vector allEntries; + // pre-allocate result vector + allEntries.reserve(totalEntryCount_); + + for (const auto& [_, entries] : entryMap_) { + allEntries.insert(allEntries.end(), entries.begin(), entries.end()); + } + + std::stable_sort( + allEntries.begin(), allEntries.end(), PerformanceEntrySorter{}); + allEntriesCache_ = allEntries; + target.insert(target.end(), allEntries.begin(), allEntries.end()); +} + +} // namespace facebook::react \ No newline at end of file diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h new file mode 100644 index 00000000000000..050dbd85d460a3 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include "PerformanceEntryBuffer.h" + +namespace facebook::react { + +class PerformanceEntryKeyedBuffer: public PerformanceEntryBuffer { + public: + PerformanceEntryKeyedBuffer() = default; + + void add(const PerformanceEntry& entry) override; + + /** + * Retrieves buffer entries, whether consumed or not, with predicate + */ + void consume(std::vector& target) override; + [[nodiscard]] size_t pendingMessagesCount() const override; + + void getEntries( + std::optional name, + std::vector& target) const override; + + void clear() override; + void clear(std::string_view name) override; + + void getEntries(std::vector& target) const; + std::optional find(const std::string& name) const; + + private: + std::unordered_map> entryMap_{}; + std::unordered_map toConsumeMap_{}; + mutable std::optional> allEntriesCache_; + size_t totalEntryCount_ = 0; + size_t totalToConsume_ = 0; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp new file mode 100644 index 00000000000000..3f04377bef0e9c --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp @@ -0,0 +1,65 @@ +/* + * 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 "PerformanceEntryLinearBuffer.h" +#include + +namespace facebook::react { + +void PerformanceEntryLinearBuffer::add(const PerformanceEntry& entry) { + entries_.push_back(entry); +} + +void PerformanceEntryLinearBuffer::consume(std::vector& target) { + target.reserve(entries_.size()); +} + +std::vector PerformanceEntryLinearBuffer::consume() { + std::vector elements = entries_; + entries_.clear(); + return elements; +} + +size_t PerformanceEntryLinearBuffer::pendingMessagesCount() const { + return entries_.size(); +} + +void PerformanceEntryLinearBuffer::getEntries( + std::optional name, + std::vector& target) const { + if (name.has_value()) { + std::copy_if( + entries_.begin(), + entries_.end(), + target.begin(), + [&name](const PerformanceEntry& entry) { + return entry.name == name.value(); + }); + } + else { + std::copy(entries_.begin(), entries_.end(), target.begin()); + } +} + +void PerformanceEntryLinearBuffer::clear() { + entries_.clear(); +} + +void PerformanceEntryLinearBuffer::clear(std::string_view name) { + entries_.erase( + std::remove_if( + entries_.begin(), + entries_.end(), + [&name](const PerformanceEntry& entry) { return entry.name == name; }), + entries_.end()); +} + +std::vector& PerformanceEntryLinearBuffer::getEntries() { + return entries_; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h new file mode 100644 index 00000000000000..b3bf91713e516a --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "PerformanceEntryBuffer.h" + +namespace facebook::react { + +class PerformanceEntryLinearBuffer: public PerformanceEntryBuffer { + public: + PerformanceEntryLinearBuffer() = default; + + void add(const PerformanceEntry& entry) override; + void consume(std::vector& target) override; + [[nodiscard]] std::vector consume(); + [[nodiscard]] size_t pendingMessagesCount() const override; + void getEntries( + std::optional name, + std::vector& target) const override; + void clear() override; + void clear(std::string_view name) override; + + [[nodiscard]] std::vector& getEntries(); + + private: + std::vector entries_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 8dbcae93479d78..def46671810542 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -24,13 +24,6 @@ DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { : JSExecutor::performanceNow(); } -void PerformanceEntryReporter::setAlwaysLogged( - PerformanceEntryType entryType, - bool isAlwaysLogged) { - auto& buffer = getBufferRef(entryType); - buffer.isAlwaysLogged = isAlwaysLogged; -} - void PerformanceEntryReporter::pushEntry(const PerformanceEntry& entry) { if (entry.entryType == PerformanceEntryType::EVENT) { eventCounts_[entry.name]++; @@ -40,11 +33,8 @@ void PerformanceEntryReporter::pushEntry(const PerformanceEntry& entry) { std::lock_guard lock(entriesMutex_); auto& buffer = getBufferRef(entry.entryType); - if (entry.entryType == PerformanceEntryType::EVENT) { - if (entry.duration < buffer.durationThreshold) { - // The entries duration is lower than the desired reporting threshold, skip - return; - } + if (!buffer.shouldAdd(entry)) { + return; } buffer.add(entry); @@ -144,7 +134,7 @@ DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( const std::string& markName) const { std::lock_guard lock(entriesMutex_); - if (auto it = markBuffer_.entries.find(markName); it) { + if (auto it = markBuffer_.find(markName); it) { return it->startTime; } else { return 0.0; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 75ecbf40cd6261..b98338052df024 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -8,9 +8,9 @@ #pragma once #include -#include "PerformanceObserver.h" #include "PerformanceObserverRegistry.h" -#include "PerformanceEntryBuffer.h" +#include "PerformanceEntryCircularBuffer.h" +#include "PerformanceEntryKeyedBuffer.h" #include #include @@ -33,21 +33,31 @@ class PerformanceEntryReporter { // creation time instead of having the singleton. static std::shared_ptr& getInstance(); - /** - * Marks performance entry buffer of type `entryType` to be buffered even if no observers are attached. - */ - void setAlwaysLogged(PerformanceEntryType entryType, bool isAlwaysLogged); + [[nodiscard]] PerformanceObserverRegistry& getObserverRegistry() { + return *observerRegistry_; + } - /** - * Whenever performance entry buffer of type `entryType` is buffered. + /* + * DOM Performance (High Resolution Time) + * https://www.w3.org/TR/hr-time-3/#dom-performance */ - bool isAlwaysLogged(PerformanceEntryType entryType) const { - return getBuffer(entryType).isAlwaysLogged; + // https://www.w3.org/TR/hr-time-3/#now-method + [[nodiscard]] DOMHighResTimeStamp getCurrentTimeStamp() const; + + void setTimeStampProvider(std::function provider) { + timeStampProvider_ = std::move(provider); } + /* + * Performance Timeline functions + * https://www.w3.org/TR/performance-timeline + */ + // https://www.w3.org/TR/performance-timeline/#queue-a-performanceentry void pushEntry(const PerformanceEntry& entry); // 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 [[nodiscard]] std::vector getEntries( std::optional entryType = std::nullopt, std::string_view entryName = {}) const; @@ -62,21 +72,16 @@ class PerformanceEntryReporter { void logLongTaskEntry(double startTime, double duration); + /* + * Event Timing API functions + * https://www.w3.org/TR/event-timing/ + */ + // https://www.w3.org/TR/event-timing/#dom-performance-eventcounts [[nodiscard]] const std::unordered_map& getEventCounts() const { return eventCounts_; } - [[nodiscard]] DOMHighResTimeStamp getCurrentTimeStamp() const; - - void setTimeStampProvider(std::function provider) { - timeStampProvider_ = std::move(provider); - } - - [[nodiscard]] PerformanceObserverRegistry& getObserverRegistry() { - return *observerRegistry_; - } - - /** + /* * User Timing Level 3 functions * https://w3c.github.io/user-timing/ */ @@ -100,7 +105,9 @@ class PerformanceEntryReporter { std::optional entryType = std::nullopt, std::string_view entryName = {}); - [[nodiscard]] const PerformanceEntryBuffer& getBuffer( + // Instead of having a map of buffers, we store buffer for each type + // separately + [[nodiscard]] inline const PerformanceEntryBuffer& getBuffer( PerformanceEntryType entryType) const { switch (entryType) { case PerformanceEntryType::EVENT: @@ -127,8 +134,6 @@ class PerformanceEntryReporter { std::unordered_map eventCounts_; - uint32_t droppedEntriesCount_{0}; - std::function timeStampProvider_ = nullptr; double getMarkTime(const std::string& markName) const; @@ -138,7 +143,7 @@ class PerformanceEntryReporter { std::string_view entryName, std::vector& res) const; - PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { + [[nodiscard]] inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { switch (entryType) { case PerformanceEntryType::EVENT: return eventBuffer_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 5e6d6681985fdb..829742974dba14 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -1,9 +1,9 @@ /* -* 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. -*/ + * 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 "PerformanceObserver.h" #include "PerformanceObserverRegistry.h" @@ -18,13 +18,11 @@ PerformanceObserver::~PerformanceObserver() { } void PerformanceObserver::pushEntry(const facebook::react::PerformanceEntry& entry) { - entries_.push_back(entry); + buffer_.add(entry); } std::vector PerformanceObserver::takeRecords() { - auto copy = entries_; - entries_.clear(); - return copy; + return buffer_.consume(); } bool PerformanceObserver::isObserving(facebook::react::PerformanceEntryType type) const { @@ -40,16 +38,20 @@ void PerformanceObserver::observe(facebook::react::PerformanceEntryType type, bo if (buffered) { auto& reporter = PerformanceEntryReporter::getInstance(); - reporter->getBuffer(type).getEntries(std::nullopt, entries_); + reporter->getBuffer(type).getEntries(std::nullopt, buffer_.getEntries()); scheduleFlushBuffer(); } } void PerformanceObserver::observe(std::unordered_set types) { - observedTypes_ = types; + observedTypes_ = std::move(types); requiresDroppedEntries_ = true; } +bool PerformanceObserver::shouldAdd(const PerformanceEntry& entry) const { + return buffer_.shouldAdd(entry); +} + void PerformanceObserver::scheduleFlushBuffer() { if (!callback_) { return; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 34dbbe966816cc..c3019a8556c054 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -10,7 +10,7 @@ #include #include #include -#include "PerformanceEntryBuffer.h" +#include "PerformanceEntryLinearBuffer.h" namespace facebook::react { @@ -35,7 +35,6 @@ class PerformanceObserver { virtual ~PerformanceObserver(); - [[nodiscard]] bool shouldAdd(PerformanceEntryType type) const; void pushEntry(const PerformanceEntry& entry); /** @@ -47,13 +46,19 @@ class PerformanceObserver { void observe(PerformanceEntryType type, bool buffered); void observe(std::unordered_set types); + [[nodiscard]] bool shouldAdd(const PerformanceEntry& entry) const; + + [[nodiscard]] const PerformanceEntryBuffer& getBuffer() const { + return buffer_; + } + private: void scheduleFlushBuffer(); std::weak_ptr registry_; PerformanceObserverCallback callback_; PerformanceObserverEntryTypeFilter observedTypes_; - std::vector entries_; + PerformanceEntryLinearBuffer buffer_; bool requiresDroppedEntries_ = false; }; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index f723d89cf8a01e..574aa7e033514d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -1,9 +1,9 @@ /* -* 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. -*/ + * 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 "PerformanceObserverRegistry.h" @@ -21,7 +21,7 @@ void PerformanceObserverRegistry::removeObserver(const PerformanceObserver& obse }); } -void PerformanceObserverRegistry::removeObserver(const std::shared_ptr observer) { +void PerformanceObserverRegistry::removeObserver(const std::shared_ptr& observer) { std::lock_guard guard{observersMutex_}; observers_.erase(observer); } @@ -40,8 +40,10 @@ void PerformanceObserverRegistry::emit(const facebook::react::PerformanceEntry& for (auto& observer_ptr : observers_) { if (auto observer = observer_ptr.lock()) { + auto shouldAdd = observer->shouldAdd(entry); + // push to observer if it is contained within its entry type filter - if (observer->isObserving(entry.entryType)) { + if (observer->isObserving(entry.entryType) && shouldAdd) { observer->pushEntry(entry); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index 3a32fd6bbcd5d3..b4a6c1dafb114a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -19,7 +19,7 @@ class PerformanceObserverRegistry { PerformanceObserverRegistry() = default; void addObserver(const std::weak_ptr& observer); - void removeObserver(const std::shared_ptr observer); + void removeObserver(const std::shared_ptr& observer); void removeObserver(const PerformanceObserver& observer); void emit(const PerformanceEntry& entry); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index 0cf92f2191ed4b..806d167176b40c 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -52,6 +52,8 @@ export interface Spec extends TurboModule { entryName?: string, ) => $ReadOnlyArray; +getSupportedPerformanceEntryTypes: () => $ReadOnlyArray; + + +getEventCounts: () => $ReadOnlyArray<[string, number]>; } export default (TurboModuleRegistry.get( From 6862527dc37a333da83161147ac91d99570f8cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 8 Aug 2024 18:23:01 +0200 Subject: [PATCH 15/60] improve `PerformanceObserver` options bridging --- .../NativePerformanceObserver.cpp | 18 ++++++++---------- .../webperformance/NativePerformanceObserver.h | 12 +++++++++++- .../timeline/PerformanceObserver.cpp | 6 +++--- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 77c917038d1046..cdf91aeea2adf5 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -50,7 +50,7 @@ jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, NativePe return observerObj; } -void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerObj, jsi::Object options) { +void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerObj, NativePerformanceObserverObserveOptions options) { auto observer = std::dynamic_pointer_cast(observerObj.getNativeState(rt)); @@ -59,23 +59,21 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb } // observer of type multiple - if (options.hasProperty(rt, "entryTypes")) { + if (options.entryTypes.has_value()) { std::unordered_set entryTypes; + auto rawTypes = options.entryTypes.value(); - auto types = options.getPropertyAsObject(rt, "entryTypes").asArray(rt); - for (auto i = 0; i < types.size(rt); ++i) { - auto rawValue = types.getValueAtIndex(rt, i).asNumber(); - if (auto entryType = parseEntryType(rawValue); entryType) { + for (auto i = 0; i < rawTypes.size(); ++i) { + if (auto entryType = parseEntryType(rawTypes[i]); entryType) { entryTypes.insert(*entryType); } } - observer->observe(types); + observer->observe(entryTypes); } else { // single - auto buffered = options.getProperty(rt, "buffered").asBool(); - auto type = options.getProperty(rt, "type").asNumber(); - if (auto entryType = parseEntryType(type); entryType) { + auto buffered = options.buffered.value_or(false); + if (auto entryType = parseEntryType(options.type.value()); entryType) { observer->observe(entryType, buffered); } } diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 4cb13930e3f341..39e5e9fa1e9625 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -23,6 +23,13 @@ namespace facebook::react { using NativePerformanceObserverCallback = AsyncCallback&&, size_t>; +using NativePerformanceObserverObserveOptions = NativePerformanceObserverPerformanceObserverInit< + // entryTypes + std::optional>, + // type + std::optional, + // buffered + std::optional>; #pragma mark - Structs @@ -41,6 +48,9 @@ struct Bridging { } }; +template <> +struct Bridging : NativePerformanceObserverPerformanceObserverInitBridging {}; + template <> struct Bridging : NativePerformanceObserverRawPerformanceEntryBridging {}; @@ -53,7 +63,7 @@ class NativePerformanceObserver NativePerformanceObserver(std::shared_ptr jsInvoker); jsi::Object createObserver(jsi::Runtime& rt, NativePerformanceObserverCallback callback); - void observe(jsi::Runtime& rt, jsi::Object observer, jsi::Object options); + void observe(jsi::Runtime& rt, jsi::Object observer, NativePerformanceObserverObserveOptions options); void disconnect(jsi::Runtime& rt, jsi::Object observer); std::vector takeRecords(jsi::Runtime& rt, jsi::Object observerObj); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 829742974dba14..d1518a2e7e78f5 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -17,7 +17,7 @@ PerformanceObserver::~PerformanceObserver() { } } -void PerformanceObserver::pushEntry(const facebook::react::PerformanceEntry& entry) { +void PerformanceObserver::pushEntry(const PerformanceEntry& entry) { buffer_.add(entry); } @@ -25,11 +25,11 @@ std::vector PerformanceObserver::takeRecords() { return buffer_.consume(); } -bool PerformanceObserver::isObserving(facebook::react::PerformanceEntryType type) const { +bool PerformanceObserver::isObserving(PerformanceEntryType type) const { return observedTypes_.contains(type); } -void PerformanceObserver::observe(facebook::react::PerformanceEntryType type, bool buffered) { +void PerformanceObserver::observe(PerformanceEntryType type, bool buffered) { // we assume that `type` was checked on JS side and is correct observedTypes_.clear(); observedTypes_.insert(type); From 863d50e607441f393d90306d8c12cf171dce9acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 13 Sep 2024 13:50:47 +0200 Subject: [PATCH 16/60] chore: prettify Performance Observer public API --- .../timeline/PerformanceObserver.cpp | 2 +- .../timeline/PerformanceObserver.h | 43 ++++++++++++++++++- .../timeline/PerformanceObserverRegistry.cpp | 2 +- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index d1518a2e7e78f5..01f8c91d92fb18 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -17,7 +17,7 @@ PerformanceObserver::~PerformanceObserver() { } } -void PerformanceObserver::pushEntry(const PerformanceEntry& entry) { +void PerformanceObserver::append(const PerformanceEntry& entry) { buffer_.add(entry); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index c3019a8556c054..2eada3c10875e9 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -35,24 +35,63 @@ class PerformanceObserver { virtual ~PerformanceObserver(); - void pushEntry(const PerformanceEntry& entry); + /** + * Appends specified entry to this buffer. + * + * Specified entry is not checked whenever it fits to this buffer + * (as in being observer). It is responsibility of the caller + * to ensure this entry fits into this buffer. + * + * Spec: + * https://w3c.github.io/performance-timeline/#queue-a-performanceentry (step 8.1) + */ + void append(const PerformanceEntry& entry); /** * Returns current observer buffer and clears it. + * + * Spec: + * https://w3c.github.io/performance-timeline/#takerecords-method */ [[nodiscard]] std::vector takeRecords(); + /** + * Checks if the observer is configured to observe specified entry type + * + * Spec: + * https://w3c.github.io/performance-timeline/#takerecords-method (step 7.1) + */ [[nodiscard]] bool isObserving(PerformanceEntryType type) const; + + /** + * Configures the observer to watch for specified entry type. + * + * This operation resets and overrides previous configurations. So consecutive + * calls to this methods remove any previous watch configuration (as per spec). + */ void observe(PerformanceEntryType type, bool buffered); + + /** + * Configures the observer to watch for specified entry type. + * + * This operation resets and overrides previous configurations. So consecutive + * calls to this methods remove any previous watch configuration (as per spec). + */ void observe(std::unordered_set types); + /** + * Determines if specified entry should be added to this buffer. + * + * Spec: + * https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming + */ [[nodiscard]] bool shouldAdd(const PerformanceEntry& entry) const; + private: [[nodiscard]] const PerformanceEntryBuffer& getBuffer() const { return buffer_; } - private: void scheduleFlushBuffer(); std::weak_ptr registry_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 574aa7e033514d..ee6ac34de43f7b 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -44,7 +44,7 @@ void PerformanceObserverRegistry::emit(const facebook::react::PerformanceEntry& // push to observer if it is contained within its entry type filter if (observer->isObserving(entry.entryType) && shouldAdd) { - observer->pushEntry(entry); + observer->append(entry); } } } From dbf0775990bd17d15b53ddd8c33879d150a0f25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 13 Sep 2024 13:57:43 +0200 Subject: [PATCH 17/60] chore: moved PushStatus back to `BoundedConsumableBuffer` --- .../timeline/BoundedConsumableBuffer.h | 16 +++++++++++++++- .../performance/timeline/PerformanceEntry.h | 16 ---------------- .../timeline/PerformanceEntryCircularBuffer.cpp | 2 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h index dde9efaf96f519..b4de6be4c105e0 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h @@ -32,7 +32,21 @@ constexpr size_t DEFAULT_MAX_SIZE = 1024; template class BoundedConsumableBuffer { public: - using PushStatus = PerformanceEntryPushStatus; + /** + * Status of the add/push operation for the `BoundedConsumableBuffer` + * container + */ + enum class PushStatus { + // There was free space in the buffer, element was successfully pushed: + OK = 0, + + // Element was pushed, but had to overwrite some already consumed elements: + OVERWRITE = 1, + + // Element wasn't pushed, as buffer size limit has been reached and it's + // not possible to overwrite already consumed elements anymore: + DROP = 2, + }; explicit BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE) : maxSize_(maxSize) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index f6cb6f1f08771d..eb6e9c07ba324b 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -40,22 +40,6 @@ struct PerformanceEntry { constexpr size_t NUM_PERFORMANCE_ENTRY_TYPES = (size_t)PerformanceEntryType::_NEXT - 1; // Valid types start from 1. -/** - * Status of the add/push operation for the `BoundedConsumableBuffer` - * container - */ -enum class PerformanceEntryPushStatus { - // There was free space in the buffer, element was successfully pushed: - OK = 0, - - // Element was pushed, but had to overwrite some already consumed elements: - OVERWRITE = 1, - - // Element wasn't pushed, as buffer size limit has been reached and it's - // not possible to overwrite already consumed elements anymore: - DROP = 2, -}; - struct PerformanceEntrySorter { bool operator()(const PerformanceEntry& lhs, const PerformanceEntry& rhs) { if (lhs.startTime != rhs.startTime) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index 7aa68d92712fff..c2d9d0a3133d31 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -11,7 +11,7 @@ namespace facebook::react { void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { auto result = entries.add(std::move(entry)); - if (result == PerformanceEntryPushStatus::DROP) { + if (result == BoundedConsumableBuffer::PushStatus::DROP) { droppedEntriesCount += 1; } } From 6e0319c8111cb06029da552f582e63ba2ed1c5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 13 Sep 2024 13:59:21 +0200 Subject: [PATCH 18/60] chore: add more comments about testing only code --- .../react/nativemodule/defaults/DefaultTurboModules.cpp | 1 + .../nativemodule/defaults/React-defaultsnativemodule.podspec | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp index 15b97979aa7558..5f96572033ad3f 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp @@ -30,6 +30,7 @@ namespace facebook::react { return std::make_shared(jsInvoker); } + // TEMPORARY (PR only, for testing) if (name == NativePerformance::kModuleName) { return std::make_shared(jsInvoker); } diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec index cca9b663a35079..69976595d29db9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec @@ -48,5 +48,7 @@ Pod::Spec.new do |s| s.dependency "React-featureflagsnativemodule" s.dependency "React-microtasksnativemodule" s.dependency "React-idlecallbacksnativemodule" + + # TEMPORARY (PR only, for testing) s.dependency "React-webperformancemodule" end From f3450b65e7efeae58d6e5c7a81611d2874697322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 13 Sep 2024 14:02:01 +0200 Subject: [PATCH 19/60] chore: add comment about divergence vs the spec --- .../nativemodule/webperformance/NativePerformanceObserver.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index cdf91aeea2adf5..3a3fe9dd8906e4 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -40,6 +40,10 @@ NativePerformanceObserver::NativePerformanceObserver( : 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)](std::vector&& entries, size_t droppedEntriesCount) -> void { callback.callWithPriority(SchedulerPriority::IdlePriority, std::move(entries), droppedEntriesCount); }; From 13f1200da7aef25167cf5db86bf8391d8ea33d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 13 Sep 2024 14:10:37 +0200 Subject: [PATCH 20/60] fix: narrow down the public API and move the responsibility of filtering to PerformanceObserver from PerformanceObserverRegistry --- .../timeline/PerformanceEntryBuffer.h | 6 ++-- .../timeline/PerformanceObserver.cpp | 16 ++++------ .../timeline/PerformanceObserver.h | 29 ++----------------- .../timeline/PerformanceObserverRegistry.cpp | 7 +---- 4 files changed, 12 insertions(+), 46 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 793d6ba75dd510..17020e9d65f4c5 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -15,14 +15,12 @@ namespace facebook::react { // all") constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; -// Default buffer size limit, per entry type -constexpr size_t DEFAULT_MAX_BUFFER_SIZE = 1024; - /** * Abstract performance entry buffer with reporting flags. * Subtypes differ on how entries are stored. */ -struct PerformanceEntryBuffer { +class PerformanceEntryBuffer { +public: double durationThreshold{DEFAULT_DURATION_THRESHOLD}; size_t droppedEntriesCount{0}; bool isAlwaysLogged{false}; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 01f8c91d92fb18..cb53086f69f74e 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -17,18 +17,18 @@ PerformanceObserver::~PerformanceObserver() { } } -void PerformanceObserver::append(const PerformanceEntry& entry) { - buffer_.add(entry); +void PerformanceObserver::handleEntry(const facebook::react::PerformanceEntry& entry) { + // https://w3c.github.io/performance-timeline/#takerecords-method (step 7.1) + // https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming + if (buffer_.shouldAdd(entry) && observedTypes_.contains(entry.entryType)) { + buffer_.add(entry); + } } std::vector PerformanceObserver::takeRecords() { return buffer_.consume(); } -bool PerformanceObserver::isObserving(PerformanceEntryType type) const { - return observedTypes_.contains(type); -} - void PerformanceObserver::observe(PerformanceEntryType type, bool buffered) { // we assume that `type` was checked on JS side and is correct observedTypes_.clear(); @@ -48,10 +48,6 @@ void PerformanceObserver::observe(std::unordered_set types requiresDroppedEntries_ = true; } -bool PerformanceObserver::shouldAdd(const PerformanceEntry& entry) const { - return buffer_.shouldAdd(entry); -} - void PerformanceObserver::scheduleFlushBuffer() { if (!callback_) { return; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 2eada3c10875e9..f8228caabb7f91 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -36,16 +36,9 @@ class PerformanceObserver { virtual ~PerformanceObserver(); /** - * Appends specified entry to this buffer. - * - * Specified entry is not checked whenever it fits to this buffer - * (as in being observer). It is responsibility of the caller - * to ensure this entry fits into this buffer. - * - * Spec: - * https://w3c.github.io/performance-timeline/#queue-a-performanceentry (step 8.1) + * Append entry to the buffer if this observer should handle this entry. */ - void append(const PerformanceEntry& entry); + void handleEntry(const PerformanceEntry& entry); /** * Returns current observer buffer and clears it. @@ -55,14 +48,6 @@ class PerformanceObserver { */ [[nodiscard]] std::vector takeRecords(); - /** - * Checks if the observer is configured to observe specified entry type - * - * Spec: - * https://w3c.github.io/performance-timeline/#takerecords-method (step 7.1) - */ - [[nodiscard]] bool isObserving(PerformanceEntryType type) const; - /** * Configures the observer to watch for specified entry type. * @@ -79,15 +64,7 @@ class PerformanceObserver { */ void observe(std::unordered_set types); - /** - * Determines if specified entry should be added to this buffer. - * - * Spec: - * https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming - */ - [[nodiscard]] bool shouldAdd(const PerformanceEntry& entry) const; - - private: +private: [[nodiscard]] const PerformanceEntryBuffer& getBuffer() const { return buffer_; } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index ee6ac34de43f7b..0539d025babbf0 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -40,12 +40,7 @@ void PerformanceObserverRegistry::emit(const facebook::react::PerformanceEntry& for (auto& observer_ptr : observers_) { if (auto observer = observer_ptr.lock()) { - auto shouldAdd = observer->shouldAdd(entry); - - // push to observer if it is contained within its entry type filter - if (observer->isObserving(entry.entryType) && shouldAdd) { - observer->append(entry); - } + observer->handleEntry(entry); } } } From 573b329cdb996be931b748eb8a7d2ad748f84b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 13 Sep 2024 14:14:31 +0200 Subject: [PATCH 21/60] chore: remove unnecessary [[nodiscard]] attirubtes --- .../performance/timeline/PerformanceEntryBuffer.h | 4 ++-- .../timeline/PerformanceEntryCircularBuffer.h | 2 +- .../timeline/PerformanceEntryKeyedBuffer.h | 2 +- .../timeline/PerformanceEntryLinearBuffer.h | 4 ++-- .../performance/timeline/PerformanceEntryReporter.h | 12 ++++++------ .../react/performance/timeline/PerformanceObserver.h | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 17020e9d65f4c5..7b979db4523f67 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -30,7 +30,7 @@ class PerformanceEntryBuffer { virtual void add(const PerformanceEntry& entry) = 0; virtual void consume(std::vector& target) = 0; - [[nodiscard]] virtual size_t pendingMessagesCount() const = 0; + virtual size_t pendingMessagesCount() const = 0; virtual void getEntries( std::optional name, std::vector& target) const = 0; @@ -40,7 +40,7 @@ class PerformanceEntryBuffer { // https://www.w3.org/TR/event-timing/#sec-should-add-performanceeventtiming // TODO: perhaps move it to a better place - [[nodiscard]] bool shouldAdd(const PerformanceEntry& entry) const { + bool shouldAdd(const PerformanceEntry& entry) const { if (entry.entryType == PerformanceEntryType::EVENT) { if (entry.duration < durationThreshold) { // The entries duration is lower than the desired reporting threshold, skip diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index ad3a6a1ad59d98..63d04816331133 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -21,7 +21,7 @@ struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; void consume(std::vector& target) override; - [[nodiscard]] size_t pendingMessagesCount() const override; + size_t pendingMessagesCount() const override; void getEntries( std::optional name, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index 050dbd85d460a3..d46787ba319898 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -23,7 +23,7 @@ class PerformanceEntryKeyedBuffer: public PerformanceEntryBuffer { * Retrieves buffer entries, whether consumed or not, with predicate */ void consume(std::vector& target) override; - [[nodiscard]] size_t pendingMessagesCount() const override; + size_t pendingMessagesCount() const override; void getEntries( std::optional name, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h index b3bf91713e516a..8d800e6f66ae25 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h @@ -18,14 +18,14 @@ class PerformanceEntryLinearBuffer: public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; void consume(std::vector& target) override; [[nodiscard]] std::vector consume(); - [[nodiscard]] size_t pendingMessagesCount() const override; + size_t pendingMessagesCount() const override; void getEntries( std::optional name, std::vector& target) const override; void clear() override; void clear(std::string_view name) override; - [[nodiscard]] std::vector& getEntries(); + std::vector& getEntries(); private: std::vector entries_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index b98338052df024..9f069842c0fc6a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -33,7 +33,7 @@ class PerformanceEntryReporter { // creation time instead of having the singleton. static std::shared_ptr& getInstance(); - [[nodiscard]] PerformanceObserverRegistry& getObserverRegistry() { + PerformanceObserverRegistry& getObserverRegistry() { return *observerRegistry_; } @@ -42,7 +42,7 @@ class PerformanceEntryReporter { * https://www.w3.org/TR/hr-time-3/#dom-performance */ // https://www.w3.org/TR/hr-time-3/#now-method - [[nodiscard]] DOMHighResTimeStamp getCurrentTimeStamp() const; + DOMHighResTimeStamp getCurrentTimeStamp() const; void setTimeStampProvider(std::function provider) { timeStampProvider_ = std::move(provider); @@ -58,7 +58,7 @@ class PerformanceEntryReporter { // 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 - [[nodiscard]] std::vector getEntries( + std::vector getEntries( std::optional entryType = std::nullopt, std::string_view entryName = {}) const; @@ -77,7 +77,7 @@ class PerformanceEntryReporter { * https://www.w3.org/TR/event-timing/ */ // https://www.w3.org/TR/event-timing/#dom-performance-eventcounts - [[nodiscard]] const std::unordered_map& getEventCounts() const { + const std::unordered_map& getEventCounts() const { return eventCounts_; } @@ -107,7 +107,7 @@ class PerformanceEntryReporter { // Instead of having a map of buffers, we store buffer for each type // separately - [[nodiscard]] inline const PerformanceEntryBuffer& getBuffer( + inline const PerformanceEntryBuffer& getBuffer( PerformanceEntryType entryType) const { switch (entryType) { case PerformanceEntryType::EVENT: @@ -143,7 +143,7 @@ class PerformanceEntryReporter { std::string_view entryName, std::vector& res) const; - [[nodiscard]] inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { + inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { switch (entryType) { case PerformanceEntryType::EVENT: return eventBuffer_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index f8228caabb7f91..18477a314d2c18 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -65,7 +65,7 @@ class PerformanceObserver { void observe(std::unordered_set types); private: - [[nodiscard]] const PerformanceEntryBuffer& getBuffer() const { + const PerformanceEntryBuffer& getBuffer() const { return buffer_; } From b2f039aaf1b6b70e4d77e48b60f266899bceeb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 13 Sep 2024 14:29:59 +0200 Subject: [PATCH 22/60] chore: rename `PerformanceObserverRegistry::emit` to `queuePerformanceEntry` --- .../react/performance/timeline/PerformanceEntryReporter.cpp | 2 +- .../react/performance/timeline/PerformanceObserverRegistry.cpp | 2 +- .../react/performance/timeline/PerformanceObserverRegistry.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index def46671810542..d4dd7051072390 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -40,7 +40,7 @@ void PerformanceEntryReporter::pushEntry(const PerformanceEntry& entry) { buffer.add(entry); } - observerRegistry_->emit(entry); + observerRegistry_->queuePerformanceEntry(entry); } void PerformanceEntryReporter::mark( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 0539d025babbf0..0b9c0699295c1f 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -26,7 +26,7 @@ void PerformanceObserverRegistry::removeObserver(const std::shared_ptr& observer); void removeObserver(const PerformanceObserver& observer); - void emit(const PerformanceEntry& entry); + void queuePerformanceEntry(const PerformanceEntry& entry); private: mutable std::mutex observersMutex_; From f613d446b3bbcd5df633eca307bc30bd15e3b31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 16 Sep 2024 12:39:32 +0200 Subject: [PATCH 23/60] chore: add `OpaqueNativeObserverHandle` type --- .../webapis/performance/specs/NativePerformanceObserver.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index 806d167176b40c..92be5e025f4cbc 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -14,6 +14,8 @@ import * as TurboModuleRegistry from '../../../../../Libraries/TurboModule/Turbo export type RawPerformanceEntryType = number; +export type OpaqueNativeObserverHandle = mixed; + export type RawPerformanceEntry = {| name: string, entryType: RawPerformanceEntryType, @@ -37,7 +39,7 @@ export type PerformanceObserverInit = { }; export interface Spec extends TurboModule { - +createObserver: (callback: () => void) => mixed; + +createObserver: (callback: () => void) => OpaqueNativeObserverHandle; +observe: (observer: mixed, options: PerformanceObserverInit) => void; +disconnect: (observer: mixed) => void; From 8e1217d1a441a90b7c4617a160d5bb938a8307e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 16 Sep 2024 13:41:31 +0200 Subject: [PATCH 24/60] chore: remove generic `pushEntry` from PerformanceEntryReporter --- .../NativePerformanceObserver.cpp | 17 +-- .../timeline/PerformanceEntryBuffer.h | 15 --- .../timeline/PerformanceEntryReporter.cpp | 121 ++++++++++-------- .../timeline/PerformanceEntryReporter.h | 47 +++---- .../timeline/PerformanceObserver.cpp | 9 +- 5 files changed, 101 insertions(+), 108 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 3a3fe9dd8906e4..a40b92087d1fb1 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -26,15 +26,6 @@ NativePerformanceObserverModuleProvider( } namespace facebook::react { - -std::optional parseEntryType(int rawValue) { - if (rawValue < 0 || rawValue >= NUM_PERFORMANCE_ENTRY_TYPES) { - return std::nullopt; - } - - return static_cast(rawValue); -} - NativePerformanceObserver::NativePerformanceObserver( std::shared_ptr jsInvoker) : NativePerformanceObserverCxxSpec(std::move(jsInvoker)) {} @@ -68,17 +59,15 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb auto rawTypes = options.entryTypes.value(); for (auto i = 0; i < rawTypes.size(); ++i) { - if (auto entryType = parseEntryType(rawTypes[i]); entryType) { - entryTypes.insert(*entryType); - } + entryTypes.insert(Bridging::fromJs(rt, rawTypes[i])); } observer->observe(entryTypes); } else { // single auto buffered = options.buffered.value_or(false); - if (auto entryType = parseEntryType(options.type.value()); entryType) { - observer->observe(entryType, buffered); + if (options.type.has_value()) { + observer->observe(static_cast(options.type.value()), buffered); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 7b979db4523f67..bf98f03f3bcff5 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -23,7 +23,6 @@ class PerformanceEntryBuffer { public: double durationThreshold{DEFAULT_DURATION_THRESHOLD}; size_t droppedEntriesCount{0}; - bool isAlwaysLogged{false}; explicit PerformanceEntryBuffer() = default; virtual ~PerformanceEntryBuffer() = default; @@ -36,20 +35,6 @@ class PerformanceEntryBuffer { std::vector& target) const = 0; virtual void clear() = 0; virtual void clear(std::string_view name) = 0; - - - // https://www.w3.org/TR/event-timing/#sec-should-add-performanceeventtiming - // TODO: perhaps move it to a better place - bool shouldAdd(const PerformanceEntry& entry) const { - if (entry.entryType == PerformanceEntryType::EVENT) { - if (entry.duration < durationThreshold) { - // The entries duration is lower than the desired reporting threshold, skip - return false; - } - } - - return true; - } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index d4dd7051072390..abb57eb2745db0 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -24,53 +24,42 @@ DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { : JSExecutor::performanceNow(); } -void PerformanceEntryReporter::pushEntry(const PerformanceEntry& entry) { - if (entry.entryType == PerformanceEntryType::EVENT) { - eventCounts_[entry.name]++; - } +void PerformanceEntryReporter::mark( + const std::string& name, + const std::optional& startTime) { + const auto entry = PerformanceEntry { + .name = name, + .entryType = PerformanceEntryType::MARK, + .startTime = startTime ? *startTime : getCurrentTimeStamp() + }; { std::lock_guard lock(entriesMutex_); - auto& buffer = getBufferRef(entry.entryType); - - if (!buffer.shouldAdd(entry)) { - return; - } - - buffer.add(entry); + markBuffer_.add(entry); } observerRegistry_->queuePerformanceEntry(entry); } -void PerformanceEntryReporter::mark( - const std::string& name, - const std::optional& startTime) { - pushEntry(PerformanceEntry{ - .name = name, - .entryType = PerformanceEntryType::MARK, - .startTime = startTime ? *startTime : getCurrentTimeStamp()}); -} - void PerformanceEntryReporter::clearEntries( std::optional entryType, std::string_view entryName) { // Clear all entry types if (!entryType) { - for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) { - clearEntries(static_cast(i), entryName); - } - - return; - } - - auto& buffer = getBufferRef(*entryType); - if (!entryName.empty()) { std::lock_guard lock(entriesMutex_); - buffer.clear(entryName); + markBuffer_.clear(); + measureBuffer_.clear(); + eventBuffer_.clear(); + longTaskBuffer_.clear(); } else { - std::lock_guard lock(entriesMutex_); - buffer.clear(); + auto& buffer = getBufferRef(*entryType); + if (!entryName.empty()) { + std::lock_guard lock(entriesMutex_); + buffer.clear(entryName); + } else { + std::lock_guard lock(entriesMutex_); + buffer.clear(); + } } } @@ -123,11 +112,19 @@ void PerformanceEntryReporter::measure( DOMHighResTimeStamp durationVal = duration ? *duration : endTimeVal - startTimeVal; - pushEntry( - {.name = std::string(name), - .entryType = PerformanceEntryType::MEASURE, - .startTime = startTimeVal, - .duration = durationVal}); + auto const entry = PerformanceEntry { + .name = std::string(name), + .entryType = PerformanceEntryType::MEASURE, + .startTime = startTimeVal, + .duration = durationVal + }; + + { + std::lock_guard lock(entriesMutex_); + measureBuffer_.add(entry); + } + + observerRegistry_->queuePerformanceEntry(entry); } DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( @@ -148,24 +145,48 @@ void PerformanceEntryReporter::logEventEntry( DOMHighResTimeStamp processingStart, DOMHighResTimeStamp processingEnd, uint32_t interactionId) { - pushEntry( - {.name = std::move(name), - .entryType = PerformanceEntryType::EVENT, - .startTime = startTime, - .duration = duration, - .processingStart = processingStart, - .processingEnd = processingEnd, - .interactionId = interactionId}); + eventCounts_[name]++; + + auto const entry = PerformanceEntry { + .name = std::move(name), + .entryType = PerformanceEntryType::EVENT, + .startTime = startTime, + .duration = duration, + .processingStart = processingStart, + .processingEnd = processingEnd, + .interactionId = interactionId + }; + + { + std::lock_guard lock(entriesMutex_); + + if (entry.duration < eventBuffer_.durationThreshold) { + // The entries duration is lower than the desired reporting threshold, skip + return; + } + + eventBuffer_.add(entry); + } + + observerRegistry_->queuePerformanceEntry(entry); } void PerformanceEntryReporter::logLongTaskEntry( DOMHighResTimeStamp startTime, DOMHighResTimeStamp duration) { - pushEntry( - {.name = std::string{"self"}, - .entryType = PerformanceEntryType::LONGTASK, - .startTime = startTime, - .duration = duration}); + auto const entry = PerformanceEntry { + .name = std::string{"self"}, + .entryType = PerformanceEntryType::LONGTASK, + .startTime = startTime, + .duration = duration + }; + + { + std::lock_guard lock(entriesMutex_); + longTaskBuffer_.add(entry); + } + + observerRegistry_->queuePerformanceEntry(entry); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 9f069842c0fc6a..e82c78a49d2ed7 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -19,7 +19,8 @@ namespace facebook::react { -constexpr size_t MAX_BUFFER_SIZE_EVENT = 150; +constexpr size_t EVENT_BUFFER_SIZE = 150; +constexpr size_t LONG_TASK_BUFFER_SIZE = 200; constexpr DOMHighResTimeStamp LONG_TASK_DURATION_THRESHOLD_MS = 50.0; @@ -48,13 +49,6 @@ class PerformanceEntryReporter { timeStampProvider_ = std::move(provider); } - /* - * Performance Timeline functions - * https://www.w3.org/TR/performance-timeline - */ - // https://www.w3.org/TR/performance-timeline/#queue-a-performanceentry - void pushEntry(const PerformanceEntry& entry); - // 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 @@ -105,30 +99,12 @@ class PerformanceEntryReporter { std::optional entryType = std::nullopt, std::string_view entryName = {}); - // Instead of having a map of buffers, we store buffer for each type - // separately - inline const PerformanceEntryBuffer& getBuffer( - PerformanceEntryType entryType) const { - switch (entryType) { - case PerformanceEntryType::EVENT: - return eventBuffer_; - case PerformanceEntryType::MARK: - return markBuffer_; - case PerformanceEntryType::MEASURE: - return measureBuffer_; - case PerformanceEntryType::LONGTASK: - return longTaskBuffer_; - default: - assert(0 && "Unhandled PerformanceEntryType"); - } - } - private: std::unique_ptr observerRegistry_; mutable std::mutex entriesMutex_; - PerformanceEntryCircularBuffer eventBuffer_{MAX_BUFFER_SIZE_EVENT}; - PerformanceEntryCircularBuffer longTaskBuffer_{MAX_BUFFER_SIZE_EVENT}; + PerformanceEntryCircularBuffer eventBuffer_{EVENT_BUFFER_SIZE}; + PerformanceEntryCircularBuffer longTaskBuffer_{LONG_TASK_BUFFER_SIZE}; PerformanceEntryKeyedBuffer markBuffer_; PerformanceEntryKeyedBuffer measureBuffer_; @@ -143,6 +119,21 @@ class PerformanceEntryReporter { std::string_view entryName, std::vector& res) const; + inline const PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) const { + switch (entryType) { + case PerformanceEntryType::EVENT: + return eventBuffer_; + case PerformanceEntryType::MARK: + return markBuffer_; + case PerformanceEntryType::MEASURE: + return measureBuffer_; + case PerformanceEntryType::LONGTASK: + return longTaskBuffer_; + default: + assert(0 && "Unhandled PerformanceEntryType"); + } + } + inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { switch (entryType) { case PerformanceEntryType::EVENT: diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index cb53086f69f74e..8acd8afcc9abb0 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -20,7 +20,14 @@ PerformanceObserver::~PerformanceObserver() { void PerformanceObserver::handleEntry(const facebook::react::PerformanceEntry& entry) { // https://w3c.github.io/performance-timeline/#takerecords-method (step 7.1) // https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming - if (buffer_.shouldAdd(entry) && observedTypes_.contains(entry.entryType)) { + if (entry.entryType == PerformanceEntryType::EVENT) { + if (entry.duration < buffer_.durationThreshold) { + // The entries duration is lower than the desired reporting threshold, skip + return; + } + } + + if (observedTypes_.contains(entry.entryType)) { buffer_.add(entry); } } From 254613fbdf659f2243714a87719c3557f3dd5fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 16 Sep 2024 13:47:04 +0200 Subject: [PATCH 25/60] chore: bring back public `getBuffer` --- .../timeline/PerformanceEntryReporter.h | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index e82c78a49d2ed7..e36eadf690e623 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -99,6 +99,21 @@ class PerformanceEntryReporter { std::optional entryType = std::nullopt, std::string_view entryName = {}); + inline const PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) const { + switch (entryType) { + case PerformanceEntryType::EVENT: + return eventBuffer_; + case PerformanceEntryType::MARK: + return markBuffer_; + case PerformanceEntryType::MEASURE: + return measureBuffer_; + case PerformanceEntryType::LONGTASK: + return longTaskBuffer_; + default: + assert(0 && "Unhandled PerformanceEntryType"); + } + } + private: std::unique_ptr observerRegistry_; @@ -119,21 +134,6 @@ class PerformanceEntryReporter { std::string_view entryName, std::vector& res) const; - inline const PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) const { - switch (entryType) { - case PerformanceEntryType::EVENT: - return eventBuffer_; - case PerformanceEntryType::MARK: - return markBuffer_; - case PerformanceEntryType::MEASURE: - return measureBuffer_; - case PerformanceEntryType::LONGTASK: - return longTaskBuffer_; - default: - assert(0 && "Unhandled PerformanceEntryType"); - } - } - inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { switch (entryType) { case PerformanceEntryType::EVENT: From b7a1732a23184432379b4fa36339e0e5fba87a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 16 Sep 2024 13:47:17 +0200 Subject: [PATCH 26/60] fix: multiple types buffering is not allowed --- .../react/performance/timeline/PerformanceObserver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 8acd8afcc9abb0..5175b85be78ad5 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -52,7 +52,7 @@ void PerformanceObserver::observe(PerformanceEntryType type, bool buffered) { void PerformanceObserver::observe(std::unordered_set types) { observedTypes_ = std::move(types); - requiresDroppedEntries_ = true; + requiresDroppedEntries_ = false; } void PerformanceObserver::scheduleFlushBuffer() { From 574fa982bb231a68ea7227f95cd90d631b1d844a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 11:31:21 +0200 Subject: [PATCH 27/60] fix: update JS layer; make observer buffer a vector; support `durationThreshold` --- .../NativePerformanceObserver.h | 5 ++- .../timeline/PerformanceObserver.cpp | 19 +++++++----- .../timeline/PerformanceObserver.h | 31 ++++++++++++++----- .../specs/NativePerformanceObserver.js | 5 ++- 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 39e5e9fa1e9625..6b2b227e7bca1b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -29,7 +29,10 @@ using NativePerformanceObserverObserveOptions = NativePerformanceObserverPerform // type std::optional, // buffered - std::optional>; + std::optional, + // durationThreshold + std::optional +>; #pragma mark - Structs diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 5175b85be78ad5..60cce56f5d2dee 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -21,38 +21,43 @@ void PerformanceObserver::handleEntry(const facebook::react::PerformanceEntry& e // https://w3c.github.io/performance-timeline/#takerecords-method (step 7.1) // https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming if (entry.entryType == PerformanceEntryType::EVENT) { - if (entry.duration < buffer_.durationThreshold) { + if (entry.duration < durationThreshold_) { // The entries duration is lower than the desired reporting threshold, skip return; } } if (observedTypes_.contains(entry.entryType)) { - buffer_.add(entry); + buffer_.push_back(entry); } } std::vector PerformanceObserver::takeRecords() { - return buffer_.consume(); + std::vector result; + buffer_.swap(result); + return result; } -void PerformanceObserver::observe(PerformanceEntryType type, bool buffered) { +void PerformanceObserver::observe(PerformanceEntryType type, PerformanceObserverObserveSingleOptions options) { // we assume that `type` was checked on JS side and is correct observedTypes_.clear(); observedTypes_.insert(type); + durationThreshold_ = options.durationThreshold; + requiresDroppedEntries_ = true; - if (buffered) { + if (options.buffered) { auto& reporter = PerformanceEntryReporter::getInstance(); - reporter->getBuffer(type).getEntries(std::nullopt, buffer_.getEntries()); + reporter->getBuffer(type).getEntries(std::nullopt, buffer_); scheduleFlushBuffer(); } } -void PerformanceObserver::observe(std::unordered_set types) { +void PerformanceObserver::observe(std::unordered_set types, PerformanceObserverObserveMultipleOptions options) { observedTypes_ = std::move(types); requiresDroppedEntries_ = false; + durationThreshold_ = options.durationThreshold; } void PerformanceObserver::scheduleFlushBuffer() { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 18477a314d2c18..44b6ce439bd879 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -19,6 +19,24 @@ using PerformanceObserverCallback = std::function types); + void observe(std::unordered_set types, PerformanceObserverObserveMultipleOptions options = {}); private: - const PerformanceEntryBuffer& getBuffer() const { - return buffer_; - } - void scheduleFlushBuffer(); std::weak_ptr registry_; PerformanceObserverCallback callback_; PerformanceObserverEntryTypeFilter observedTypes_; - PerformanceEntryLinearBuffer buffer_; + + /// https://www.w3.org/TR/event-timing/#sec-modifications-perf-timeline + double durationThreshold_{DEFAULT_DURATION_THRESHOLD}; + std::vector buffer_; bool requiresDroppedEntries_ = false; }; diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index 92be5e025f4cbc..eeb53c7ab5003a 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -16,6 +16,8 @@ export type RawPerformanceEntryType = number; export type OpaqueNativeObserverHandle = mixed; +export type NativeBatchedObserverCallback = (entries: $ReadOnlyArray, droppedEntriesCount: number) => void; + export type RawPerformanceEntry = {| name: string, entryType: RawPerformanceEntryType, @@ -36,10 +38,11 @@ export type PerformanceObserverInit = { entryTypes: $ReadOnlyArray; type: number; buffered: boolean; + durationThreshold: number; }; export interface Spec extends TurboModule { - +createObserver: (callback: () => void) => OpaqueNativeObserverHandle; + +createObserver: (callback: NativeBatchedObserverCallback) => OpaqueNativeObserverHandle; +observe: (observer: mixed, options: PerformanceObserverInit) => void; +disconnect: (observer: mixed) => void; From 6dc3b4a5ad9dbf57f826d00c89ee35fb0897620f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 13:09:29 +0200 Subject: [PATCH 28/60] fix: update native and JS layer --- .../NativePerformanceObserver.cpp | 23 +++++++++++++++---- .../NativePerformanceObserver.h | 2 ++ .../timeline/PerformanceObserver.cpp | 22 ++++++++++++------ .../timeline/PerformanceObserver.h | 9 +++++++- .../performance/PerformanceObserver.js | 3 ++- .../specs/NativePerformanceObserver.js | 9 ++++---- 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index a40b92087d1fb1..288ba02afd237b 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -35,8 +35,8 @@ jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, NativePe // 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)](std::vector&& entries, size_t droppedEntriesCount) -> void { - callback.callWithPriority(SchedulerPriority::IdlePriority, std::move(entries), droppedEntriesCount); + PerformanceObserverCallback cb = [callback = std::move(callback)]() -> void { + callback.callWithPriority(SchedulerPriority::IdlePriority); }; auto observer = std::make_shared(std::move(cb)); @@ -45,6 +45,17 @@ jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, NativePe return observerObj; } +double NativePerformanceObserver::getDroppedEntriesCount(jsi::Runtime& rt, jsi::Object observerObj) { + auto observer = + std::dynamic_pointer_cast(observerObj.getNativeState(rt)); + + if (!observer) { + return 0; + } + + return observer->getDroppedEntriesCount(); +} + void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerObj, NativePerformanceObserverObserveOptions options) { auto observer = std::dynamic_pointer_cast(observerObj.getNativeState(rt)); @@ -52,6 +63,7 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb if (!observer) { return; } + auto durationThreshold = options.durationThreshold.value_or(0.0); // observer of type multiple if (options.entryTypes.has_value()) { @@ -62,12 +74,15 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb entryTypes.insert(Bridging::fromJs(rt, rawTypes[i])); } - observer->observe(entryTypes); + observer->observe(entryTypes, { .durationThreshold = durationThreshold }); } else { // single auto buffered = options.buffered.value_or(false); if (options.type.has_value()) { - observer->observe(static_cast(options.type.value()), buffered); + observer->observe(static_cast(options.type.value()), { + .buffered = buffered, + .durationThreshold = durationThreshold + }); } } diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 6b2b227e7bca1b..8ad0cfb32597ba 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -66,6 +66,8 @@ class NativePerformanceObserver NativePerformanceObserver(std::shared_ptr jsInvoker); jsi::Object createObserver(jsi::Runtime& rt, NativePerformanceObserverCallback callback); + double getDroppedEntriesCount(jsi::Runtime& rt, jsi::Object observerObj); + void observe(jsi::Runtime& rt, jsi::Object observer, NativePerformanceObserverObserveOptions options); void disconnect(jsi::Runtime& rt, jsi::Object observer); std::vector takeRecords(jsi::Runtime& rt, jsi::Object observerObj); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 60cce56f5d2dee..b2ad23939a9701 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -60,12 +60,8 @@ void PerformanceObserver::observe(std::unordered_set types durationThreshold_ = options.durationThreshold; } -void PerformanceObserver::scheduleFlushBuffer() { - if (!callback_) { - return; - } - - auto droppedEntriesCount = 0; +double PerformanceObserver::getDroppedEntriesCount() { + double droppedEntriesCount = 0; if (requiresDroppedEntries_) { auto reporter = PerformanceEntryReporter::getInstance(); @@ -77,7 +73,19 @@ void PerformanceObserver::scheduleFlushBuffer() { requiresDroppedEntries_ = false; } - callback_(takeRecords(), droppedEntriesCount); + return droppedEntriesCount; +} + +void PerformanceObserver::scheduleFlushBuffer() { + if (!callback_) { + return; + } + + if (!didScheduleFlushBuffer) { + didScheduleFlushBuffer = true; + + callback_(); + } } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 44b6ce439bd879..282304621b159a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -15,7 +15,7 @@ namespace facebook::react { using PerformanceObserverEntryTypeFilter = std::unordered_set; -using PerformanceObserverCallback = std::function, size_t)>; +using PerformanceObserverCallback = std::function; class PerformanceObserverRegistry; @@ -82,6 +82,12 @@ class PerformanceObserver { */ void observe(std::unordered_set types, PerformanceObserverObserveMultipleOptions options = {}); + /** + * Internal function called by JS bridge to get number of dropped entries count + * counted at call time. + */ + double getDroppedEntriesCount(); + private: void scheduleFlushBuffer(); @@ -92,6 +98,7 @@ class PerformanceObserver { /// https://www.w3.org/TR/event-timing/#sec-modifications-perf-timeline double durationThreshold_{DEFAULT_DURATION_THRESHOLD}; std::vector buffer_; + bool didScheduleFlushBuffer = false; bool requiresDroppedEntries_ = false; }; diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index cf6d26d2733cbe..9164ff73ecd0af 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -174,8 +174,9 @@ export class PerformanceObserver { } #createNativeObserver() { - return NativePerformanceObserver.createObserver((droppedEntriesCount) => { + return NativePerformanceObserver.createObserver(() => { const entryList = new PerformanceObserverEntryList(NativePerformanceObserver.takeRecords(this.#observerHandle)); + const droppedEntriesCount = NativePerformanceObserver.getDroppedEntriesCount(this.#nativeObserverHandle); this.#callback(entryList, this, { droppedEntriesCount }); }); } diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index eeb53c7ab5003a..2fde99092b2b54 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -16,7 +16,7 @@ export type RawPerformanceEntryType = number; export type OpaqueNativeObserverHandle = mixed; -export type NativeBatchedObserverCallback = (entries: $ReadOnlyArray, droppedEntriesCount: number) => void; +export type NativeBatchedObserverCallback = () => void; export type RawPerformanceEntry = {| name: string, @@ -43,10 +43,11 @@ export type PerformanceObserverInit = { export interface Spec extends TurboModule { +createObserver: (callback: NativeBatchedObserverCallback) => OpaqueNativeObserverHandle; + +getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle) => number; - +observe: (observer: mixed, options: PerformanceObserverInit) => void; - +disconnect: (observer: mixed) => void; - +takeRecords: (observer: mixed) => $ReadOnlyArray; + +observe: (observer: OpaqueNativeObserverHandle, options: PerformanceObserverInit) => void; + +disconnect: (observer: OpaqueNativeObserverHandle) => void; + +takeRecords: (observer: OpaqueNativeObserverHandle) => $ReadOnlyArray; +clearEntries: ( entryType?: RawPerformanceEntryType, From 6a12b1935bd419f6682c96ff1e4c19ad1bf9c150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 13:11:09 +0200 Subject: [PATCH 29/60] fix: make performance observer inherit from NativeState --- .../react/performance/timeline/PerformanceObserver.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 282304621b159a..cddfd35a7994e8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "PerformanceEntryLinearBuffer.h" namespace facebook::react { @@ -45,7 +46,7 @@ struct PerformanceObserverObserveSingleOptions: public PerformanceObserverObserv * Entries are pushed to the observer by the `PerformanceEntryReporter` class, * through the `PerformanceObserverRegistry` class which acts as a central hub. */ -class PerformanceObserver { +class PerformanceObserver: public jsi::NativeState { public: explicit PerformanceObserver( PerformanceObserverCallback&& callback) From 6056aa774d89c0bb937470f32350d8a5b3f64f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 13:16:52 +0200 Subject: [PATCH 30/60] fix: tests --- .../tests/RuntimeSchedulerTest.cpp | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp index d8a04d17c0e1fa..48b26b8a7a0c7f 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/tests/RuntimeSchedulerTest.cpp @@ -84,8 +84,6 @@ class RuntimeSchedulerTest : public testing::TestWithParam { performanceEntryReporter_ = PerformanceEntryReporter::getInstance().get(); - performanceEntryReporter_->startReporting(PerformanceEntryType::LONGTASK); - runtimeScheduler_ = std::make_unique(runtimeExecutor, stubNow); @@ -94,7 +92,7 @@ class RuntimeSchedulerTest : public testing::TestWithParam { void TearDown() override { ReactNativeFeatureFlags::dangerouslyReset(); - performanceEntryReporter_->popPendingEntries(); + performanceEntryReporter_->clearEntries(); } jsi::Function createHostFunctionFromLambda( @@ -1244,8 +1242,8 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasks) { EXPECT_EQ(didRunTask1, 1); EXPECT_EQ(stubQueue_->size(), 0); - auto pendingEntries = performanceEntryReporter_->popPendingEntries(); - EXPECT_EQ(pendingEntries.entries.size(), 0); + auto pendingEntries = performanceEntryReporter_->getEntries(); + EXPECT_EQ(pendingEntries.size(), 0); bool didRunTask2 = false; stubClock_->setTimePoint(100ms); @@ -1265,12 +1263,11 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasks) { EXPECT_EQ(didRunTask2, 1); EXPECT_EQ(stubQueue_->size(), 0); - pendingEntries = performanceEntryReporter_->popPendingEntries(); - EXPECT_EQ(pendingEntries.entries.size(), 1); - EXPECT_EQ( - pendingEntries.entries[0].entryType, PerformanceEntryType::LONGTASK); - EXPECT_EQ(pendingEntries.entries[0].startTime, 100); - EXPECT_EQ(pendingEntries.entries[0].duration, 50); + pendingEntries = performanceEntryReporter_->getEntries(); + EXPECT_EQ(pendingEntries.size(), 1); + EXPECT_EQ(pendingEntries[0].entryType, PerformanceEntryType::LONGTASK); + EXPECT_EQ(pendingEntries[0].startTime, 100); + EXPECT_EQ(pendingEntries[0].duration, 50); } TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { @@ -1311,8 +1308,8 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { EXPECT_EQ(didRunTask1, 1); EXPECT_EQ(stubQueue_->size(), 0); - auto pendingEntries = performanceEntryReporter_->popPendingEntries(); - EXPECT_EQ(pendingEntries.entries.size(), 0); + auto pendingEntries = performanceEntryReporter_->getEntries(); + EXPECT_EQ(pendingEntries.size(), 0); bool didRunTask2 = false; stubClock_->setTimePoint(100ms); @@ -1346,12 +1343,12 @@ TEST_P(RuntimeSchedulerTest, reportsLongTasksWithYielding) { EXPECT_EQ(didRunTask2, 1); EXPECT_EQ(stubQueue_->size(), 0); - pendingEntries = performanceEntryReporter_->popPendingEntries(); - EXPECT_EQ(pendingEntries.entries.size(), 1); + pendingEntries = performanceEntryReporter_->getEntries(); + EXPECT_EQ(pendingEntries.size(), 1); EXPECT_EQ( - pendingEntries.entries[0].entryType, PerformanceEntryType::LONGTASK); - EXPECT_EQ(pendingEntries.entries[0].startTime, 100); - EXPECT_EQ(pendingEntries.entries[0].duration, 120); + pendingEntries[0].entryType, PerformanceEntryType::LONGTASK); + EXPECT_EQ(pendingEntries[0].startTime, 100); + EXPECT_EQ(pendingEntries[0].duration, 120); } INSTANTIATE_TEST_SUITE_P( From e0aa0a2d5e71a14f20e091f6d306b68bd5cef5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 15:25:16 +0200 Subject: [PATCH 31/60] chore: cleanup PerformanceObserver and PerformanceObserverRegistry dependency --- .../timeline/PerformanceEntryReporter.h | 2 +- .../timeline/PerformanceObserver.cpp | 7 ---- .../timeline/PerformanceObserver.h | 19 ++++++--- .../timeline/PerformanceObserverRegistry.cpp | 18 +++++++-- .../timeline/PerformanceObserverRegistry.h | 39 ++++++++++++++++--- 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index e36eadf690e623..95a6273784c438 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -115,7 +115,7 @@ class PerformanceEntryReporter { } private: - std::unique_ptr observerRegistry_; + PerformanceObserverRegistry::Ptr observerRegistry_; mutable std::mutex entriesMutex_; PerformanceEntryCircularBuffer eventBuffer_{EVENT_BUFFER_SIZE}; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index b2ad23939a9701..f490f842df779d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -6,17 +6,10 @@ */ #include "PerformanceObserver.h" -#include "PerformanceObserverRegistry.h" #include "PerformanceEntryReporter.h" namespace facebook::react { -PerformanceObserver::~PerformanceObserver() { - if (auto registry = registry_.lock()) { - registry->removeObserver(*this); - } -} - void PerformanceObserver::handleEntry(const facebook::react::PerformanceEntry& entry) { // https://w3c.github.io/performance-timeline/#takerecords-method (step 7.1) // https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index cddfd35a7994e8..05d3561252599a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "PerformanceEntryLinearBuffer.h" @@ -18,8 +19,6 @@ namespace facebook::react { using PerformanceObserverEntryTypeFilter = std::unordered_set; using PerformanceObserverCallback = std::function; -class PerformanceObserverRegistry; - /** * Represents subset of spec's `PerformanceObserverInit` that is allowed for multiple types. * @@ -45,14 +44,23 @@ struct PerformanceObserverObserveSingleOptions: public PerformanceObserverObserv * * Entries are pushed to the observer by the `PerformanceEntryReporter` class, * through the `PerformanceObserverRegistry` class which acts as a central hub. + * + * To create new performance observers, you should use + * `PerformanceObserverRegistry::createObserver` which creates the observer, + * registers it immediately and unregisters it when it goes out of scope. + * + * You can still manually create instance of `PerformanceObserver`, but then + * you need to manually register and unregister it from registry. */ class PerformanceObserver: public jsi::NativeState { public: - explicit PerformanceObserver( - PerformanceObserverCallback&& callback) + using Ptr = std::shared_ptr; + using WeakPtr = std::weak_ptr; + + explicit PerformanceObserver(PerformanceObserverCallback&& callback) : callback_(std::move(callback)) {} - virtual ~PerformanceObserver(); + virtual ~PerformanceObserver() {} /** * Append entry to the buffer if this observer should handle this entry. @@ -92,7 +100,6 @@ class PerformanceObserver: public jsi::NativeState { private: void scheduleFlushBuffer(); - std::weak_ptr registry_; PerformanceObserverCallback callback_; PerformanceObserverEntryTypeFilter observedTypes_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 0b9c0699295c1f..213eb333f55175 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -9,7 +9,19 @@ namespace facebook::react { -void PerformanceObserverRegistry::addObserver(const std::weak_ptr& observer) { +PerformanceObserver::Ptr PerformanceObserverRegistry::createObserver(PerformanceObserverCallback&& callback) { + // We allocate observer manually, and use `shared_ptr` deleter + // functionality to remove observer from registry + auto obj = new PerformanceObserver(std::move(callback)); + auto deleter = [&](PerformanceObserver* observer) -> void { + this->removeObserver(*obj); + delete observer; + }; + PerformanceObserver::Ptr ptr {obj, deleter}; + return ptr; +} + +void PerformanceObserverRegistry::addObserver(const PerformanceObserver::WeakPtr& observer) { std::lock_guard guard{observersMutex_}; observers_.insert(observer); } @@ -21,12 +33,12 @@ void PerformanceObserverRegistry::removeObserver(const PerformanceObserver& obse }); } -void PerformanceObserverRegistry::removeObserver(const std::shared_ptr& observer) { +void PerformanceObserverRegistry::removeObserver(const PerformanceObserver::Ptr& observer) { std::lock_guard guard{observersMutex_}; observers_.erase(observer); } -void PerformanceObserverRegistry::queuePerformanceEntry(const facebook::react::PerformanceEntry& entry) { +void PerformanceObserverRegistry::queuePerformanceEntry(const PerformanceEntry& entry) { std::lock_guard lock(observersMutex_); // filter dead observers diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index d3ef35fa8313e4..fc00cc906756cf 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -14,19 +14,48 @@ namespace facebook::react { +/** + * PerformanceObserverRegistry acts as a container for known performance + * observer instances. + * + * You can queue performance entries through this registry, which then delegates + * the entry to all registered observers. + */ class PerformanceObserverRegistry { public: - PerformanceObserverRegistry() = default; + using Ptr = std::unique_ptr; + using WeakPtr = std::weak_ptr; - void addObserver(const std::weak_ptr& observer); - void removeObserver(const std::shared_ptr& observer); - void removeObserver(const PerformanceObserver& observer); + PerformanceObserverRegistry() = default; + /** + * Creates Performance Observer instance and registers it in the registry. + * + * Observer is automatically removed from the registry when it goes out of scope. + * You can manually remove it from the registry earlier by calling `removeObserver`. + */ + PerformanceObserver::Ptr createObserver(PerformanceObserverCallback&& callback); + + /** + * Removes observer from the registry. + * + * It is called automatically for observers created by `createObserver` method. + */ + void removeObserver(const PerformanceObserver::Ptr& observer); + + /** + * Delegates specified performance `entry` to all registered observers + * in this registry. + */ void queuePerformanceEntry(const PerformanceEntry& entry); + private: + void addObserver(const PerformanceObserver::WeakPtr& observer); + void removeObserver(const PerformanceObserver& observer); + private: mutable std::mutex observersMutex_; - std::set, std::owner_less>> observers_; + std::set> observers_; }; } // namespace facebook::react From a2f54b10522856c2611871524c7dde1628e00a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 16:46:18 +0200 Subject: [PATCH 32/60] chore: cleanup PerformanceObserver and PerformanceObserverRegistry dependency --- .../webperformance/NativePerformance.cpp | 6 ++++++ .../NativePerformanceObserver.cpp | 19 +++++++++---------- .../NativePerformanceObserver.h | 2 +- .../timeline/PerformanceObserver.cpp | 10 +++++++--- .../timeline/PerformanceObserver.h | 14 +++++++++++--- 5 files changed, 34 insertions(+), 17 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 9698781bab3414..1e7e62d35a1736 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -15,7 +15,10 @@ #include #include #include +#if __has_include() #include +#define HAS_FUSEBOX +#endif #include "NativePerformance.h" #ifdef RN_DISABLE_OSS_PLUGIN_HEADER @@ -109,8 +112,11 @@ void NativePerformance::measure( std::optional endMark) { auto [trackName, eventName] = parseTrackName(name); +#ifdef HAS_FUSEBOX FuseboxTracer::getFuseboxTracer().addEvent( eventName, (uint64_t)startTime, (uint64_t)endTime, trackName); +#endif + PerformanceEntryReporter::getInstance()->measure( eventName, startTime, endTime, duration, startMark, endMark); diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 288ba02afd237b..40c4bb9732c0f0 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -35,11 +35,16 @@ jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, NativePe // 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)]() -> void { - callback.callWithPriority(SchedulerPriority::IdlePriority); + PerformanceObserverCallback cb = [this, callback = std::move(callback)](PerformanceObserver& currentObserver) { + jsInvoker_->invokeAsync(SchedulerPriority::IdlePriority, [currentObserver, callback] (jsi::Runtime& /*rt*/) mutable { + callback.call(); + currentObserver.flush(); + }); }; - auto observer = std::make_shared(std::move(cb)); + auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); + auto observer = registry.createObserver(std::move(cb)); + jsi::Object observerObj {rt}; observerObj.setNativeState(rt, observer); return observerObj; @@ -79,15 +84,9 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb else { // single auto buffered = options.buffered.value_or(false); if (options.type.has_value()) { - observer->observe(static_cast(options.type.value()), { - .buffered = buffered, - .durationThreshold = durationThreshold - }); + observer->observe(static_cast( options.type.value()), { .buffered = buffered, .durationThreshold = durationThreshold }); } } - - auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); - registry.addObserver(observer); } void NativePerformanceObserver::disconnect(jsi::Runtime& rt, jsi::Object observerObj) { diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 8ad0cfb32597ba..21374c1b680f47 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -22,7 +22,7 @@ namespace facebook::react { -using NativePerformanceObserverCallback = AsyncCallback&&, size_t>; +using NativePerformanceObserverCallback = AsyncCallback<>; using NativePerformanceObserverObserveOptions = NativePerformanceObserverPerformanceObserverInit< // entryTypes std::optional>, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index f490f842df779d..cef009644c19c7 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -10,7 +10,7 @@ namespace facebook::react { -void PerformanceObserver::handleEntry(const facebook::react::PerformanceEntry& entry) { +void PerformanceObserver::handleEntry(const PerformanceEntry& entry) { // https://w3c.github.io/performance-timeline/#takerecords-method (step 7.1) // https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming if (entry.entryType == PerformanceEntryType::EVENT) { @@ -53,7 +53,7 @@ void PerformanceObserver::observe(std::unordered_set types durationThreshold_ = options.durationThreshold; } -double PerformanceObserver::getDroppedEntriesCount() { +double PerformanceObserver::getDroppedEntriesCount() noexcept { double droppedEntriesCount = 0; if (requiresDroppedEntries_) { @@ -69,6 +69,10 @@ double PerformanceObserver::getDroppedEntriesCount() { return droppedEntriesCount; } +void PerformanceObserver::flush() noexcept { + didScheduleFlushBuffer = false; +} + void PerformanceObserver::scheduleFlushBuffer() { if (!callback_) { return; @@ -77,7 +81,7 @@ void PerformanceObserver::scheduleFlushBuffer() { if (!didScheduleFlushBuffer) { didScheduleFlushBuffer = true; - callback_(); + callback_(*this); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 05d3561252599a..a989c5a8a5cc29 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -16,8 +16,10 @@ namespace facebook::react { +class PerformanceObserver; + using PerformanceObserverEntryTypeFilter = std::unordered_set; -using PerformanceObserverCallback = std::function; +using PerformanceObserverCallback = std::function; /** * Represents subset of spec's `PerformanceObserverInit` that is allowed for multiple types. @@ -33,8 +35,9 @@ struct PerformanceObserverObserveMultipleOptions { * * https://w3c.github.io/performance-timeline/#performanceobserverinit-dictionary */ -struct PerformanceObserverObserveSingleOptions: public PerformanceObserverObserveMultipleOptions { +struct PerformanceObserverObserveSingleOptions { bool buffered = false; + double durationThreshold = 0.0; }; /** @@ -95,7 +98,12 @@ class PerformanceObserver: public jsi::NativeState { * Internal function called by JS bridge to get number of dropped entries count * counted at call time. */ - double getDroppedEntriesCount(); + double getDroppedEntriesCount() noexcept; + + /** + * Called when the callback was dispatched + */ + void flush() noexcept; private: void scheduleFlushBuffer(); From 1452d4429b607cd84f2b19065386aa15aa56a640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 16:57:07 +0200 Subject: [PATCH 33/60] chore: style fixes --- .../nativemodule/webperformance/NativePerformanceObserver.h | 2 +- .../react/performance/timeline/PerformanceObserver.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 21374c1b680f47..7c45950cb29309 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -63,7 +63,7 @@ struct Bridging class NativePerformanceObserver : public NativePerformanceObserverCxxSpec { public: - NativePerformanceObserver(std::shared_ptr jsInvoker); + explicit NativePerformanceObserver(std::shared_ptr jsInvoker); jsi::Object createObserver(jsi::Runtime& rt, NativePerformanceObserverCallback callback); double getDroppedEntriesCount(jsi::Runtime& rt, jsi::Object observerObj); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index a989c5a8a5cc29..db54e60c01e9e9 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -63,7 +63,7 @@ class PerformanceObserver: public jsi::NativeState { explicit PerformanceObserver(PerformanceObserverCallback&& callback) : callback_(std::move(callback)) {} - virtual ~PerformanceObserver() {} + ~PerformanceObserver() = default; /** * Append entry to the buffer if this observer should handle this entry. From 11ebbefa10e11791458449c4ed145786bd4c7dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 18:03:54 +0200 Subject: [PATCH 34/60] chore: narrow down CircularBuffer API --- .../performance/timeline/BoundedConsumableBuffer.h | 2 +- .../timeline/PerformanceEntryCircularBuffer.cpp | 14 +++++++------- .../timeline/PerformanceEntryCircularBuffer.h | 10 ++++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h index b4de6be4c105e0..f8452934db58f7 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h @@ -61,7 +61,7 @@ class BoundedConsumableBuffer { PushStatus add(const T&& el) { if (entries_.size() < maxSize_) { // Haven't reached max buffer size yet, just add and grow the buffer - entries_.push_back(el); + entries_.emplace_back(el); cursorEnd_++; numToConsume_++; return PushStatus::OK; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index c2d9d0a3133d31..140ce9be3f1d7a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -10,32 +10,32 @@ namespace facebook::react { void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { - auto result = entries.add(std::move(entry)); + auto result = entries_.add(std::move(entry)); if (result == BoundedConsumableBuffer::PushStatus::DROP) { - droppedEntriesCount += 1; + droppedEntriesCount_ += 1; } } void PerformanceEntryCircularBuffer::consume(std::vector& target) { - entries.consume(target); + entries_.consume(target); } size_t PerformanceEntryCircularBuffer::pendingMessagesCount() const { - return entries.getNumToConsume(); + return entries_.getNumToConsume(); } void PerformanceEntryCircularBuffer::getEntries(std::optional name, std::vector& target) const { - entries.getEntries( + entries_.getEntries( target, [&](const PerformanceEntry& e) { return e.name == name; }); } void PerformanceEntryCircularBuffer::clear() { - entries.clear(); + entries_.clear(); } void PerformanceEntryCircularBuffer::clear(std::string_view name) { - entries.clear([&](const PerformanceEntry& e) { return e.name == name; }); + entries_.clear([&](const PerformanceEntry& e) { return e.name == name; }); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index 63d04816331133..5de0a379557054 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -12,10 +12,9 @@ namespace facebook::react { -struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { - BoundedConsumableBuffer entries; - - explicit PerformanceEntryCircularBuffer(size_t size) : entries(size) {} +class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { +public: + explicit PerformanceEntryCircularBuffer(size_t size) : entries_(size) {} ~PerformanceEntryCircularBuffer() override = default; void add(const PerformanceEntry& entry) override; @@ -29,6 +28,9 @@ struct PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void clear() override; void clear(std::string_view name) override; + +private: + BoundedConsumableBuffer entries_; }; } // namespace facebook::react From 9a59aece2b1a1f4a56f8ca1a538ce9a958ce9cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 18:25:25 +0200 Subject: [PATCH 35/60] chore: remove LinearBuffer and `pendingMessagesCount` --- .../timeline/PerformanceEntryBuffer.h | 1 - .../PerformanceEntryCircularBuffer.cpp | 6 +- .../timeline/PerformanceEntryCircularBuffer.h | 2 - .../timeline/PerformanceEntryKeyedBuffer.cpp | 6 +- .../timeline/PerformanceEntryKeyedBuffer.h | 1 - .../timeline/PerformanceEntryLinearBuffer.cpp | 65 ------------------- .../timeline/PerformanceEntryLinearBuffer.h | 34 ---------- .../timeline/PerformanceObserver.h | 2 +- 8 files changed, 3 insertions(+), 114 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp delete mode 100644 packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index bf98f03f3bcff5..abdacb8a2a89be 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -29,7 +29,6 @@ class PerformanceEntryBuffer { virtual void add(const PerformanceEntry& entry) = 0; virtual void consume(std::vector& target) = 0; - virtual size_t pendingMessagesCount() const = 0; virtual void getEntries( std::optional name, std::vector& target) const = 0; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index 140ce9be3f1d7a..0e60efc9af1b79 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -12,7 +12,7 @@ namespace facebook::react { void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { auto result = entries_.add(std::move(entry)); if (result == BoundedConsumableBuffer::PushStatus::DROP) { - droppedEntriesCount_ += 1; + droppedEntriesCount += 1; } } @@ -20,10 +20,6 @@ void PerformanceEntryCircularBuffer::consume(std::vector& targ entries_.consume(target); } -size_t PerformanceEntryCircularBuffer::pendingMessagesCount() const { - return entries_.getNumToConsume(); -} - void PerformanceEntryCircularBuffer::getEntries(std::optional name, std::vector& target) const { entries_.getEntries( target, [&](const PerformanceEntry& e) { return e.name == name; }); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index 5de0a379557054..a4cbb5fcb767e6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -20,8 +20,6 @@ class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; void consume(std::vector& target) override; - size_t pendingMessagesCount() const override; - void getEntries( std::optional name, std::vector& target) const override; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index 2e6f170f32df29..1f2f5f21f849c6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -27,10 +27,6 @@ void PerformanceEntryKeyedBuffer::consume(std::vector& target) } } -size_t PerformanceEntryKeyedBuffer::pendingMessagesCount() const { - return totalToConsume_; -} - void PerformanceEntryKeyedBuffer::getEntries(std::optional name, std::vector& target) const { if (name.has_value()) { std::string nameStr{name.value()}; @@ -92,4 +88,4 @@ void PerformanceEntryKeyedBuffer::getEntries(std::vector& targ target.insert(target.end(), allEntries.begin(), allEntries.end()); } -} // namespace facebook::react \ No newline at end of file +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index d46787ba319898..8195ae832597fb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -23,7 +23,6 @@ class PerformanceEntryKeyedBuffer: public PerformanceEntryBuffer { * Retrieves buffer entries, whether consumed or not, with predicate */ void consume(std::vector& target) override; - size_t pendingMessagesCount() const override; void getEntries( std::optional name, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp deleted file mode 100644 index 3f04377bef0e9c..00000000000000 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.cpp +++ /dev/null @@ -1,65 +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 "PerformanceEntryLinearBuffer.h" -#include - -namespace facebook::react { - -void PerformanceEntryLinearBuffer::add(const PerformanceEntry& entry) { - entries_.push_back(entry); -} - -void PerformanceEntryLinearBuffer::consume(std::vector& target) { - target.reserve(entries_.size()); -} - -std::vector PerformanceEntryLinearBuffer::consume() { - std::vector elements = entries_; - entries_.clear(); - return elements; -} - -size_t PerformanceEntryLinearBuffer::pendingMessagesCount() const { - return entries_.size(); -} - -void PerformanceEntryLinearBuffer::getEntries( - std::optional name, - std::vector& target) const { - if (name.has_value()) { - std::copy_if( - entries_.begin(), - entries_.end(), - target.begin(), - [&name](const PerformanceEntry& entry) { - return entry.name == name.value(); - }); - } - else { - std::copy(entries_.begin(), entries_.end(), target.begin()); - } -} - -void PerformanceEntryLinearBuffer::clear() { - entries_.clear(); -} - -void PerformanceEntryLinearBuffer::clear(std::string_view name) { - entries_.erase( - std::remove_if( - entries_.begin(), - entries_.end(), - [&name](const PerformanceEntry& entry) { return entry.name == name; }), - entries_.end()); -} - -std::vector& PerformanceEntryLinearBuffer::getEntries() { - return entries_; -} - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h deleted file mode 100644 index 8d800e6f66ae25..00000000000000 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryLinearBuffer.h +++ /dev/null @@ -1,34 +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 - -#include "PerformanceEntryBuffer.h" - -namespace facebook::react { - -class PerformanceEntryLinearBuffer: public PerformanceEntryBuffer { - public: - PerformanceEntryLinearBuffer() = default; - - void add(const PerformanceEntry& entry) override; - void consume(std::vector& target) override; - [[nodiscard]] std::vector consume(); - size_t pendingMessagesCount() const override; - void getEntries( - std::optional name, - std::vector& target) const override; - void clear() override; - void clear(std::string_view name) override; - - std::vector& getEntries(); - - private: - std::vector entries_; -}; - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index db54e60c01e9e9..9f0d0b7330cb3c 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -12,7 +12,7 @@ #include #include #include -#include "PerformanceEntryLinearBuffer.h" +#include "PerformanceEntryBuffer.h" namespace facebook::react { From a1f7bb6dadd422fac68bffbe8744ce2666ced9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 18:27:39 +0200 Subject: [PATCH 36/60] chore: fix getEventCounts position --- .../webapis/performance/specs/NativePerformanceObserver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index 2fde99092b2b54..c1a9cb8411d620 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -42,6 +42,7 @@ export type PerformanceObserverInit = { }; export interface Spec extends TurboModule { + +getEventCounts: () => $ReadOnlyArray<[string, number]>; +createObserver: (callback: NativeBatchedObserverCallback) => OpaqueNativeObserverHandle; +getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle) => number; @@ -58,8 +59,7 @@ export interface Spec extends TurboModule { entryName?: string, ) => $ReadOnlyArray; +getSupportedPerformanceEntryTypes: () => $ReadOnlyArray; - - +getEventCounts: () => $ReadOnlyArray<[string, number]>; + } export default (TurboModuleRegistry.get( From 187be80755120ad1ca65c3653daa2b2b0f43d159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 18:30:25 +0200 Subject: [PATCH 37/60] chore: fix lock_guard initialization style --- .../performance/timeline/PerformanceObserverRegistry.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 213eb333f55175..93f48ca7447934 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -22,19 +22,19 @@ PerformanceObserver::Ptr PerformanceObserverRegistry::createObserver(Performance } void PerformanceObserverRegistry::addObserver(const PerformanceObserver::WeakPtr& observer) { - std::lock_guard guard{observersMutex_}; + std::lock_guard guard(observersMutex_); observers_.insert(observer); } void PerformanceObserverRegistry::removeObserver(const PerformanceObserver& observer) { - std::lock_guard guard{observersMutex_}; + std::lock_guard guard(observersMutex_); erase_if(observers_, [&](auto e) -> bool { return !e.expired() && *e.lock() == observer; }); } void PerformanceObserverRegistry::removeObserver(const PerformanceObserver::Ptr& observer) { - std::lock_guard guard{observersMutex_}; + std::lock_guard guard(observersMutex_); observers_.erase(observer); } From a2bf9cf202a7e159e815447c4cd320479a6d11bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Tue, 17 Sep 2024 18:31:42 +0200 Subject: [PATCH 38/60] style: rename `entriesMutex_` to `buffersMutex_` --- .../timeline/PerformanceEntryReporter.cpp | 18 +++++++++--------- .../timeline/PerformanceEntryReporter.h | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index abb57eb2745db0..030fc22a6fe122 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -34,7 +34,7 @@ void PerformanceEntryReporter::mark( }; { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); markBuffer_.add(entry); } @@ -46,7 +46,7 @@ void PerformanceEntryReporter::clearEntries( std::string_view entryName) { // Clear all entry types if (!entryType) { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); markBuffer_.clear(); measureBuffer_.clear(); eventBuffer_.clear(); @@ -54,10 +54,10 @@ void PerformanceEntryReporter::clearEntries( } else { auto& buffer = getBufferRef(*entryType); if (!entryName.empty()) { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); buffer.clear(entryName); } else { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); buffer.clear(); } } @@ -67,7 +67,7 @@ void PerformanceEntryReporter::getEntries( PerformanceEntryType entryType, std::string_view entryName, std::vector& res) const { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); auto& buffer = getBuffer(entryType); if (entryName.empty()) { @@ -120,7 +120,7 @@ void PerformanceEntryReporter::measure( }; { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); measureBuffer_.add(entry); } @@ -129,7 +129,7 @@ void PerformanceEntryReporter::measure( DOMHighResTimeStamp PerformanceEntryReporter::getMarkTime( const std::string& markName) const { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); if (auto it = markBuffer_.find(markName); it) { return it->startTime; @@ -158,7 +158,7 @@ void PerformanceEntryReporter::logEventEntry( }; { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); if (entry.duration < eventBuffer_.durationThreshold) { // The entries duration is lower than the desired reporting threshold, skip @@ -182,7 +182,7 @@ void PerformanceEntryReporter::logLongTaskEntry( }; { - std::lock_guard lock(entriesMutex_); + std::lock_guard lock(buffersMutex_); longTaskBuffer_.add(entry); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 95a6273784c438..58ae2d87eedd57 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -117,7 +117,7 @@ class PerformanceEntryReporter { private: PerformanceObserverRegistry::Ptr observerRegistry_; - mutable std::mutex entriesMutex_; + mutable std::mutex buffersMutex_; PerformanceEntryCircularBuffer eventBuffer_{EVENT_BUFFER_SIZE}; PerformanceEntryCircularBuffer longTaskBuffer_{LONG_TASK_BUFFER_SIZE}; PerformanceEntryKeyedBuffer markBuffer_; From 1cc39286d3effbf181e6155ad8a24a499bcf2f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 12:05:25 +0200 Subject: [PATCH 39/60] fix: remove concept of consuming, renamed `BoundedConsumableBuffer` to `CircularBuffer`, cleaned up buffer API --- .../timeline/BoundedConsumableBuffer.h | 248 ------------------ .../performance/timeline/CircularBuffer.h | 147 +++++++++++ .../timeline/PerformanceEntryBuffer.h | 1 - .../PerformanceEntryCircularBuffer.cpp | 14 +- .../timeline/PerformanceEntryCircularBuffer.h | 11 +- .../timeline/PerformanceEntryKeyedBuffer.cpp | 15 -- .../timeline/PerformanceEntryKeyedBuffer.h | 7 - .../tests/BoundedConsumableBufferTest.cpp | 219 ---------------- .../timeline/tests/CircularBufferTest.cpp | 129 +++++++++ 9 files changed, 285 insertions(+), 506 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h delete mode 100644 packages/react-native/ReactCommon/react/performance/timeline/tests/BoundedConsumableBufferTest.cpp create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp diff --git a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h deleted file mode 100644 index f8452934db58f7..00000000000000 --- a/packages/react-native/ReactCommon/react/performance/timeline/BoundedConsumableBuffer.h +++ /dev/null @@ -1,248 +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 - -#include -#include -#include -#include "PerformanceEntry.h" - -namespace facebook::react { - -constexpr size_t DEFAULT_MAX_SIZE = 1024; - -/** - * A container for storing entries of type T, with the following properties: - * - It can only grow up to a specified max size - * - It's a circular buffer (the oldest elements are dropped if reached max - * size and adding a new element) - * - The entries can be "consumed" (once), which from the point of view of - * the consumer effectively clears the buffer - * - Even after the entries are consumed, all of the non-overwritten entries - * can still be independently retrieved an arbitrary amount of times - * - * Note that the space for maxSize elements is reserved on construction. This - * ensures that pointers to elements remain stable across add() operations. - */ -template -class BoundedConsumableBuffer { - public: - /** - * Status of the add/push operation for the `BoundedConsumableBuffer` - * container - */ - enum class PushStatus { - // There was free space in the buffer, element was successfully pushed: - OK = 0, - - // Element was pushed, but had to overwrite some already consumed elements: - OVERWRITE = 1, - - // Element wasn't pushed, as buffer size limit has been reached and it's - // not possible to overwrite already consumed elements anymore: - DROP = 2, - }; - - explicit BoundedConsumableBuffer(size_t maxSize = DEFAULT_MAX_SIZE) - : maxSize_(maxSize) { - entries_.reserve(maxSize_); - } - - /** - * Adds (pushes) element into the buffer. Returns the result/status of the - * operation, which will depend on whether the buffer reached the max allowed - * size and how many are there unconsumed elements. - */ - PushStatus add(const T&& el) { - if (entries_.size() < maxSize_) { - // Haven't reached max buffer size yet, just add and grow the buffer - entries_.emplace_back(el); - cursorEnd_++; - numToConsume_++; - return PushStatus::OK; - } else if (numToConsume_ == maxSize_) { - // Drop the oldest (yet unconsumed) element in the buffer - entries_[position_] = el; - cursorEnd_ = (cursorEnd_ + 1) % maxSize_; - position_ = (position_ + 1) % maxSize_; - cursorStart_ = position_; - return PushStatus::DROP; - } else { - // Overwrite the oldest (but already consumed) element in the buffer - entries_[position_] = el; - position_ = (position_ + 1) % entries_.size(); - cursorEnd_ = position_; - numToConsume_++; - return PushStatus::OVERWRITE; - } - } - - /** - * Returns pointer to next entry which would be overwritten or dropped if - * added a new element. Null if no entry will be dropped. - */ - const T* getNextOverwriteCandidate() const { - if (entries_.size() < maxSize_) { - return nullptr; - } else { - return &entries_[position_]; - } - } - - T& operator[](size_t idx) { - return entries_[(position_ + idx) % entries_.size()]; - } - - /** - * Returns reference to the last unconsumed element - */ - T& back() { - return entries_[(cursorEnd_ - 1 + entries_.size()) % entries_.size()]; - } - - size_t size() const { - return entries_.size(); - } - - size_t getNumToConsume() const { - return numToConsume_; - } - - void clear() { - entries_.clear(); - position_ = 0; - cursorStart_ = 0; - cursorEnd_ = 0; - numToConsume_ = 0; - } - - /** - * Clears buffer entries by predicate - */ - void clear(std::function predicate) { - std::vector entries; - int numToConsume = 0; - - entries.reserve(maxSize_); - for (size_t i = 0; i < entries_.size(); i++) { - T& el = entries_[(i + position_) % entries_.size()]; - if (predicate(el)) { - continue; - } - - entries.push_back(std::move(el)); - if (i + numToConsume_ >= entries_.size()) { // el is unconsumed - numToConsume++; - } - } - - numToConsume_ = numToConsume; - cursorEnd_ = entries.size() % maxSize_; - cursorStart_ = (cursorEnd_ - numToConsume_ + maxSize_) % maxSize_; - position_ = 0; - - entries.swap(entries_); - } - - /** - * Retrieves buffer entries, whether consumed or not - */ - std::vector getEntries() const { - std::vector res; - getEntries(res); - return res; - } - - /** - * Retrieves buffer entries, whether consumed or not, with predicate - */ - std::vector getEntries(std::function predicate) const { - std::vector res; - getEntries(res, predicate); - return res; - } - - void getEntries(std::vector& res) const { - const size_t oldSize = res.size(); - res.resize(oldSize + entries_.size()); - std::copy( - entries_.begin() + position_, entries_.end(), res.begin() + oldSize); - std::copy( - entries_.begin(), - entries_.begin() + position_, - res.begin() + oldSize + entries_.size() - position_); - } - - void getEntries(std::vector& res, std::function predicate) - const { - for (size_t i = 0; i < entries_.size(); i++) { - const T& el = entries_[(i + position_) % entries_.size()]; - if (predicate(el)) { - res.push_back(el); - } - } - } - - /** - * "Consumes" all the currently unconsumed entries in the buffer and returns - * these entries. Note that even if the buffer may not have unconsumed - * elements currently, it's still possible to retrieve all buffer elements - * via `getEntries`. - */ - std::vector consume() { - std::vector res; - consume(res); - return res; - } - - void consume(std::vector& res) { - if (numToConsume_ == 0) { - return; - } - - const size_t resStart = res.size(); - res.resize(res.size() + numToConsume_); - if (cursorEnd_ > cursorStart_) { - std::copy( - entries_.begin() + cursorStart_, - entries_.begin() + cursorEnd_, - res.begin() + resStart); - } else { - std::copy( - entries_.begin() + cursorStart_, - entries_.end(), - res.begin() + resStart); - std::copy( - entries_.begin(), - entries_.begin() + cursorEnd_, - res.begin() + resStart + static_cast(entries_.size()) - - cursorStart_); - } - - cursorStart_ = cursorEnd_; - numToConsume_ = 0; - } - - private: - std::vector entries_; - - const size_t maxSize_; - - // Current starting position in the circular buffer: - size_t position_{0}; - - // Current "cursor" - positions of the first and after last unconsumed - // element, relative to the starting position: - size_t cursorStart_{0}; - size_t cursorEnd_{0}; - - // Number of currently unconsumed elements: - size_t numToConsume_{0}; -}; - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h new file mode 100644 index 00000000000000..d25edbb729e970 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include "PerformanceEntry.h" + +namespace facebook::react { + +constexpr size_t DEFAULT_MAX_SIZE = 1024; + +/** + * A container for storing entries of type T, with the following properties: + * - It can only grow up to a specified max size + * - It's a circular buffer (the oldest elements are dropped if reached max + * size and adding a new element) + * + * Note that the space for maxSize elements is reserved on construction. This + * ensures that pointers to elements remain stable across add() operations. + */ +template +class CircularBuffer { +public: + explicit CircularBuffer(size_t maxSize = DEFAULT_MAX_SIZE) : maxSize_(maxSize) { + entries_.reserve(maxSize_); + } + + /** + * Adds (pushes) element into the buffer. + * + * Returns the result of the operation, which will depend on whether the buffer + * reached the max allowed size, in which case `true` is returned. If no + * items were overridden `false` is returned. + */ + bool add(const T&& el) { + if (entries_.size() < maxSize_) { + // Haven't reached max buffer size yet, just add and grow the buffer + entries_.emplace_back(el); + return false; + } else { + // Overwrite the oldest (but already consumed) element in the buffer + entries_[position_] = el; + position_ = (position_ + 1) % entries_.size(); + return true; + } + } + + /** + * Returns pointer to next entry which would be overwritten or dropped if + * added a new element. Null if no entry will be dropped. + */ + const T* getNextOverwriteCandidate() const { + if (entries_.size() < maxSize_) { + return nullptr; + } else { + return &entries_[position_]; + } + } + + T& operator[](size_t idx) { + return entries_[(position_ + idx) % entries_.size()]; + } + + size_t size() const { + return entries_.size(); + } + + void clear() { + entries_.clear(); + position_ = 0; + } + + /** + * Clears buffer entries by predicate + */ + void clear(std::function predicate) { + std::vector entries; + + entries.reserve(maxSize_); + for (size_t i = 0; i < entries_.size(); i++) { + T& el = entries_[(i + position_) % entries_.size()]; + if (predicate(el)) { + continue; + } + + entries.push_back(std::move(el)); + } + + position_ = 0; + entries.swap(entries_); + } + + /** + * Retrieves buffer entries, whether consumed or not + */ + std::vector getEntries() const { + std::vector res; + getEntries(res); + return res; + } + + /** + * Retrieves buffer entries, whether consumed or not, with predicate + */ + std::vector getEntries(std::function predicate) const { + std::vector res; + getEntries(res, predicate); + return res; + } + + void getEntries(std::vector& res) const { + const size_t oldSize = res.size(); + res.resize(oldSize + entries_.size()); + std::copy( + entries_.begin() + position_, entries_.end(), res.begin() + oldSize); + std::copy( + entries_.begin(), + entries_.begin() + position_, + res.begin() + oldSize + entries_.size() - position_); + } + + void getEntries(std::vector& res, std::function predicate) + const { + for (size_t i = 0; i < entries_.size(); i++) { + const T& el = entries_[(i + position_) % entries_.size()]; + if (predicate(el)) { + res.push_back(el); + } + } + } + + private: + std::vector entries_; + const size_t maxSize_; + + // Current starting position in the circular buffer: + size_t position_{0}; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index abdacb8a2a89be..03e5851a2092e1 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -28,7 +28,6 @@ class PerformanceEntryBuffer { virtual ~PerformanceEntryBuffer() = default; virtual void add(const PerformanceEntry& entry) = 0; - virtual void consume(std::vector& target) = 0; virtual void getEntries( std::optional name, std::vector& target) const = 0; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index 0e60efc9af1b79..5f71f175048eb1 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -10,28 +10,22 @@ namespace facebook::react { void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { - auto result = entries_.add(std::move(entry)); - if (result == BoundedConsumableBuffer::PushStatus::DROP) { + if (buffer_.add(std::move(entry))) { droppedEntriesCount += 1; } } -void PerformanceEntryCircularBuffer::consume(std::vector& target) { - entries_.consume(target); -} - void PerformanceEntryCircularBuffer::getEntries(std::optional name, std::vector& target) const { - entries_.getEntries( - target, [&](const PerformanceEntry& e) { return e.name == name; }); + buffer_.getEntries(target, [&](const PerformanceEntry& e) { return e.name == name; }); } void PerformanceEntryCircularBuffer::clear() { - entries_.clear(); + buffer_.clear(); } void PerformanceEntryCircularBuffer::clear(std::string_view name) { - entries_.clear([&](const PerformanceEntry& e) { return e.name == name; }); + buffer_.clear([&](const PerformanceEntry& e) { return e.name == name; }); } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index a4cbb5fcb767e6..ea0f441a0ccb70 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -7,18 +7,17 @@ #pragma once +#include "CircularBuffer.h" #include "PerformanceEntryBuffer.h" -#include "BoundedConsumableBuffer.h" namespace facebook::react { class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { -public: - explicit PerformanceEntryCircularBuffer(size_t size) : entries_(size) {} + public: + explicit PerformanceEntryCircularBuffer(size_t size) : buffer_(size) {} ~PerformanceEntryCircularBuffer() override = default; void add(const PerformanceEntry& entry) override; - void consume(std::vector& target) override; void getEntries( std::optional name, @@ -27,8 +26,8 @@ class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void clear() override; void clear(std::string_view name) override; -private: - BoundedConsumableBuffer entries_; + private: + CircularBuffer buffer_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index 1f2f5f21f849c6..8b20149286bf7d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -12,19 +12,8 @@ namespace facebook::react { void PerformanceEntryKeyedBuffer::add(const PerformanceEntry& entry) { auto& list = entryMap_.at(entry.name); - auto& toConsume = toConsumeMap_.at(entry.name); list.push_back(entry); totalEntryCount_ += 1; - totalToConsume_ += 1; - toConsume += 1; -} - -void PerformanceEntryKeyedBuffer::consume(std::vector& target) { - for (const auto& [name, entries] : entryMap_) { - target.insert( - target.end(), entries.end() - toConsumeMap_[name], entries.end()); - toConsumeMap_[name] = 0; - } } void PerformanceEntryKeyedBuffer::getEntries(std::optional name, std::vector& target) const { @@ -41,9 +30,7 @@ void PerformanceEntryKeyedBuffer::getEntries(std::optional nam void PerformanceEntryKeyedBuffer::clear() { entryMap_.clear(); - toConsumeMap_.clear(); totalEntryCount_ = 0; - totalToConsume_ = 0; } void PerformanceEntryKeyedBuffer::clear(std::string_view nameView) { @@ -51,8 +38,6 @@ void PerformanceEntryKeyedBuffer::clear(std::string_view nameView) { if (auto node = entryMap_.find(name); node != entryMap_.end()) { totalEntryCount_ -= node->second.size(); - totalToConsume_ -= node->second.size(); - toConsumeMap_[name] = 0; node->second.clear(); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index 8195ae832597fb..7379860add82b3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -19,11 +19,6 @@ class PerformanceEntryKeyedBuffer: public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; - /** - * Retrieves buffer entries, whether consumed or not, with predicate - */ - void consume(std::vector& target) override; - void getEntries( std::optional name, std::vector& target) const override; @@ -36,10 +31,8 @@ class PerformanceEntryKeyedBuffer: public PerformanceEntryBuffer { private: std::unordered_map> entryMap_{}; - std::unordered_map toConsumeMap_{}; mutable std::optional> allEntriesCache_; size_t totalEntryCount_ = 0; - size_t totalToConsume_ = 0; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/BoundedConsumableBufferTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/BoundedConsumableBufferTest.cpp deleted file mode 100644 index 19ef891e287a2d..00000000000000 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/BoundedConsumableBufferTest.cpp +++ /dev/null @@ -1,219 +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 - -#include - -#include "../BoundedConsumableBuffer.h" - -namespace facebook::react { - -using namespace facebook::react; - -constexpr auto OK = BoundedConsumableBuffer::PushStatus::OK; -constexpr auto DROP = BoundedConsumableBuffer::PushStatus::DROP; -constexpr auto OVERWRITE = BoundedConsumableBuffer::PushStatus::OVERWRITE; - -TEST(BoundedConsumableBuffer, CanAddAndRetrieveElements) { - BoundedConsumableBuffer buffer; - - ASSERT_EQ(OK, buffer.add(1)); - ASSERT_EQ(OK, buffer.add(2)); - - ASSERT_EQ(1, buffer[0]); - ASSERT_EQ(2, buffer[1]); - ASSERT_EQ(2, buffer.size()); - ASSERT_EQ(std::vector({1, 2}), buffer.getEntries()); - - ASSERT_EQ(OK, buffer.add(3)); - ASSERT_EQ(3, buffer.size()); - ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); - - ASSERT_EQ(1, buffer[0]); - ASSERT_EQ(2, buffer[1]); - ASSERT_EQ(3, buffer[2]); -} - -TEST(BoundedConsumableBuffer, CanConsumeElements) { - BoundedConsumableBuffer buffer; - - ASSERT_EQ(OK, buffer.add(1)); - ASSERT_EQ(OK, buffer.add(2)); - - ASSERT_EQ(std::vector({1, 2}), buffer.getEntries()); - - auto elems1 = buffer.consume(); - - ASSERT_EQ(std::vector({1, 2}), elems1); - ASSERT_EQ(std::vector({1, 2}), buffer.getEntries()); - - auto elems2 = buffer.consume(); - ASSERT_TRUE(elems2.empty()); - - ASSERT_EQ(std::vector({1, 2}), buffer.getEntries()); - - ASSERT_EQ(OK, buffer.add(3)); - ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); - auto elems3 = buffer.consume(); - ASSERT_EQ(std::vector({3}), elems3); - - auto elems4 = buffer.consume(); - ASSERT_TRUE(elems4.empty()); - - ASSERT_EQ(OK, buffer.add(4)); - ASSERT_EQ(OK, buffer.add(5)); - - ASSERT_EQ(std::vector({1, 2, 3, 4, 5}), buffer.getEntries()); - auto elems5 = buffer.consume(); - ASSERT_EQ(std::vector({4, 5}), elems5); - - auto elems6 = buffer.consume(); - ASSERT_TRUE(elems6.empty()); -} - -TEST(BoundedConsumableBuffer, WrapsAroundCorrectly) { - BoundedConsumableBuffer buffer(3); - - ASSERT_EQ(OK, buffer.add(1)); - ASSERT_EQ(OK, buffer.add(2)); - - auto elems1 = buffer.consume(); - - ASSERT_EQ(std::vector({1, 2}), buffer.getEntries()); - ASSERT_EQ(std::vector({1, 2}), elems1); - - auto elems2 = buffer.consume(); - ASSERT_TRUE(elems2.empty()); - - ASSERT_EQ(OK, buffer.add(3)); - ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); - auto elems3 = buffer.consume(); - ASSERT_EQ(std::vector({3}), elems3); - - auto elems4 = buffer.consume(); - ASSERT_TRUE(elems4.empty()); - - ASSERT_EQ(OVERWRITE, buffer.add(4)); - ASSERT_EQ(OVERWRITE, buffer.add(5)); - - ASSERT_EQ(std::vector({3, 4, 5}), buffer.getEntries()); - auto elems5 = buffer.consume(); - ASSERT_EQ(std::vector({4, 5}), elems5); - - auto elems6 = buffer.consume(); - ASSERT_TRUE(elems6.empty()); - - ASSERT_EQ(OVERWRITE, buffer.add(6)); - ASSERT_EQ(OVERWRITE, buffer.add(7)); - - ASSERT_EQ(std::vector({5, 6, 7}), buffer.getEntries()); - - auto elems7 = buffer.consume(); - - ASSERT_EQ(std::vector({5, 6, 7}), buffer.getEntries()); - ASSERT_EQ(std::vector({6, 7}), elems7); - - ASSERT_EQ(OVERWRITE, buffer.add(8)); - ASSERT_EQ(OVERWRITE, buffer.add(9)); - ASSERT_EQ(OVERWRITE, buffer.add(10)); - ASSERT_EQ(DROP, buffer.add(11)); - ASSERT_EQ(std::vector({9, 10, 11}), buffer.getEntries()); - - ASSERT_EQ(DROP, buffer.add(12)); - ASSERT_EQ(std::vector({10, 11, 12}), buffer.getEntries()); - - ASSERT_EQ(10, buffer[0]); - ASSERT_EQ(11, buffer[1]); - ASSERT_EQ(12, buffer[2]); - - ASSERT_EQ(DROP, buffer.add(13)); - ASSERT_EQ(std::vector({11, 12, 13}), buffer.getEntries()); - auto elems8 = buffer.consume(); - ASSERT_EQ(std::vector({11, 12, 13}), elems8); - - ASSERT_EQ(11, buffer[0]); - ASSERT_EQ(12, buffer[1]); - ASSERT_EQ(13, buffer[2]); - ASSERT_EQ(11, *buffer.getNextOverwriteCandidate()); - - ASSERT_EQ(OVERWRITE, buffer.add(14)); - ASSERT_EQ(14, buffer.back()); - - ASSERT_EQ(std::vector({12, 13, 14}), buffer.getEntries()); - auto elems9 = buffer.consume(); - ASSERT_EQ(std::vector({14}), elems9); -} - -TEST(BoundedConsumableBuffer, CanClearByPredicate) { - BoundedConsumableBuffer buffer(5); - - buffer.add(1); - buffer.add(0); - buffer.add(2); - buffer.add(0); - - buffer.consume(); - buffer.add(3); - - buffer.add(0); - buffer.add(4); - - buffer.clear([](const int& el) { return el == 0; }); - - ASSERT_EQ(std::vector({2, 3, 4}), buffer.getEntries()); - auto elems = buffer.consume(); - ASSERT_EQ(std::vector({3, 4}), elems); - ASSERT_EQ(std::vector({2, 3, 4}), buffer.getEntries()); -} - -TEST(BoundedConsumableBuffer, CanClearBeforeReachingMaxSize) { - BoundedConsumableBuffer buffer(5); - - buffer.add(1); - buffer.add(2); - buffer.consume(); - buffer.add(3); - - buffer.clear([](const int&) { return false; }); // no-op clear - ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); - - buffer.add(4); - buffer.add(5); - - ASSERT_EQ(std::vector({3, 4, 5}), buffer.consume()); - ASSERT_EQ(std::vector({1, 2, 3, 4, 5}), buffer.getEntries()); -} - -TEST(BoundedConsumableBuffer, CanGetByPredicate) { - BoundedConsumableBuffer buffer(5); - - buffer.add(1); - buffer.add(0); - buffer.add(2); - buffer.add(0); - - buffer.consume(); - buffer.add(3); - - ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries([](const int& el) { - return el != 0; - })); - - buffer.add(0); - buffer.add(4); - - ASSERT_EQ(std::vector({2, 3, 4}), buffer.getEntries([](const int& el) { - return el != 0; - })); - auto elems = buffer.consume(); - ASSERT_EQ(std::vector({2, 3, 4}), buffer.getEntries([](const int& el) { - return el != 0; - })); -} - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp new file mode 100644 index 00000000000000..488144a6828db7 --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include + +#include "../CircularBuffer.h" + +namespace facebook::react { + +using namespace facebook::react; + +constexpr auto OK = false; +constexpr auto OVERWRITE = true; + +TEST(CircularBuffer, CanAddAndRetrieveElements) { + CircularBuffer buffer; + + ASSERT_EQ(OK, buffer.add(1)); + ASSERT_EQ(OK, buffer.add(2)); + + ASSERT_EQ(1, buffer[0]); + ASSERT_EQ(2, buffer[1]); + ASSERT_EQ(2, buffer.size()); + ASSERT_EQ(std::vector({1, 2}), buffer.getEntries()); + + ASSERT_EQ(OK, buffer.add(3)); + ASSERT_EQ(3, buffer.size()); + ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); + + ASSERT_EQ(1, buffer[0]); + ASSERT_EQ(2, buffer[1]); + ASSERT_EQ(3, buffer[2]); +} + +TEST(BoundedConsumableBuffer, WrapsAroundCorrectly) { + CircularBuffer buffer(3); + + ASSERT_EQ(OK, buffer.add(1)); + ASSERT_EQ(OK, buffer.add(2)); + + ASSERT_EQ(std::vector({1, 2}), buffer.getEntries()); + + ASSERT_EQ(OK, buffer.add(3)); + ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); + + ASSERT_EQ(OVERWRITE, buffer.add(4)); + ASSERT_EQ(OVERWRITE, buffer.add(5)); + + ASSERT_EQ(std::vector({3, 4, 5}), buffer.getEntries()); + + ASSERT_EQ(OVERWRITE, buffer.add(6)); + ASSERT_EQ(OVERWRITE, buffer.add(7)); + + ASSERT_EQ(std::vector({5, 6, 7}), buffer.getEntries()); + + ASSERT_EQ(OVERWRITE, buffer.add(8)); + ASSERT_EQ(OVERWRITE, buffer.add(9)); + ASSERT_EQ(OVERWRITE, buffer.add(10)); + ASSERT_EQ(std::vector({8, 9, 10}), buffer.getEntries()); + + ASSERT_EQ(8, buffer[0]); + ASSERT_EQ(9, buffer[1]); + ASSERT_EQ(10, buffer[2]); + + ASSERT_EQ(8, *buffer.getNextOverwriteCandidate()); +} + +TEST(BoundedConsumableBuffer, CanClearByPredicate) { + CircularBuffer buffer(5); + + buffer.add(1); + buffer.add(0); + buffer.add(2); + buffer.add(0); + buffer.add(3); + + buffer.clear([](const int& el) { return el == 0; }); + ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); + + buffer.add(0); + buffer.add(4); + buffer.clear([](const int& el) { return el == 0; }); + ASSERT_EQ(std::vector({1, 2, 3, 4}), buffer.getEntries()); +} + +TEST(BoundedConsumableBuffer, CanClearBeforeReachingMaxSize) { + CircularBuffer buffer(5); + + buffer.add(1); + buffer.add(2); + buffer.add(3); + + buffer.clear([](const int&) { return false; }); // no-op clear + ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries()); + + buffer.add(4); + buffer.add(5); + + ASSERT_EQ(std::vector({1, 2, 3, 4, 5}), buffer.getEntries()); +} + +TEST(BoundedConsumableBuffer, CanGetByPredicate) { + CircularBuffer buffer(5); + + buffer.add(1); + buffer.add(0); + buffer.add(2); + buffer.add(0); + buffer.add(3); + + ASSERT_EQ(std::vector({1, 2, 3}), buffer.getEntries([](const int& el) { + return el != 0; + })); + + buffer.add(0); + buffer.add(4); + + ASSERT_EQ(std::vector({2, 3, 4}), buffer.getEntries([](const int& el) { + return el != 0; + })); +} + +} // namespace facebook::react From 771537e931ee6bff2666ca79d24d6b2eee29c84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 14:10:20 +0200 Subject: [PATCH 40/60] chore: remove allEntries cache from keyed buffer --- .../performance/timeline/PerformanceEntryKeyedBuffer.cpp | 7 ------- .../performance/timeline/PerformanceEntryKeyedBuffer.h | 1 - 2 files changed, 8 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index 8b20149286bf7d..e7b2345d8de5ce 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -53,12 +53,6 @@ std::optional PerformanceEntryKeyedBuffer::find(const std::str } void PerformanceEntryKeyedBuffer::getEntries(std::vector& target) const { - if (allEntriesCache_.has_value()) { - auto& allEntries = allEntriesCache_.value(); - target.insert(target.end(), allEntries.begin(), allEntries.end()); - return; - } - std::vector allEntries; // pre-allocate result vector allEntries.reserve(totalEntryCount_); @@ -69,7 +63,6 @@ void PerformanceEntryKeyedBuffer::getEntries(std::vector& targ std::stable_sort( allEntries.begin(), allEntries.end(), PerformanceEntrySorter{}); - allEntriesCache_ = allEntries; target.insert(target.end(), allEntries.begin(), allEntries.end()); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index 7379860add82b3..4e9825c6114738 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -31,7 +31,6 @@ class PerformanceEntryKeyedBuffer: public PerformanceEntryBuffer { private: std::unordered_map> entryMap_{}; - mutable std::optional> allEntriesCache_; size_t totalEntryCount_ = 0; }; From 88cc8adf8d46ca95754033cc0d07644b78349d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 15:17:38 +0200 Subject: [PATCH 41/60] fix: refactor EntryReporter public API --- .../timeline/PerformanceEntryReporter.cpp | 25 ++++++----- .../timeline/PerformanceEntryReporter.h | 42 ++++++++++--------- .../timeline/PerformanceObserver.cpp | 6 +-- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 030fc22a6fe122..971234e125e7e8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -24,6 +24,10 @@ DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { : JSExecutor::performanceNow(); } +double PerformanceEntryReporter::getDroppedEntriesCount(PerformanceEntryType type) const noexcept { + return getBuffer(type).droppedEntriesCount; +} + void PerformanceEntryReporter::mark( const std::string& name, const std::optional& startTime) { @@ -51,26 +55,27 @@ void PerformanceEntryReporter::clearEntries( measureBuffer_.clear(); eventBuffer_.clear(); longTaskBuffer_.clear(); + return; + } + + auto& buffer = getBufferRef(*entryType); + if (!entryName.empty()) { + std::lock_guard lock(buffersMutex_); + buffer.clear(entryName); } else { - auto& buffer = getBufferRef(*entryType); - if (!entryName.empty()) { - std::lock_guard lock(buffersMutex_); - buffer.clear(entryName); - } else { - std::lock_guard lock(buffersMutex_); - buffer.clear(); - } + std::lock_guard lock(buffersMutex_); + buffer.clear(); } } void PerformanceEntryReporter::getEntries( PerformanceEntryType entryType, - std::string_view entryName, + std::optional entryName, std::vector& res) const { std::lock_guard lock(buffersMutex_); auto& buffer = getBuffer(entryType); - if (entryName.empty()) { + if (entryName.has_value() && entryName.value().empty()) { buffer.getEntries(std::nullopt, res); } else { buffer.getEntries(entryName, res); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 58ae2d87eedd57..36dc756f295a2d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -37,6 +37,8 @@ class PerformanceEntryReporter { PerformanceObserverRegistry& getObserverRegistry() { return *observerRegistry_; } + + double getDroppedEntriesCount(PerformanceEntryType type) const noexcept; /* * DOM Performance (High Resolution Time) @@ -98,21 +100,11 @@ class PerformanceEntryReporter { void clearEntries( std::optional entryType = std::nullopt, std::string_view entryName = {}); - - inline const PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) const { - switch (entryType) { - case PerformanceEntryType::EVENT: - return eventBuffer_; - case PerformanceEntryType::MARK: - return markBuffer_; - case PerformanceEntryType::MEASURE: - return measureBuffer_; - case PerformanceEntryType::LONGTASK: - return longTaskBuffer_; - default: - assert(0 && "Unhandled PerformanceEntryType"); - } - } + + void getEntries( + PerformanceEntryType entryType, + std::optional entryName, + std::vector& res) const; private: PerformanceObserverRegistry::Ptr observerRegistry_; @@ -129,11 +121,6 @@ class PerformanceEntryReporter { double getMarkTime(const std::string& markName) const; - void getEntries( - PerformanceEntryType entryType, - std::string_view entryName, - std::vector& res) const; - inline PerformanceEntryBuffer& getBufferRef(PerformanceEntryType entryType) { switch (entryType) { case PerformanceEntryType::EVENT: @@ -148,6 +135,21 @@ class PerformanceEntryReporter { assert(0 && "Unhandled PerformanceEntryType"); } } + + const inline PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) const { + switch (entryType) { + case PerformanceEntryType::EVENT: + return eventBuffer_; + case PerformanceEntryType::MARK: + return markBuffer_; + case PerformanceEntryType::MEASURE: + return measureBuffer_; + case PerformanceEntryType::LONGTASK: + return longTaskBuffer_; + default: + assert(0 && "Unhandled PerformanceEntryType"); + } + } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index cef009644c19c7..90ba7398f5fade 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -42,7 +42,7 @@ void PerformanceObserver::observe(PerformanceEntryType type, PerformanceObserver if (options.buffered) { auto& reporter = PerformanceEntryReporter::getInstance(); - reporter->getBuffer(type).getEntries(std::nullopt, buffer_); + reporter->getEntries(type, std::nullopt, buffer_); scheduleFlushBuffer(); } } @@ -59,8 +59,8 @@ double PerformanceObserver::getDroppedEntriesCount() noexcept { if (requiresDroppedEntries_) { auto reporter = PerformanceEntryReporter::getInstance(); - for (auto& entry : observedTypes_) { - droppedEntriesCount += reporter->getBuffer(entry).droppedEntriesCount; + for (auto& entryType : observedTypes_) { + droppedEntriesCount += reporter->getDroppedEntriesCount(entryType); } requiresDroppedEntries_ = false; From a8cbb486ba9640da9f1c8237fb22d9cb31e65564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 15:27:25 +0200 Subject: [PATCH 42/60] style: format code --- .../webperformance/NativePerformance.cpp | 2 +- .../NativePerformanceObserver.cpp | 77 +++++++++++-------- .../NativePerformanceObserver.h | 42 ++++++---- .../performance/timeline/CircularBuffer.h | 11 +-- .../performance/timeline/PerformanceEntry.h | 2 +- .../timeline/PerformanceEntryBuffer.h | 2 +- .../PerformanceEntryCircularBuffer.cpp | 11 ++- .../timeline/PerformanceEntryCircularBuffer.h | 2 +- .../timeline/PerformanceEntryKeyedBuffer.cpp | 12 ++- .../timeline/PerformanceEntryKeyedBuffer.h | 2 +- .../timeline/PerformanceEntryReporter.cpp | 49 ++++++------ .../timeline/PerformanceEntryReporter.h | 19 ++--- .../timeline/PerformanceObserver.cpp | 11 ++- .../timeline/PerformanceObserver.h | 42 ++++++---- .../timeline/PerformanceObserverRegistry.cpp | 17 ++-- .../timeline/PerformanceObserverRegistry.h | 16 ++-- 16 files changed, 190 insertions(+), 127 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 1e7e62d35a1736..2b933d897d0b4e 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -116,7 +116,7 @@ void NativePerformance::measure( FuseboxTracer::getFuseboxTracer().addEvent( eventName, (uint64_t)startTime, (uint64_t)endTime, trackName); #endif - + PerformanceEntryReporter::getInstance()->measure( eventName, startTime, endTime, duration, startMark, endMark); diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 40c4bb9732c0f0..03810e4896c344 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -30,29 +30,37 @@ NativePerformanceObserver::NativePerformanceObserver( std::shared_ptr jsInvoker) : NativePerformanceObserverCxxSpec(std::move(jsInvoker)) {} -jsi::Object NativePerformanceObserver::createObserver(jsi::Runtime& rt, NativePerformanceObserverCallback callback) { +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 = [this, callback = std::move(callback)](PerformanceObserver& currentObserver) { - jsInvoker_->invokeAsync(SchedulerPriority::IdlePriority, [currentObserver, callback] (jsi::Runtime& /*rt*/) mutable { - callback.call(); - currentObserver.flush(); - }); + // the spec. The specification requires us to queue a single task that + // dispatches observer callbacks. Instead, we are queuing all callbacks as + // separate tasks in the scheduler. + PerformanceObserverCallback cb = [this, callback = std::move(callback)]( + PerformanceObserver& currentObserver) { + jsInvoker_->invokeAsync( + SchedulerPriority::IdlePriority, + [currentObserver, callback](jsi::Runtime& /*rt*/) mutable { + callback.call(); + currentObserver.flush(); + }); }; - auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); + auto& registry = + PerformanceEntryReporter::getInstance()->getObserverRegistry(); auto observer = registry.createObserver(std::move(cb)); - jsi::Object observerObj {rt}; + jsi::Object observerObj{rt}; observerObj.setNativeState(rt, observer); return observerObj; } -double NativePerformanceObserver::getDroppedEntriesCount(jsi::Runtime& rt, jsi::Object observerObj) { - auto observer = - std::dynamic_pointer_cast(observerObj.getNativeState(rt)); +double NativePerformanceObserver::getDroppedEntriesCount( + jsi::Runtime& rt, + jsi::Object observerObj) { + auto observer = std::dynamic_pointer_cast( + observerObj.getNativeState(rt)); if (!observer) { return 0; @@ -61,9 +69,12 @@ double NativePerformanceObserver::getDroppedEntriesCount(jsi::Runtime& rt, jsi:: return observer->getDroppedEntriesCount(); } -void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerObj, NativePerformanceObserverObserveOptions options) { - auto observer = - std::dynamic_pointer_cast(observerObj.getNativeState(rt)); +void NativePerformanceObserver::observe( + jsi::Runtime& rt, + jsi::Object observerObj, + NativePerformanceObserverObserveOptions options) { + auto observer = std::dynamic_pointer_cast( + observerObj.getNativeState(rt)); if (!observer) { return; @@ -76,35 +87,41 @@ void NativePerformanceObserver::observe(jsi::Runtime& rt, jsi::Object observerOb auto rawTypes = options.entryTypes.value(); for (auto i = 0; i < rawTypes.size(); ++i) { - entryTypes.insert(Bridging::fromJs(rt, rawTypes[i])); + entryTypes.insert( + Bridging::fromJs(rt, rawTypes[i])); } - observer->observe(entryTypes, { .durationThreshold = durationThreshold }); - } - else { // single + observer->observe(entryTypes, {.durationThreshold = durationThreshold}); + } else { // single auto buffered = options.buffered.value_or(false); if (options.type.has_value()) { - observer->observe(static_cast( options.type.value()), { .buffered = buffered, .durationThreshold = durationThreshold }); + observer->observe( + static_cast(options.type.value()), + {.buffered = buffered, .durationThreshold = durationThreshold}); } } } -void NativePerformanceObserver::disconnect(jsi::Runtime& rt, jsi::Object observerObj) { - auto observer = - std::dynamic_pointer_cast(observerObj.getNativeState(rt)); +void NativePerformanceObserver::disconnect( + jsi::Runtime& rt, + jsi::Object observerObj) { + auto observer = std::dynamic_pointer_cast( + observerObj.getNativeState(rt)); if (!observer) { return; } observerObj.setNativeState(rt, nullptr); - auto& registry = PerformanceEntryReporter::getInstance()->getObserverRegistry(); + auto& registry = + PerformanceEntryReporter::getInstance()->getObserverRegistry(); registry.removeObserver(observer); } -std::vector NativePerformanceObserver::takeRecords(jsi::Runtime& rt, jsi::Object observerObj) { - auto observer = - std::dynamic_pointer_cast( +std::vector NativePerformanceObserver::takeRecords( + jsi::Runtime& rt, + jsi::Object observerObj) { + auto observer = std::dynamic_pointer_cast( observerObj.getNativeState(rt)); if (!observer) { @@ -150,6 +167,6 @@ std::vector> NativePerformanceObserver::getEventCounts(jsi::Runtime& /*rt*/) { const auto& eventCounts = PerformanceEntryReporter::getInstance()->getEventCounts(); - return { eventCounts.begin(), eventCounts.end() }; + return {eventCounts.begin(), eventCounts.end()}; } } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 7c45950cb29309..89d80cdd782732 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -23,16 +23,16 @@ namespace facebook::react { using NativePerformanceObserverCallback = AsyncCallback<>; -using NativePerformanceObserverObserveOptions = NativePerformanceObserverPerformanceObserverInit< - // entryTypes - std::optional>, - // type - std::optional, - // buffered - std::optional, - // durationThreshold - std::optional ->; +using NativePerformanceObserverObserveOptions = + NativePerformanceObserverPerformanceObserverInit< + // entryTypes + std::optional>, + // type + std::optional, + // buffered + std::optional, + // durationThreshold + std::optional>; #pragma mark - Structs @@ -52,7 +52,9 @@ struct Bridging { }; template <> -struct Bridging : NativePerformanceObserverPerformanceObserverInitBridging {}; +struct Bridging + : NativePerformanceObserverPerformanceObserverInitBridging< + NativePerformanceObserverObserveOptions> {}; template <> struct Bridging @@ -64,15 +66,23 @@ class NativePerformanceObserver : public NativePerformanceObserverCxxSpec { public: explicit NativePerformanceObserver(std::shared_ptr jsInvoker); - - jsi::Object createObserver(jsi::Runtime& rt, NativePerformanceObserverCallback callback); + + 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 observe( + jsi::Runtime& rt, + jsi::Object observer, + NativePerformanceObserverObserveOptions options); void disconnect(jsi::Runtime& rt, jsi::Object observer); - std::vector takeRecords(jsi::Runtime& rt, jsi::Object observerObj); + std::vector takeRecords( + jsi::Runtime& rt, + jsi::Object observerObj); - std::vector> getEventCounts(jsi::Runtime& rt); + std::vector> getEventCounts( + jsi::Runtime& rt); void clearEntries( jsi::Runtime& rt, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h index d25edbb729e970..5ed2f27233c8b8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h @@ -27,17 +27,18 @@ constexpr size_t DEFAULT_MAX_SIZE = 1024; */ template class CircularBuffer { -public: - explicit CircularBuffer(size_t maxSize = DEFAULT_MAX_SIZE) : maxSize_(maxSize) { + public: + explicit CircularBuffer(size_t maxSize = DEFAULT_MAX_SIZE) + : maxSize_(maxSize) { entries_.reserve(maxSize_); } /** * Adds (pushes) element into the buffer. * - * Returns the result of the operation, which will depend on whether the buffer - * reached the max allowed size, in which case `true` is returned. If no - * items were overridden `false` is returned. + * Returns the result of the operation, which will depend on whether the + * buffer reached the max allowed size, in which case `true` is returned. If + * no items were overridden `false` is returned. */ bool add(const T&& el) { if (entries_.size() < maxSize_) { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h index eb6e9c07ba324b..91c4fb58e2f584 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntry.h @@ -7,10 +7,10 @@ #pragma once +#include #include #include #include -#include namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index 03e5851a2092e1..c86099e65a8602 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -20,7 +20,7 @@ constexpr double DEFAULT_DURATION_THRESHOLD = 0.0; * Subtypes differ on how entries are stored. */ class PerformanceEntryBuffer { -public: + public: double durationThreshold{DEFAULT_DURATION_THRESHOLD}; size_t droppedEntriesCount{0}; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index 5f71f175048eb1..8b5c3b428035ee 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -9,21 +9,24 @@ namespace facebook::react { -void PerformanceEntryCircularBuffer::add(const facebook::react::PerformanceEntry& entry) { +void PerformanceEntryCircularBuffer::add( + const facebook::react::PerformanceEntry& entry) { if (buffer_.add(std::move(entry))) { droppedEntriesCount += 1; } } -void PerformanceEntryCircularBuffer::getEntries(std::optional name, std::vector& target) const { - buffer_.getEntries(target, [&](const PerformanceEntry& e) { return e.name == name; }); +void PerformanceEntryCircularBuffer::getEntries( + std::optional name, + std::vector& target) const { + buffer_.getEntries( + target, [&](const PerformanceEntry& e) { return e.name == name; }); } void PerformanceEntryCircularBuffer::clear() { buffer_.clear(); } - void PerformanceEntryCircularBuffer::clear(std::string_view name) { buffer_.clear([&](const PerformanceEntry& e) { return e.name == name; }); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index ea0f441a0ccb70..91836d5c2c08c2 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -25,7 +25,7 @@ class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void clear() override; void clear(std::string_view name) override; - + private: CircularBuffer buffer_; }; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index e7b2345d8de5ce..79ae7bb52eaba3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -16,7 +16,9 @@ void PerformanceEntryKeyedBuffer::add(const PerformanceEntry& entry) { totalEntryCount_ += 1; } -void PerformanceEntryKeyedBuffer::getEntries(std::optional name, std::vector& target) const { +void PerformanceEntryKeyedBuffer::getEntries( + std::optional name, + std::vector& target) const { if (name.has_value()) { std::string nameStr{name.value()}; @@ -34,7 +36,7 @@ void PerformanceEntryKeyedBuffer::clear() { } void PerformanceEntryKeyedBuffer::clear(std::string_view nameView) { - std::string name { nameView }; + std::string name{nameView}; if (auto node = entryMap_.find(name); node != entryMap_.end()) { totalEntryCount_ -= node->second.size(); @@ -42,7 +44,8 @@ void PerformanceEntryKeyedBuffer::clear(std::string_view nameView) { } } -std::optional PerformanceEntryKeyedBuffer::find(const std::string& name) const { +std::optional PerformanceEntryKeyedBuffer::find( + const std::string& name) const { if (auto node = entryMap_.find(name); node != entryMap_.end()) { if (!node->second.empty()) { return std::make_optional(node->second.back()); @@ -52,7 +55,8 @@ std::optional PerformanceEntryKeyedBuffer::find(const std::str return std::nullopt; } -void PerformanceEntryKeyedBuffer::getEntries(std::vector& target) const { +void PerformanceEntryKeyedBuffer::getEntries( + std::vector& target) const { std::vector allEntries; // pre-allocate result vector allEntries.reserve(totalEntryCount_); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index 4e9825c6114738..26cc88d2605300 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -13,7 +13,7 @@ namespace facebook::react { -class PerformanceEntryKeyedBuffer: public PerformanceEntryBuffer { +class PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { public: PerformanceEntryKeyedBuffer() = default; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 971234e125e7e8..6d2955fd0eb18d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -17,25 +17,26 @@ PerformanceEntryReporter::getInstance() { return instance; } -PerformanceEntryReporter::PerformanceEntryReporter(): observerRegistry_(std::make_unique()) {} +PerformanceEntryReporter::PerformanceEntryReporter() + : observerRegistry_(std::make_unique()) {} DOMHighResTimeStamp PerformanceEntryReporter::getCurrentTimeStamp() const { return timeStampProvider_ != nullptr ? timeStampProvider_() : JSExecutor::performanceNow(); } -double PerformanceEntryReporter::getDroppedEntriesCount(PerformanceEntryType type) const noexcept { +double PerformanceEntryReporter::getDroppedEntriesCount( + PerformanceEntryType type) const noexcept { return getBuffer(type).droppedEntriesCount; } void PerformanceEntryReporter::mark( const std::string& name, const std::optional& startTime) { - const auto entry = PerformanceEntry { + const auto entry = PerformanceEntry{ .name = name, .entryType = PerformanceEntryType::MARK, - .startTime = startTime ? *startTime : getCurrentTimeStamp() - }; + .startTime = startTime ? *startTime : getCurrentTimeStamp()}; { std::lock_guard lock(buffersMutex_); @@ -57,7 +58,7 @@ void PerformanceEntryReporter::clearEntries( longTaskBuffer_.clear(); return; } - + auto& buffer = getBufferRef(*entryType); if (!entryName.empty()) { std::lock_guard lock(buffersMutex_); @@ -117,12 +118,11 @@ void PerformanceEntryReporter::measure( DOMHighResTimeStamp durationVal = duration ? *duration : endTimeVal - startTimeVal; - auto const entry = PerformanceEntry { + const auto entry = PerformanceEntry{ .name = std::string(name), .entryType = PerformanceEntryType::MEASURE, .startTime = startTimeVal, - .duration = durationVal - }; + .duration = durationVal}; { std::lock_guard lock(buffersMutex_); @@ -152,21 +152,21 @@ void PerformanceEntryReporter::logEventEntry( uint32_t interactionId) { eventCounts_[name]++; - auto const entry = PerformanceEntry { - .name = std::move(name), - .entryType = PerformanceEntryType::EVENT, - .startTime = startTime, - .duration = duration, - .processingStart = processingStart, - .processingEnd = processingEnd, - .interactionId = interactionId - }; + const auto entry = PerformanceEntry{ + .name = std::move(name), + .entryType = PerformanceEntryType::EVENT, + .startTime = startTime, + .duration = duration, + .processingStart = processingStart, + .processingEnd = processingEnd, + .interactionId = interactionId}; { std::lock_guard lock(buffersMutex_); if (entry.duration < eventBuffer_.durationThreshold) { - // The entries duration is lower than the desired reporting threshold, skip + // The entries duration is lower than the desired reporting threshold, + // skip return; } @@ -179,12 +179,11 @@ void PerformanceEntryReporter::logEventEntry( void PerformanceEntryReporter::logLongTaskEntry( DOMHighResTimeStamp startTime, DOMHighResTimeStamp duration) { - auto const entry = PerformanceEntry { - .name = std::string{"self"}, - .entryType = PerformanceEntryType::LONGTASK, - .startTime = startTime, - .duration = duration - }; + const auto entry = PerformanceEntry{ + .name = std::string{"self"}, + .entryType = PerformanceEntryType::LONGTASK, + .startTime = startTime, + .duration = duration}; { std::lock_guard lock(buffersMutex_); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 36dc756f295a2d..36af1a3f78cb02 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -8,9 +8,9 @@ #pragma once #include -#include "PerformanceObserverRegistry.h" #include "PerformanceEntryCircularBuffer.h" #include "PerformanceEntryKeyedBuffer.h" +#include "PerformanceObserverRegistry.h" #include #include @@ -37,7 +37,7 @@ class PerformanceEntryReporter { PerformanceObserverRegistry& getObserverRegistry() { return *observerRegistry_; } - + double getDroppedEntriesCount(PerformanceEntryType type) const noexcept; /* @@ -100,13 +100,13 @@ class PerformanceEntryReporter { void clearEntries( std::optional entryType = std::nullopt, std::string_view entryName = {}); - + void getEntries( - PerformanceEntryType entryType, - std::optional entryName, - std::vector& res) const; + PerformanceEntryType entryType, + std::optional entryName, + std::vector& res) const; -private: + private: PerformanceObserverRegistry::Ptr observerRegistry_; mutable std::mutex buffersMutex_; @@ -135,8 +135,9 @@ class PerformanceEntryReporter { assert(0 && "Unhandled PerformanceEntryType"); } } - - const inline PerformanceEntryBuffer& getBuffer(PerformanceEntryType entryType) const { + + const inline PerformanceEntryBuffer& getBuffer( + PerformanceEntryType entryType) const { switch (entryType) { case PerformanceEntryType::EVENT: return eventBuffer_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 90ba7398f5fade..1eaa00a1fc6858 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -15,7 +15,8 @@ void PerformanceObserver::handleEntry(const PerformanceEntry& entry) { // https://www.w3.org/TR/event-timing/#should-add-performanceeventtiming if (entry.entryType == PerformanceEntryType::EVENT) { if (entry.duration < durationThreshold_) { - // The entries duration is lower than the desired reporting threshold, skip + // The entries duration is lower than the desired reporting threshold, + // skip return; } } @@ -31,7 +32,9 @@ std::vector PerformanceObserver::takeRecords() { return result; } -void PerformanceObserver::observe(PerformanceEntryType type, PerformanceObserverObserveSingleOptions options) { +void PerformanceObserver::observe( + PerformanceEntryType type, + PerformanceObserverObserveSingleOptions options) { // we assume that `type` was checked on JS side and is correct observedTypes_.clear(); observedTypes_.insert(type); @@ -47,7 +50,9 @@ void PerformanceObserver::observe(PerformanceEntryType type, PerformanceObserver } } -void PerformanceObserver::observe(std::unordered_set types, PerformanceObserverObserveMultipleOptions options) { +void PerformanceObserver::observe( + std::unordered_set types, + PerformanceObserverObserveMultipleOptions options) { observedTypes_ = std::move(types); requiresDroppedEntries_ = false; durationThreshold_ = options.durationThreshold; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 9f0d0b7330cb3c..157af0025c2ff8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -7,22 +7,25 @@ #pragma once +#include #include +#include #include #include -#include -#include #include "PerformanceEntryBuffer.h" namespace facebook::react { class PerformanceObserver; -using PerformanceObserverEntryTypeFilter = std::unordered_set; -using PerformanceObserverCallback = std::function; +using PerformanceObserverEntryTypeFilter = + std::unordered_set; +using PerformanceObserverCallback = + std::function; /** - * Represents subset of spec's `PerformanceObserverInit` that is allowed for multiple types. + * Represents subset of spec's `PerformanceObserverInit` that is allowed for + * multiple types. * * https://w3c.github.io/performance-timeline/#performanceobserverinit-dictionary */ @@ -31,7 +34,8 @@ struct PerformanceObserverObserveMultipleOptions { }; /** - * Represents subset of spec's `PerformanceObserverInit` that is allowed for single type. + * Represents subset of spec's `PerformanceObserverInit` that is allowed for + * single type. * * https://w3c.github.io/performance-timeline/#performanceobserverinit-dictionary */ @@ -55,7 +59,7 @@ struct PerformanceObserverObserveSingleOptions { * You can still manually create instance of `PerformanceObserver`, but then * you need to manually register and unregister it from registry. */ -class PerformanceObserver: public jsi::NativeState { +class PerformanceObserver : public jsi::NativeState { public: using Ptr = std::shared_ptr; using WeakPtr = std::weak_ptr; @@ -82,21 +86,27 @@ class PerformanceObserver: public jsi::NativeState { * Configures the observer to watch for specified entry type. * * This operation resets and overrides previous configurations. So consecutive - * calls to this methods remove any previous watch configuration (as per spec). + * calls to this methods remove any previous watch configuration (as per + * spec). */ - void observe(PerformanceEntryType type, PerformanceObserverObserveSingleOptions options = {}); + void observe( + PerformanceEntryType type, + PerformanceObserverObserveSingleOptions options = {}); /** * Configures the observer to watch for specified entry type. * * This operation resets and overrides previous configurations. So consecutive - * calls to this methods remove any previous watch configuration (as per spec). + * calls to this methods remove any previous watch configuration (as per + * spec). */ - void observe(std::unordered_set types, PerformanceObserverObserveMultipleOptions options = {}); + void observe( + std::unordered_set types, + PerformanceObserverObserveMultipleOptions options = {}); /** - * Internal function called by JS bridge to get number of dropped entries count - * counted at call time. + * Internal function called by JS bridge to get number of dropped entries + * count counted at call time. */ double getDroppedEntriesCount() noexcept; @@ -105,7 +115,7 @@ class PerformanceObserver: public jsi::NativeState { */ void flush() noexcept; -private: + private: void scheduleFlushBuffer(); PerformanceObserverCallback callback_; @@ -118,7 +128,9 @@ class PerformanceObserver: public jsi::NativeState { bool requiresDroppedEntries_ = false; }; -inline bool operator==(const PerformanceObserver& lhs, const PerformanceObserver& rhs) { +inline bool operator==( + const PerformanceObserver& lhs, + const PerformanceObserver& rhs) { return &lhs == &rhs; } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 93f48ca7447934..3538f6cf574e99 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -9,7 +9,8 @@ namespace facebook::react { -PerformanceObserver::Ptr PerformanceObserverRegistry::createObserver(PerformanceObserverCallback&& callback) { +PerformanceObserver::Ptr PerformanceObserverRegistry::createObserver( + PerformanceObserverCallback&& callback) { // We allocate observer manually, and use `shared_ptr` deleter // functionality to remove observer from registry auto obj = new PerformanceObserver(std::move(callback)); @@ -17,28 +18,32 @@ PerformanceObserver::Ptr PerformanceObserverRegistry::createObserver(Performance this->removeObserver(*obj); delete observer; }; - PerformanceObserver::Ptr ptr {obj, deleter}; + PerformanceObserver::Ptr ptr{obj, deleter}; return ptr; } -void PerformanceObserverRegistry::addObserver(const PerformanceObserver::WeakPtr& observer) { +void PerformanceObserverRegistry::addObserver( + const PerformanceObserver::WeakPtr& observer) { std::lock_guard guard(observersMutex_); observers_.insert(observer); } -void PerformanceObserverRegistry::removeObserver(const PerformanceObserver& observer) { +void PerformanceObserverRegistry::removeObserver( + const PerformanceObserver& observer) { std::lock_guard guard(observersMutex_); erase_if(observers_, [&](auto e) -> bool { return !e.expired() && *e.lock() == observer; }); } -void PerformanceObserverRegistry::removeObserver(const PerformanceObserver::Ptr& observer) { +void PerformanceObserverRegistry::removeObserver( + const PerformanceObserver::Ptr& observer) { std::lock_guard guard(observersMutex_); observers_.erase(observer); } -void PerformanceObserverRegistry::queuePerformanceEntry(const PerformanceEntry& entry) { +void PerformanceObserverRegistry::queuePerformanceEntry( + const PerformanceEntry& entry) { std::lock_guard lock(observersMutex_); // filter dead observers diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index fc00cc906756cf..614612ad81de0c 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -31,15 +31,18 @@ class PerformanceObserverRegistry { /** * Creates Performance Observer instance and registers it in the registry. * - * Observer is automatically removed from the registry when it goes out of scope. - * You can manually remove it from the registry earlier by calling `removeObserver`. + * Observer is automatically removed from the registry when it goes out of + * scope. You can manually remove it from the registry earlier by calling + * `removeObserver`. */ - PerformanceObserver::Ptr createObserver(PerformanceObserverCallback&& callback); + PerformanceObserver::Ptr createObserver( + PerformanceObserverCallback&& callback); /** * Removes observer from the registry. * - * It is called automatically for observers created by `createObserver` method. + * It is called automatically for observers created by `createObserver` + * method. */ void removeObserver(const PerformanceObserver::Ptr& observer); @@ -55,7 +58,10 @@ class PerformanceObserverRegistry { private: mutable std::mutex observersMutex_; - std::set> observers_; + std::set< + PerformanceObserver::WeakPtr, + std::owner_less> + observers_; }; } // namespace facebook::react From 40b6200648b5f54f62070fd5d54b987cc7547410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 16:09:05 +0200 Subject: [PATCH 43/60] chore: make add/removeObserver part of registry public API --- .../react/performance/timeline/PerformanceObserverRegistry.h | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index 614612ad81de0c..fb118ed99e5402 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -52,7 +52,6 @@ class PerformanceObserverRegistry { */ void queuePerformanceEntry(const PerformanceEntry& entry); - private: void addObserver(const PerformanceObserver::WeakPtr& observer); void removeObserver(const PerformanceObserver& observer); From 055ae21f7cabe8d319eec12b54c0094b5c04b9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 16:09:51 +0200 Subject: [PATCH 44/60] chore: remove getNextOverwriteCandidate --- .../react/performance/timeline/CircularBuffer.h | 12 ------------ .../timeline/tests/CircularBufferTest.cpp | 2 -- 2 files changed, 14 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h index 5ed2f27233c8b8..f4dbd5f4a02726 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h @@ -53,18 +53,6 @@ class CircularBuffer { } } - /** - * Returns pointer to next entry which would be overwritten or dropped if - * added a new element. Null if no entry will be dropped. - */ - const T* getNextOverwriteCandidate() const { - if (entries_.size() < maxSize_) { - return nullptr; - } else { - return &entries_[position_]; - } - } - T& operator[](size_t idx) { return entries_[(position_ + idx) % entries_.size()]; } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp index 488144a6828db7..3dd59765f0ef6a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp @@ -67,8 +67,6 @@ TEST(BoundedConsumableBuffer, WrapsAroundCorrectly) { ASSERT_EQ(8, buffer[0]); ASSERT_EQ(9, buffer[1]); ASSERT_EQ(10, buffer[2]); - - ASSERT_EQ(8, *buffer.getNextOverwriteCandidate()); } TEST(BoundedConsumableBuffer, CanClearByPredicate) { From 4a9372cd205d9b38de14fd1452b923e3d69b0906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 16:21:18 +0200 Subject: [PATCH 45/60] fix: schedule buffer flush only when its not empty --- .../react/performance/timeline/PerformanceObserver.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 1eaa00a1fc6858..b3504ada479a16 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -46,7 +46,10 @@ void PerformanceObserver::observe( if (options.buffered) { auto& reporter = PerformanceEntryReporter::getInstance(); reporter->getEntries(type, std::nullopt, buffer_); - scheduleFlushBuffer(); + + if (!buffer_.empty()) { + scheduleFlushBuffer(); + } } } From 8c987ab63d5f9fb510d44bfe798467e3c58a681d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 16:46:57 +0200 Subject: [PATCH 46/60] chore: cleanup optionals as arguments --- .../NativePerformanceObserver.cpp | 17 +++++++- .../timeline/PerformanceEntryBuffer.h | 3 +- .../PerformanceEntryCircularBuffer.cpp | 6 ++- .../timeline/PerformanceEntryCircularBuffer.h | 3 +- .../timeline/PerformanceEntryKeyedBuffer.cpp | 42 ++++++++---------- .../timeline/PerformanceEntryKeyedBuffer.h | 6 ++- .../timeline/PerformanceEntryReporter.cpp | 43 ++++++++++--------- .../timeline/PerformanceEntryReporter.h | 12 ++---- .../timeline/PerformanceObserver.cpp | 2 +- 9 files changed, 74 insertions(+), 60 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 03810e4896c344..b4e3859390516d 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -143,8 +143,21 @@ std::vector NativePerformanceObserver::getEntries( jsi::Runtime& /*rt*/, std::optional entryType, std::optional entryName) { - return PerformanceEntryReporter::getInstance()->getEntries( - entryType, entryName ? entryName->c_str() : std::string_view{}); + const auto reporter = PerformanceEntryReporter::getInstance(); + + if (entryType.has_value()) { + if (entryName.has_value()) { + return reporter->getEntriesByName(entryName.value(), entryType.value()); + } + else { + return reporter->getEntriesByType(entryType.value()); + } + } + else if (entryName.has_value()) { + return reporter->getEntriesByName(entryName.value()); + } + + return reporter->getEntries(); } std::vector diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h index c86099e65a8602..738237c471d32c 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryBuffer.h @@ -29,8 +29,9 @@ class PerformanceEntryBuffer { virtual void add(const PerformanceEntry& entry) = 0; virtual void getEntries( - std::optional name, + std::string_view name, std::vector& target) const = 0; + virtual void getEntries(std::vector& target) const = 0; virtual void clear() = 0; virtual void clear(std::string_view name) = 0; }; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index 8b5c3b428035ee..556127aa15c9a3 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -16,8 +16,12 @@ void PerformanceEntryCircularBuffer::add( } } +void PerformanceEntryCircularBuffer::getEntries(std::vector& target) const { + buffer_.getEntries(target); +} + void PerformanceEntryCircularBuffer::getEntries( - std::optional name, + std::string_view name, std::vector& target) const { buffer_.getEntries( target, [&](const PerformanceEntry& e) { return e.name == name; }); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index 91836d5c2c08c2..ba5bc621ca2577 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -19,8 +19,9 @@ class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; + void getEntries(std::vector& target) const override; void getEntries( - std::optional name, + std::string_view name, std::vector& target) const override; void clear() override; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index 79ae7bb52eaba3..4b4089efcce4f8 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -17,16 +17,27 @@ void PerformanceEntryKeyedBuffer::add(const PerformanceEntry& entry) { } void PerformanceEntryKeyedBuffer::getEntries( - std::optional name, std::vector& target) const { - if (name.has_value()) { - std::string nameStr{name.value()}; + std::vector allEntries; + // pre-allocate result vector + allEntries.reserve(totalEntryCount_); - if (auto node = entryMap_.find(nameStr); node != entryMap_.end()) { - target.insert(target.end(), node->second.begin(), node->second.end()); - } - } else { - getEntries(target); + for (const auto& [_, entries] : entryMap_) { + allEntries.insert(allEntries.end(), entries.begin(), entries.end()); + } + + std::stable_sort( + allEntries.begin(), allEntries.end(), PerformanceEntrySorter{}); + target.insert(target.end(), allEntries.begin(), allEntries.end()); +} + +void PerformanceEntryKeyedBuffer::getEntries( + std::string_view name, + std::vector& target) const { + std::string nameStr{name}; + + if (auto node = entryMap_.find(nameStr); node != entryMap_.end()) { + target.insert(target.end(), node->second.begin(), node->second.end()); } } @@ -55,19 +66,4 @@ std::optional PerformanceEntryKeyedBuffer::find( return std::nullopt; } -void PerformanceEntryKeyedBuffer::getEntries( - std::vector& target) const { - std::vector allEntries; - // pre-allocate result vector - allEntries.reserve(totalEntryCount_); - - for (const auto& [_, entries] : entryMap_) { - allEntries.insert(allEntries.end(), entries.begin(), entries.end()); - } - - std::stable_sort( - allEntries.begin(), allEntries.end(), PerformanceEntrySorter{}); - target.insert(target.end(), allEntries.begin(), allEntries.end()); -} - } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index 26cc88d2605300..884847d43add45 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -20,13 +20,15 @@ class PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; void getEntries( - std::optional name, + std::vector& target) const override; + + void getEntries( + std::string_view name, std::vector& target) const override; void clear() override; void clear(std::string_view name) override; - void getEntries(std::vector& target) const; std::optional find(const std::string& name) const; private: diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index 6d2955fd0eb18d..db2d0657309844 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -69,35 +69,36 @@ void PerformanceEntryReporter::clearEntries( } } -void PerformanceEntryReporter::getEntries( - PerformanceEntryType entryType, - std::optional entryName, - std::vector& res) const { - std::lock_guard lock(buffersMutex_); - auto& buffer = getBuffer(entryType); - - if (entryName.has_value() && entryName.value().empty()) { - buffer.getEntries(std::nullopt, res); - } else { - buffer.getEntries(entryName, res); +std::vector PerformanceEntryReporter::getEntries() const { + std::vector res; + // Collect all entry types + for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) { + getBuffer(static_cast(i)).getEntries(res); } + return res; } -std::vector PerformanceEntryReporter::getEntries( - std::optional entryType, - std::string_view entryName) const { +std::vector PerformanceEntryReporter::getEntriesByType(PerformanceEntryType entryType) const { std::vector res; - if (!entryType) { - // Collect all entry types - for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) { - getEntries(static_cast(i), entryName, res); - } - } else { - getEntries(*entryType, entryName, res); + getBuffer(entryType).getEntries(res); + return res; +} + +std::vector PerformanceEntryReporter::getEntriesByName(std::string_view entryName) const { + std::vector res; + // Collect all entry types + for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) { + getBuffer(static_cast(i)).getEntries(entryName, res); } return res; } +std::vector PerformanceEntryReporter::getEntriesByName(std::string_view entryName, PerformanceEntryType entryType) const { + std::vector res; + getBuffer(entryType).getEntries(entryName, res); + return res; +} + void PerformanceEntryReporter::measure( const std::string_view& name, DOMHighResTimeStamp startTime, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 36af1a3f78cb02..2eb9c205e6609e 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -54,9 +54,10 @@ class PerformanceEntryReporter { // https://www.w3.org/TR/performance-timeline/#getentries-method // https://www.w3.org/TR/performance-timeline/#getentriesbytype-method // https://www.w3.org/TR/performance-timeline/#getentriesbyname-method - std::vector getEntries( - std::optional entryType = std::nullopt, - std::string_view entryName = {}) const; + std::vector getEntries() const; + std::vector getEntriesByType(PerformanceEntryType entryType) const; + std::vector getEntriesByName(std::string_view entryName) const; + std::vector getEntriesByName(std::string_view entryName, PerformanceEntryType entryType) const; void logEventEntry( std::string name, @@ -101,11 +102,6 @@ class PerformanceEntryReporter { std::optional entryType = std::nullopt, std::string_view entryName = {}); - void getEntries( - PerformanceEntryType entryType, - std::optional entryName, - std::vector& res) const; - private: PerformanceObserverRegistry::Ptr observerRegistry_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index b3504ada479a16..1b227cb391c366 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -45,7 +45,7 @@ void PerformanceObserver::observe( if (options.buffered) { auto& reporter = PerformanceEntryReporter::getInstance(); - reporter->getEntries(type, std::nullopt, buffer_); + buffer_ = reporter->getEntriesByType(type); if (!buffer_.empty()) { scheduleFlushBuffer(); From d22f5906bfd5cd036699bde1e8db2af38e3c82a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Wed, 18 Sep 2024 16:53:57 +0200 Subject: [PATCH 47/60] chore: remove vector after clearing elements by name --- .../react/performance/timeline/PerformanceEntryKeyedBuffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp index 4b4089efcce4f8..7a456868af7a2b 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.cpp @@ -51,7 +51,7 @@ void PerformanceEntryKeyedBuffer::clear(std::string_view nameView) { if (auto node = entryMap_.find(name); node != entryMap_.end()) { totalEntryCount_ -= node->second.size(); - node->second.clear(); + entryMap_.erase(node); } } From dfd94132df006b7f69a309dbe8a2f8150ce23898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 19 Sep 2024 10:15:15 +0200 Subject: [PATCH 48/60] chore: remove pointer aliases, make PerformanceObserver shared instead of owned --- .../NativePerformanceObserver.cpp | 10 ++++--- .../timeline/PerformanceEntryReporter.h | 2 +- .../timeline/PerformanceObserver.h | 3 --- .../timeline/PerformanceObserverRegistry.cpp | 26 ++----------------- .../timeline/PerformanceObserverRegistry.h | 24 ++++------------- 5 files changed, 14 insertions(+), 51 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index b4e3859390516d..5b49196ac1fb24 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -47,10 +47,7 @@ jsi::Object NativePerformanceObserver::createObserver( }); }; - auto& registry = - PerformanceEntryReporter::getInstance()->getObserverRegistry(); - auto observer = registry.createObserver(std::move(cb)); - + auto observer = std::make_shared(std::move(cb)); jsi::Object observerObj{rt}; observerObj.setNativeState(rt, observer); return observerObj; @@ -100,6 +97,10 @@ void NativePerformanceObserver::observe( {.buffered = buffered, .durationThreshold = durationThreshold}); } } + + auto& registry = + PerformanceEntryReporter::getInstance()->getObserverRegistry(); + registry.addObserver(observer); } void NativePerformanceObserver::disconnect( @@ -111,6 +112,7 @@ void NativePerformanceObserver::disconnect( if (!observer) { return; } + observerObj.setNativeState(rt, nullptr); auto& registry = diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 2eb9c205e6609e..3cdca5c489a1c6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -103,7 +103,7 @@ class PerformanceEntryReporter { std::string_view entryName = {}); private: - PerformanceObserverRegistry::Ptr observerRegistry_; + std::unique_ptr observerRegistry_; mutable std::mutex buffersMutex_; PerformanceEntryCircularBuffer eventBuffer_{EVENT_BUFFER_SIZE}; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 157af0025c2ff8..148f60697bb71b 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -61,9 +61,6 @@ struct PerformanceObserverObserveSingleOptions { */ class PerformanceObserver : public jsi::NativeState { public: - using Ptr = std::shared_ptr; - using WeakPtr = std::weak_ptr; - explicit PerformanceObserver(PerformanceObserverCallback&& callback) : callback_(std::move(callback)) {} diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 3538f6cf574e99..06373f77657b6a 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -9,35 +9,13 @@ namespace facebook::react { -PerformanceObserver::Ptr PerformanceObserverRegistry::createObserver( - PerformanceObserverCallback&& callback) { - // We allocate observer manually, and use `shared_ptr` deleter - // functionality to remove observer from registry - auto obj = new PerformanceObserver(std::move(callback)); - auto deleter = [&](PerformanceObserver* observer) -> void { - this->removeObserver(*obj); - delete observer; - }; - PerformanceObserver::Ptr ptr{obj, deleter}; - return ptr; -} - -void PerformanceObserverRegistry::addObserver( - const PerformanceObserver::WeakPtr& observer) { +void PerformanceObserverRegistry::addObserver(std::shared_ptr observer) { std::lock_guard guard(observersMutex_); observers_.insert(observer); } void PerformanceObserverRegistry::removeObserver( - const PerformanceObserver& observer) { - std::lock_guard guard(observersMutex_); - erase_if(observers_, [&](auto e) -> bool { - return !e.expired() && *e.lock() == observer; - }); -} - -void PerformanceObserverRegistry::removeObserver( - const PerformanceObserver::Ptr& observer) { + std::shared_ptr observer) { std::lock_guard guard(observersMutex_); observers_.erase(observer); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index fb118ed99e5402..8b90df7ab7ac07 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -23,28 +23,17 @@ namespace facebook::react { */ class PerformanceObserverRegistry { public: - using Ptr = std::unique_ptr; - using WeakPtr = std::weak_ptr; - PerformanceObserverRegistry() = default; /** - * Creates Performance Observer instance and registers it in the registry. - * - * Observer is automatically removed from the registry when it goes out of - * scope. You can manually remove it from the registry earlier by calling - * `removeObserver`. + * Adds observer to the registry. */ - PerformanceObserver::Ptr createObserver( - PerformanceObserverCallback&& callback); + void addObserver(std::shared_ptr observer); /** * Removes observer from the registry. - * - * It is called automatically for observers created by `createObserver` - * method. */ - void removeObserver(const PerformanceObserver::Ptr& observer); + void removeObserver(std::shared_ptr observer); /** * Delegates specified performance `entry` to all registered observers @@ -52,14 +41,11 @@ class PerformanceObserverRegistry { */ void queuePerformanceEntry(const PerformanceEntry& entry); - void addObserver(const PerformanceObserver::WeakPtr& observer); - void removeObserver(const PerformanceObserver& observer); - private: mutable std::mutex observersMutex_; std::set< - PerformanceObserver::WeakPtr, - std::owner_less> + std::weak_ptr, + std::owner_less>> observers_; }; From 8d78ccd71bbac01054e29400580eccd2a0b2714c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 19 Sep 2024 10:15:51 +0200 Subject: [PATCH 49/60] style: fix --- .../webperformance/NativePerformanceObserver.cpp | 10 ++++------ .../timeline/PerformanceEntryCircularBuffer.cpp | 3 ++- .../timeline/PerformanceEntryCircularBuffer.h | 5 ++--- .../performance/timeline/PerformanceEntryKeyedBuffer.h | 8 +++----- .../performance/timeline/PerformanceEntryReporter.cpp | 10 +++++++--- .../performance/timeline/PerformanceEntryReporter.h | 10 +++++++--- .../timeline/PerformanceObserverRegistry.cpp | 3 ++- 7 files changed, 27 insertions(+), 22 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 5b49196ac1fb24..57f59ac8b6e804 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -146,19 +146,17 @@ std::vector NativePerformanceObserver::getEntries( std::optional entryType, std::optional entryName) { const auto reporter = PerformanceEntryReporter::getInstance(); - + if (entryType.has_value()) { if (entryName.has_value()) { return reporter->getEntriesByName(entryName.value(), entryType.value()); - } - else { + } else { return reporter->getEntriesByType(entryType.value()); } - } - else if (entryName.has_value()) { + } else if (entryName.has_value()) { return reporter->getEntriesByName(entryName.value()); } - + return reporter->getEntries(); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp index 556127aa15c9a3..93dfef9b0195fd 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.cpp @@ -16,7 +16,8 @@ void PerformanceEntryCircularBuffer::add( } } -void PerformanceEntryCircularBuffer::getEntries(std::vector& target) const { +void PerformanceEntryCircularBuffer::getEntries( + std::vector& target) const { buffer_.getEntries(target); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h index ba5bc621ca2577..e9e4e54ace74c6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryCircularBuffer.h @@ -20,9 +20,8 @@ class PerformanceEntryCircularBuffer : public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; void getEntries(std::vector& target) const override; - void getEntries( - std::string_view name, - std::vector& target) const override; + void getEntries(std::string_view name, std::vector& target) + const override; void clear() override; void clear(std::string_view name) override; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h index 884847d43add45..30472cbfcdd323 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryKeyedBuffer.h @@ -19,12 +19,10 @@ class PerformanceEntryKeyedBuffer : public PerformanceEntryBuffer { void add(const PerformanceEntry& entry) override; - void getEntries( - std::vector& target) const override; + void getEntries(std::vector& target) const override; - void getEntries( - std::string_view name, - std::vector& target) const override; + void getEntries(std::string_view name, std::vector& target) + const override; void clear() override; void clear(std::string_view name) override; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp index db2d0657309844..fac6364e1019ac 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.cpp @@ -78,13 +78,15 @@ std::vector PerformanceEntryReporter::getEntries() const { return res; } -std::vector PerformanceEntryReporter::getEntriesByType(PerformanceEntryType entryType) const { +std::vector PerformanceEntryReporter::getEntriesByType( + PerformanceEntryType entryType) const { std::vector res; getBuffer(entryType).getEntries(res); return res; } -std::vector PerformanceEntryReporter::getEntriesByName(std::string_view entryName) const { +std::vector PerformanceEntryReporter::getEntriesByName( + std::string_view entryName) const { std::vector res; // Collect all entry types for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) { @@ -93,7 +95,9 @@ std::vector PerformanceEntryReporter::getEntriesByName(std::st return res; } -std::vector PerformanceEntryReporter::getEntriesByName(std::string_view entryName, PerformanceEntryType entryType) const { +std::vector PerformanceEntryReporter::getEntriesByName( + std::string_view entryName, + PerformanceEntryType entryType) const { std::vector res; getBuffer(entryType).getEntries(entryName, res); return res; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h index 3cdca5c489a1c6..bd151038152900 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceEntryReporter.h @@ -55,9 +55,13 @@ class PerformanceEntryReporter { // https://www.w3.org/TR/performance-timeline/#getentriesbytype-method // https://www.w3.org/TR/performance-timeline/#getentriesbyname-method std::vector getEntries() const; - std::vector getEntriesByType(PerformanceEntryType entryType) const; - std::vector getEntriesByName(std::string_view entryName) const; - std::vector getEntriesByName(std::string_view entryName, PerformanceEntryType entryType) const; + std::vector getEntriesByType( + PerformanceEntryType entryType) const; + std::vector getEntriesByName( + std::string_view entryName) const; + std::vector getEntriesByName( + std::string_view entryName, + PerformanceEntryType entryType) const; void logEventEntry( std::string name, diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index 06373f77657b6a..cd61870652f50e 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -9,7 +9,8 @@ namespace facebook::react { -void PerformanceObserverRegistry::addObserver(std::shared_ptr observer) { +void PerformanceObserverRegistry::addObserver( + std::shared_ptr observer) { std::lock_guard guard(observersMutex_); observers_.insert(observer); } From 56d04ab84672e62b7fb0b352650df0af86ede773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 19 Sep 2024 10:18:25 +0200 Subject: [PATCH 50/60] style: update comment --- .../react/performance/timeline/PerformanceObserver.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 148f60697bb71b..307d3eb79dcf42 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -51,13 +51,6 @@ struct PerformanceObserverObserveSingleOptions { * * Entries are pushed to the observer by the `PerformanceEntryReporter` class, * through the `PerformanceObserverRegistry` class which acts as a central hub. - * - * To create new performance observers, you should use - * `PerformanceObserverRegistry::createObserver` which creates the observer, - * registers it immediately and unregisters it when it goes out of scope. - * - * You can still manually create instance of `PerformanceObserver`, but then - * you need to manually register and unregister it from registry. */ class PerformanceObserver : public jsi::NativeState { public: From fb3197ce72147f2f66f7b2cb98b27fcc7350c914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 19 Sep 2024 10:38:45 +0200 Subject: [PATCH 51/60] chore: remove default max size from circular buffer --- .../ReactCommon/react/performance/timeline/CircularBuffer.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h index f4dbd5f4a02726..54c3a49b2dabf0 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h @@ -14,8 +14,6 @@ namespace facebook::react { -constexpr size_t DEFAULT_MAX_SIZE = 1024; - /** * A container for storing entries of type T, with the following properties: * - It can only grow up to a specified max size @@ -28,8 +26,7 @@ constexpr size_t DEFAULT_MAX_SIZE = 1024; template class CircularBuffer { public: - explicit CircularBuffer(size_t maxSize = DEFAULT_MAX_SIZE) - : maxSize_(maxSize) { + explicit CircularBuffer(size_t maxSize): maxSize_(maxSize) { entries_.reserve(maxSize_); } From 23802a465cf185f39000640ab455e4b06e626563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Thu, 19 Sep 2024 10:44:06 +0200 Subject: [PATCH 52/60] fix: optimize JS native call --- .../webapis/performance/PerformanceObserver.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 9164ff73ecd0af..acd03d67e16517 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -124,6 +124,7 @@ export class PerformanceObserver { #nativeObserverHandle: mixed | void; #callback: PerformanceObserverCallback; #type: 'single' | 'multiple' | void; + #calledAtLeastOnce = false; constructor(callback: PerformanceObserverCallback) { this.#callback = callback; @@ -153,7 +154,7 @@ export class PerformanceObserver { // The same observer may receive multiple calls to "observe", so we need // to check what is new on this call vs. previous ones. - NativePerformanceObserver.observe(this.#observerHandle, { + NativePerformanceObserver.observe(this.#nativeObserverHandle, { entryTypes, durationThreshold: options.type === 'event' ? options.durationThreshold : undefined, @@ -174,9 +175,14 @@ export class PerformanceObserver { } #createNativeObserver() { + this.#calledAtLeastOnce = false; return NativePerformanceObserver.createObserver(() => { - const entryList = new PerformanceObserverEntryList(NativePerformanceObserver.takeRecords(this.#observerHandle)); - const droppedEntriesCount = NativePerformanceObserver.getDroppedEntriesCount(this.#nativeObserverHandle); + const entryList = new PerformanceObserverEntryList(NativePerformanceObserver.takeRecords(this.#nativeObserverHandle)); + let droppedEntriesCount = 0; + if (!this.#calledAtLeastOnce) { + droppedEntriesCount = NativePerformanceObserver.getDroppedEntriesCount(this.#nativeObserverHandle); + this.#calledAtLeastOnce = true; + } this.#callback(entryList, this, { droppedEntriesCount }); }); } From 8fe46c216d91148887e0d7fa852357d7121be87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 20 Sep 2024 11:02:36 +0200 Subject: [PATCH 53/60] fix: tests --- .../webperformance/NativePerformance.cpp | 12 + .../webperformance/NativePerformance.h | 9 + .../NativePerformanceObserver.cpp | 24 +- .../NativePerformanceObserver.h | 1 + .../performance/timeline/CircularBuffer.h | 2 +- .../timeline/PerformanceObserver.cpp | 9 +- .../timeline/PerformanceObserver.h | 39 ++- .../timeline/PerformanceObserverRegistry.cpp | 1 + .../timeline/PerformanceObserverRegistry.h | 4 +- .../timeline/tests/CircularBufferTest.cpp | 2 +- .../tests/PerformanceEntryReporterTest.cpp | 104 +------ .../tests/PerformanceObserverTest.cpp | 198 +++++++++++++ .../webapis/performance/Performance.js | 12 +- .../performance/PerformanceObserver.js | 82 +++--- .../performance/__tests__/EventCounts-test.js | 88 ++---- .../__tests__/PerformanceObserver-test.js | 262 +----------------- .../performance/specs/NativePerformance.js | 8 + .../specs/NativePerformanceObserver.js | 10 +- .../specs/__mocks__/NativePerformance.js | 42 ++- .../__mocks__/NativePerformanceObserver.js | 102 +++---- .../__tests__/NativePerformanceMock-test.js | 22 +- .../NativePerformanceObserverMock-test.js | 63 +---- 22 files changed, 471 insertions(+), 625 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 2b933d897d0b4e..58bd66a49b6423 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -137,6 +137,18 @@ void NativePerformance::measure( #endif } +void NativePerformance::logEvent( + jsi::Runtime& rt, + std::string name, + double startTime, + double duration, + double processingStart, + double processingEnd, + double interactionId) { + PerformanceEntryReporter::getInstance()->logEventEntry( + name, startTime, duration, processingStart, processingEnd, interactionId); +} + std::unordered_map NativePerformance::getSimpleMemoryInfo( jsi::Runtime& rt) { auto heapInfo = rt.instrumentation().getHeapInfo(false); diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index fae3bf6c0dc4f9..73963c5fe228a6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -36,6 +36,15 @@ class NativePerformance : public NativePerformanceCxxSpec { std::optional startMark, std::optional endMark); + void logEvent( + jsi::Runtime& rt, + std::string name, + double startTime, + double duration, + double processingStart, + double processingEnd, + double interactionId); + // To align with web API, we will make sure to return three properties // (jsHeapSizeLimit, totalJSHeapSize, usedJSHeapSize) + anything needed from // the VM side. diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 57f59ac8b6e804..965689b5baf850 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -37,17 +37,14 @@ jsi::Object NativePerformanceObserver::createObserver( // the spec. The specification requires us to queue a single task that // dispatches observer callbacks. Instead, we are queuing all callbacks as // separate tasks in the scheduler. - PerformanceObserverCallback cb = [this, callback = std::move(callback)]( - PerformanceObserver& currentObserver) { - jsInvoker_->invokeAsync( - SchedulerPriority::IdlePriority, - [currentObserver, callback](jsi::Runtime& /*rt*/) mutable { - callback.call(); - currentObserver.flush(); - }); + PerformanceObserverCallback cb = [callback = std::move(callback)]() { + callback.callWithPriority(SchedulerPriority::IdlePriority); }; - auto observer = std::make_shared(std::move(cb)); + auto& registry = + PerformanceEntryReporter::getInstance()->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, std::move(cb)); jsi::Object observerObj{rt}; observerObj.setNativeState(rt, observer); return observerObj; @@ -97,10 +94,6 @@ void NativePerformanceObserver::observe( {.buffered = buffered, .durationThreshold = durationThreshold}); } } - - auto& registry = - PerformanceEntryReporter::getInstance()->getObserverRegistry(); - registry.addObserver(observer); } void NativePerformanceObserver::disconnect( @@ -114,10 +107,7 @@ void NativePerformanceObserver::disconnect( } observerObj.setNativeState(rt, nullptr); - - auto& registry = - PerformanceEntryReporter::getInstance()->getObserverRegistry(); - registry.removeObserver(observer); + observer->disconnect(); } std::vector NativePerformanceObserver::takeRecords( diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h index 89d80cdd782732..da92cae4e5d7a9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.h @@ -16,6 +16,7 @@ #endif #include +#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h index 54c3a49b2dabf0..b0fb5920a76a77 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/CircularBuffer.h @@ -26,7 +26,7 @@ namespace facebook::react { template class CircularBuffer { public: - explicit CircularBuffer(size_t maxSize): maxSize_(maxSize) { + explicit CircularBuffer(size_t maxSize) : maxSize_(maxSize) { entries_.reserve(maxSize_); } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp index 1b227cb391c366..9c5e8a74e041bf 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.cpp @@ -29,6 +29,7 @@ void PerformanceObserver::handleEntry(const PerformanceEntry& entry) { std::vector PerformanceObserver::takeRecords() { std::vector result; buffer_.swap(result); + flush(); return result; } @@ -51,6 +52,7 @@ void PerformanceObserver::observe( scheduleFlushBuffer(); } } + registry_.addObserver(shared_from_this()); } void PerformanceObserver::observe( @@ -59,6 +61,7 @@ void PerformanceObserver::observe( observedTypes_ = std::move(types); requiresDroppedEntries_ = false; durationThreshold_ = options.durationThreshold; + registry_.addObserver(shared_from_this()); } double PerformanceObserver::getDroppedEntriesCount() noexcept { @@ -77,6 +80,10 @@ double PerformanceObserver::getDroppedEntriesCount() noexcept { return droppedEntriesCount; } +void PerformanceObserver::disconnect() noexcept { + registry_.removeObserver(shared_from_this()); +} + void PerformanceObserver::flush() noexcept { didScheduleFlushBuffer = false; } @@ -89,7 +96,7 @@ void PerformanceObserver::scheduleFlushBuffer() { if (!didScheduleFlushBuffer) { didScheduleFlushBuffer = true; - callback_(*this); + callback_(); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h index 307d3eb79dcf42..e5c02fab15dcd4 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserver.h @@ -13,6 +13,7 @@ #include #include #include "PerformanceEntryBuffer.h" +#include "PerformanceObserverRegistry.h" namespace facebook::react { @@ -20,8 +21,7 @@ class PerformanceObserver; using PerformanceObserverEntryTypeFilter = std::unordered_set; -using PerformanceObserverCallback = - std::function; +using PerformanceObserverCallback = std::function; /** * Represents subset of spec's `PerformanceObserverInit` that is allowed for @@ -52,10 +52,27 @@ struct PerformanceObserverObserveSingleOptions { * Entries are pushed to the observer by the `PerformanceEntryReporter` class, * through the `PerformanceObserverRegistry` class which acts as a central hub. */ -class PerformanceObserver : public jsi::NativeState { +class PerformanceObserver + : public jsi::NativeState, + public std::enable_shared_from_this { + private: + struct PrivateUseCreateMethod { + explicit PrivateUseCreateMethod() = default; + }; + public: - explicit PerformanceObserver(PerformanceObserverCallback&& callback) - : callback_(std::move(callback)) {} + explicit PerformanceObserver( + PrivateUseCreateMethod, + PerformanceObserverRegistry& registry, + PerformanceObserverCallback&& callback) + : registry_(registry), callback_(std::move(callback)) {} + + static std::shared_ptr create( + PerformanceObserverRegistry& registry, + PerformanceObserverCallback&& callback) { + return std::make_shared( + PrivateUseCreateMethod(), registry, std::move(callback)); + } ~PerformanceObserver() = default; @@ -95,19 +112,21 @@ class PerformanceObserver : public jsi::NativeState { PerformanceObserverObserveMultipleOptions options = {}); /** - * Internal function called by JS bridge to get number of dropped entries - * count counted at call time. + * Disconnects observer from the registry */ - double getDroppedEntriesCount() noexcept; + void disconnect() noexcept; /** - * Called when the callback was dispatched + * Internal function called by JS bridge to get number of dropped entries + * count counted at call time. */ - void flush() noexcept; + double getDroppedEntriesCount() noexcept; private: void scheduleFlushBuffer(); + void flush() noexcept; + PerformanceObserverRegistry& registry_; PerformanceObserverCallback callback_; PerformanceObserverEntryTypeFilter observedTypes_; diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index cd61870652f50e..c1ce53881917eb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -6,6 +6,7 @@ */ #include "PerformanceObserverRegistry.h" +#include "PerformanceObserver.h" namespace facebook::react { diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index 8b90df7ab7ac07..52bd710b03cbcc 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -10,10 +10,12 @@ #include #include #include -#include "PerformanceObserver.h" +#include "PerformanceEntry.h" namespace facebook::react { +class PerformanceObserver; + /** * PerformanceObserverRegistry acts as a container for known performance * observer instances. diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp index 3dd59765f0ef6a..a198b98e6b0715 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/CircularBufferTest.cpp @@ -19,7 +19,7 @@ constexpr auto OK = false; constexpr auto OVERWRITE = true; TEST(CircularBuffer, CanAddAndRetrieveElements) { - CircularBuffer buffer; + CircularBuffer buffer{5}; ASSERT_EQ(OK, buffer.add(1)); ASSERT_EQ(OK, buffer.add(2)); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp index 65cb1a97887b25..eb9b1256e3b1fb 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceEntryReporterTest.cpp @@ -41,76 +41,19 @@ namespace facebook::react { using namespace facebook::react; -TEST(PerformanceEntryReporter, PerformanceEntryReporterTestStartReporting) { - auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); - reporter->clearEntries(); - - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - - ASSERT_TRUE(reporter->isReporting(PerformanceEntryType::MARK)); - ASSERT_TRUE(reporter->isReporting(PerformanceEntryType::MEASURE)); - - ASSERT_FALSE(reporter->isReporting(PerformanceEntryType::EVENT)); -} - -TEST(PerformanceEntryReporter, PerformanceEntryReporterTestStopReporting) { - auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); - reporter->clearEntries(); - - reporter->startReporting(PerformanceEntryType::MARK); - - reporter->mark("mark0", 0.0); - reporter->mark("mark1", 0.0); - reporter->mark("mark2", 0.0); - reporter->measure("measure0", 0.0, 0.0); - - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(3, entries.size()); - - res = reporter->popPendingEntries(); - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(0, res.entries.size()); - - reporter->stopReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - - reporter->mark("mark3"); - reporter->measure("measure1", 0.0, 0.0); - - res = reporter->popPendingEntries(); - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(1, res.entries.size()); - ASSERT_STREQ("measure1", res.entries[0].name.c_str()); -} - TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { auto reporter = PerformanceEntryReporter::getInstance(); - reporter->stopReporting(); reporter->clearEntries(); - reporter->startReporting(PerformanceEntryType::MARK); - reporter->mark("mark0", 0.0); reporter->mark("mark1", 1.0); reporter->mark("mark2", 2.0); // Report mark0 again reporter->mark("mark0", 3.0); - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; + const auto& entries = reporter->getEntries(); - ASSERT_EQ(0, res.droppedEntriesCount); ASSERT_EQ(4, entries.size()); const std::vector expected = { @@ -133,13 +76,8 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMarks) { TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); reporter->clearEntries(); - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - reporter->mark("mark0", 0.0); reporter->mark("mark1", 1.0); reporter->mark("mark2", 2.0); @@ -160,10 +98,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { // Uses the last reported time for mark4 reporter->measure("measure7", 0.0, 0.0, std::nullopt, "mark1", "mark4"); - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; - - ASSERT_EQ(0, res.droppedEntriesCount); + const auto& entries = reporter->getEntries(); const std::vector expected = { {.name = "mark0", @@ -244,18 +179,12 @@ static std::vector getTypes( TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); reporter->clearEntries(); - auto res = reporter->popPendingEntries(); - const auto& entries = res.entries; - - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(0, entries.size()); - - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); + { + const auto& entries = reporter->getEntries(); + ASSERT_EQ(0, entries.size()); + } reporter->mark("common_name", 0.0); reporter->mark("mark1", 1.0); @@ -267,17 +196,15 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { reporter->measure("measure3", 0.0, 0.0, 5.0, "mark1"); reporter->measure("measure4", 1.5, 0.0, std::nullopt, std::nullopt, "mark2"); - res = reporter->popPendingEntries(); - ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_EQ(8, res.entries.size()); - - reporter->getEntries(PerformanceEntryType::MARK); - const auto marks = reporter->getEntries(PerformanceEntryType::MARK); + { + const auto& entries = reporter->getEntries(); + ASSERT_EQ(8, entries.size()); + } - const auto measures = reporter->getEntries(PerformanceEntryType::MEASURE); - const auto common_name = reporter->getEntries(std::nullopt, "common_name"); + const auto marks = reporter->getEntriesByType(PerformanceEntryType::MARK); + const auto measures = reporter->getEntriesByType(PerformanceEntryType::MEASURE); + const auto common_name = reporter->getEntriesByName("common_name"); - reporter->getEntries(); const auto all = reporter->getEntries(); ASSERT_EQ( @@ -320,13 +247,8 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) { TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearEntries) { auto reporter = PerformanceEntryReporter::getInstance(); - - reporter->stopReporting(); reporter->clearEntries(); - reporter->startReporting(PerformanceEntryType::MARK); - reporter->startReporting(PerformanceEntryType::MEASURE); - reporter->mark("common_name", 0.0); reporter->mark("mark1", 1.0); reporter->mark("mark2", 2.0); diff --git a/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp new file mode 100644 index 00000000000000..acb4a1312e65bf --- /dev/null +++ b/packages/react-native/ReactCommon/react/performance/timeline/tests/PerformanceObserverTest.cpp @@ -0,0 +1,198 @@ +/* +* Copyright (c) Meta Platforms, Inc. and affiliates. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +#include +#include + +#include "../PerformanceEntryReporter.h" +#include "../PerformanceObserver.h" + +namespace facebook::react { + +[[maybe_unused]] static bool operator==( + const PerformanceEntry& lhs, + const PerformanceEntry& rhs) { + return lhs.name == rhs.name && lhs.entryType == rhs.entryType && + lhs.startTime == rhs.startTime && lhs.duration == rhs.duration && + lhs.processingStart == rhs.processingStart && + lhs.processingEnd == rhs.processingEnd && + lhs.interactionId == rhs.interactionId; +} +} // namespace facebook::react + + using namespace facebook::react; + +TEST(PerformanceObserver, PerformanceObserverTestObserveFlushes) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + bool in = false; + auto observer = PerformanceObserver::create(registry, [&]() { in = true; }); + observer->observe(PerformanceEntryType::MARK); + + // buffer is empty + ASSERT_FALSE(in); + + registry.queuePerformanceEntry({ .name = "test", .entryType = PerformanceEntryType::MARK, .startTime = 10, .duration = 10 }); + ASSERT_TRUE(in); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilteredSingle) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe(PerformanceEntryType::MEASURE); + registry.queuePerformanceEntry({ .name = "test", .entryType = PerformanceEntryType::MARK, .startTime = 10, .duration = 10 }); + + // wrong type + ASSERT_EQ(observer->takeRecords().size(), 0); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilterMulti) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto called = false; + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe({ PerformanceEntryType::MEASURE, PerformanceEntryType::MARK }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test1", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test2", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "off3", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + + ASSERT_EQ(observer->takeRecords().size(), 2); + ASSERT_FALSE(called); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilterSingleCallbackNotCalled) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto called = false; + auto observer = PerformanceObserver::create(registry, [&]() { called = true; }); + observer->observe(PerformanceEntryType::MEASURE); + registry.queuePerformanceEntry({ .name = "test", .entryType = PerformanceEntryType::MARK, .startTime = 10, .duration = 10 }); + + ASSERT_FALSE(called); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestFilterMultiCallbackNotCalled) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto called = false; + auto observer = PerformanceObserver::create(registry, [&]() { called = true; }); + observer->observe({ PerformanceEntryType::MEASURE, PerformanceEntryType::MARK }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test1", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "test2", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + registry.queuePerformanceEntry(PerformanceEntry { .name = "off3", .entryType = PerformanceEntryType::EVENT, .startTime = 10, .duration = 10 }); + + ASSERT_FALSE(called); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe(PerformanceEntryType::MARK); + registry.queuePerformanceEntry({ .name = "test1", .entryType = PerformanceEntryType::MARK, .startTime = 10 }); + registry.queuePerformanceEntry({ .name = "off", .entryType = PerformanceEntryType::MEASURE, .startTime = 10 }); + registry.queuePerformanceEntry({ .name = "test2", .entryType = PerformanceEntryType::MARK, .startTime = 20 }); + registry.queuePerformanceEntry({ .name = "test3", .entryType = PerformanceEntryType::MARK, .startTime = 30 }); + + const std::vector expected = { + { .name = "test1", .entryType = PerformanceEntryType::MARK, .startTime = 10 }, + { .name = "test2", .entryType = PerformanceEntryType::MARK, .startTime = 20 }, + { .name = "test3", .entryType = PerformanceEntryType::MARK, .startTime = 30 }, + }; + + ASSERT_EQ(expected, observer->takeRecords()); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestObserveDurationThreshold) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer = PerformanceObserver::create(registry, [&]() {}); + observer->observe(PerformanceEntryType::EVENT, { .durationThreshold = 50 }); + registry.queuePerformanceEntry({ .name = "test1", .entryType = PerformanceEntryType::EVENT, .duration = 50 }); + registry.queuePerformanceEntry({ .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "off1", .entryType = PerformanceEntryType::EVENT, .duration = 40 }); + registry.queuePerformanceEntry({ .name = "off2", .entryType = PerformanceEntryType::MARK, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }); + + const std::vector expected = { + { .name = "test1", .entryType = PerformanceEntryType::EVENT, .duration = 50 }, + { .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }, + { .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }, + }; + + ASSERT_EQ(expected, observer->takeRecords()); + + observer->disconnect(); +} + +TEST(PerformanceObserver, PerformanceObserverTestMultiple) { + auto reporter = PerformanceEntryReporter::getInstance(); + reporter->clearEntries(); + + auto& registry = reporter->getObserverRegistry(); + + auto observer1 = PerformanceObserver::create(registry, [&]() {}); + auto observer2 = PerformanceObserver::create(registry, [&]() {}); + observer1->observe(PerformanceEntryType::EVENT, { .durationThreshold = 50 }); + observer2->observe(PerformanceEntryType::EVENT, { .durationThreshold = 80 }); + + registry.queuePerformanceEntry({ .name = "test1", .entryType = PerformanceEntryType::MEASURE, .duration = 50 }); + registry.queuePerformanceEntry({ .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "off1", .entryType = PerformanceEntryType::EVENT, .duration = 40 }); + registry.queuePerformanceEntry({ .name = "off2", .entryType = PerformanceEntryType::MARK, .duration = 100 }); + registry.queuePerformanceEntry({ .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }); + + const std::vector expected1 = { + { .name = "test1", .entryType = PerformanceEntryType::EVENT, .duration = 50 }, + { .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 }, + { .name = "test3", .entryType = PerformanceEntryType::EVENT, .duration = 60 }, + }; + + const std::vector expected2 = { + { .name = "test2", .entryType = PerformanceEntryType::EVENT, .duration = 100 } + }; + + ASSERT_EQ(expected1, observer1->takeRecords()); + ASSERT_EQ(expected2, observer2->takeRecords()); + + observer1->disconnect(); + observer2->disconnect(); +} \ No newline at end of file diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 5861d3582803b8..7e4dd07f73d1b0 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -40,16 +40,6 @@ declare var global: { const getCurrentTimeStamp: () => DOMHighResTimeStamp = NativePerformance?.now ?? global.nativePerformanceNow ?? (() => Date.now()); -// We want some of the performance entry types to be always logged, -// even if they are not currently observed - this is either to be able to -// retrieve them at any time via Performance.getEntries* or to refer by other entries -// (such as when measures may refer to marks, even if the latter are not observed) -if (NativePerformanceObserver?.setIsBuffered) { - NativePerformanceObserver?.setIsBuffered( - ALWAYS_LOGGED_ENTRY_TYPES.map(performanceEntryTypeToRaw), - true, - ); -} function warnNoNativePerformance() { warnOnce( @@ -145,7 +135,7 @@ export default class Performance { return; } - NativePerformanceObserver?.clearEntries( + NativePerformanceObserver.clearEntries( RawPerformanceEntryTypeValues.MARK, markName, ); diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index acd03d67e16517..811e5760257c32 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -22,6 +22,7 @@ import { rawToPerformanceEntryType, } from './RawPerformanceEntry'; import NativePerformanceObserver from './specs/NativePerformanceObserver'; +import type { OpaqueNativeObserverHandle } from './specs/NativePerformanceObserver'; export type PerformanceEntryList = $ReadOnlyArray; @@ -56,27 +57,23 @@ export class PerformanceObserverEntryList { } } +export type PerformanceObserverCallbackOptions = { + droppedEntriesCount: number, +}; + export type PerformanceObserverCallback = ( list: PerformanceObserverEntryList, observer: PerformanceObserver, // The number of buffered entries which got dropped from the buffer due to the buffer being full: - droppedEntryCount?: number, + options?: PerformanceObserverCallbackOptions ) => void; -export type PerformanceObserverInit = - | { - entryTypes: Array, - } - | { - type: PerformanceEntryType, - durationThreshold?: DOMHighResTimeStamp, - }; - -type PerformanceObserverConfig = {| - callback: PerformanceObserverCallback, - entryTypes: $ReadOnlySet, - durationThreshold: ?number, -|}; +export type PerformanceObserverInit = { + entryTypes?: Array, + type?: PerformanceEntryType, + buffered?: boolean, + durationThreshold?: DOMHighResTimeStamp, +}; export function warnNoNativePerformanceObserver() { warnOnce( @@ -121,7 +118,7 @@ function getSupportedPerformanceEntryTypes(): $ReadOnlyArray { - const entryList = new PerformanceObserverEntryList(NativePerformanceObserver.takeRecords(this.#nativeObserverHandle)); + + return NativePerformanceObserver.createObserver(() => { // $FlowNotNull + const entries = NativePerformanceObserver + .takeRecords(this.#nativeObserverHandle) + .map(rawToPerformanceEntry); + const entryList = new PerformanceObserverEntryList(entries); + let droppedEntriesCount = 0; if (!this.#calledAtLeastOnce) { droppedEntriesCount = NativePerformanceObserver.getDroppedEntriesCount(this.#nativeObserverHandle); this.#calledAtLeastOnce = true; } + this.#callback(entryList, this, { droppedEntriesCount }); }); } diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js index 3e7a951686600d..057af0229a3f39 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js @@ -9,13 +9,16 @@ * @oncall react_native */ -import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry'; - // NOTE: Jest mocks of transitive dependencies don't appear to work with // ES6 module imports, therefore forced to use commonjs style imports here. const Performance = require('../Performance').default; -const NativePerformanceObserverMock = - require('../specs/__mocks__/NativePerformanceObserver').default; +const NativePerformanceMock = + require('../specs/__mocks__/NativePerformance').default; + +jest.mock( + '../specs/NativePerformance', + () => require('../specs/__mocks__/NativePerformance').default, +); jest.mock( '../specs/NativePerformanceObserver', @@ -29,41 +32,20 @@ describe('EventCounts', () => { }); it('consistently implements the API for EventCounts', async () => { - const eventDefaultValues = { - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 100, - }; - - NativePerformanceObserverMock.logRawEntry({ - name: 'click', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'input', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'input', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); + let interactionId = 0; + const eventDefaultValues = [ + 0, // startTime + 100, // duration + 0, // processing start + 100, // processingEnd + ]; + + NativePerformanceMock?.logEvent('click', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); const eventCounts = new Performance().eventCounts; expect(eventCounts.size).toBe(3); @@ -89,35 +71,17 @@ describe('EventCounts', () => { expect(Array.from(eventCounts.values())).toStrictEqual([1, 2, 3]); await jest.runAllTicks(); - NativePerformanceObserverMock.logRawEntry({ - name: 'input', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); + NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); expect(Array.from(eventCounts.values())).toStrictEqual([1, 3, 5]); await jest.runAllTicks(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'click', - ...eventDefaultValues, - }); + NativePerformanceMock?.logEvent('click', ...eventDefaultValues, interactionId++); await jest.runAllTicks(); - NativePerformanceObserverMock.logRawEntry({ - name: 'keyup', - ...eventDefaultValues, - }); + NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); expect(Array.from(eventCounts.values())).toStrictEqual([2, 3, 6]); }); diff --git a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js index 1c4bc1d60d992a..4c26bc0ca0431b 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/PerformanceObserver-test.js @@ -11,50 +11,23 @@ import type {PerformanceEntryList} from '../PerformanceObserver'; -import {RawPerformanceEntryTypeValues} from '../RawPerformanceEntry'; - -// NOTE: Jest mocks of transitive dependencies don't appear to work with -// ES6 module imports, therefore forced to use commonjs style imports here. -const {PerformanceObserver} = require('../PerformanceObserver'); -const NativePerformanceObserverMock = - require('../specs/__mocks__/NativePerformanceObserver').default; -const NativePerformanceObserver = require('../specs/NativePerformanceObserver'); +jest.mock( + '../specs/NativePerformance', + () => require('../specs/__mocks__/NativePerformance').default, +); jest.mock( '../specs/NativePerformanceObserver', () => require('../specs/__mocks__/NativePerformanceObserver').default, ); -describe('PerformanceObserver', () => { - it('can be mocked by a reference NativePerformanceObserver implementation', async () => { - expect(NativePerformanceObserver).not.toBe(undefined); - - let totalEntries = 0; - const observer: PerformanceObserver = new PerformanceObserver( - (list, _observer) => { - expect(_observer).toBe(observer); - const entries = list.getEntries(); - expect(entries).toHaveLength(1); - const entry = entries[0]; - expect(entry.name).toBe('mark1'); - expect(entry.entryType).toBe('mark'); - totalEntries += entries.length; - }, - ); - expect(() => observer.observe({entryTypes: ['mark']})).not.toThrow(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 0, - }); - - await jest.runAllTicks(); - expect(totalEntries).toBe(1); - observer.disconnect(); - }); +// // NOTE: Jest mocks of transitive dependencies don't appear to work with +// // ES6 module imports, therefore forced to use commonjs style imports here. +const {PerformanceObserver} = require('../PerformanceObserver'); +const NativePerformanceMock = + require('../specs/__mocks__/NativePerformance').default; +describe('PerformanceObserver', () => { it('prevents durationThreshold to be used together with entryTypes', async () => { const observer = new PerformanceObserver((list, _observer) => {}); @@ -73,223 +46,10 @@ describe('PerformanceObserver', () => { observer.observe({type: 'measure', durationThreshold: 100}); - NativePerformanceObserverMock.logRawEntry({ - name: 'measure1', - entryType: RawPerformanceEntryTypeValues.MEASURE, - startTime: 0, - duration: 200, - }); + NativePerformanceMock?.measure('measure1', 0, 200); await jest.runAllTicks(); expect(entries).toHaveLength(1); expect(entries.map(e => e.name)).toStrictEqual(['measure1']); }); - - it('handles durationThreshold argument as expected', async () => { - let entries: PerformanceEntryList = []; - const observer = new PerformanceObserver((list, _observer) => { - entries = [...entries, ...list.getEntries()]; - }); - - observer.observe({type: 'event', durationThreshold: 100}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event2', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 20, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event3', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 100, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event4', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 500, - }); - - await jest.runAllTicks(); - expect(entries).toHaveLength(3); - expect(entries.map(e => e.name)).toStrictEqual([ - 'event1', - 'event3', - 'event4', - ]); - }); - - it('correctly works with multiple PerformanceObservers with durationThreshold', async () => { - let entries1: PerformanceEntryList = []; - const observer1 = new PerformanceObserver((list, _observer) => { - entries1 = [...entries1, ...list.getEntries()]; - }); - - let entries2: PerformanceEntryList = []; - const observer2 = new PerformanceObserver((list, _observer) => { - entries2 = [...entries2, ...list.getEntries()]; - }); - - let entries3: PerformanceEntryList = []; - const observer3 = new PerformanceObserver((list, _observer) => { - entries3 = [...entries3, ...list.getEntries()]; - }); - - let entries4: PerformanceEntryList = []; - const observer4 = new PerformanceObserver((list, _observer) => { - entries4 = [...entries4, ...list.getEntries()]; - }); - - observer2.observe({type: 'event', durationThreshold: 200}); - observer1.observe({type: 'event', durationThreshold: 100}); - observer3.observe({type: 'event', durationThreshold: 300}); - observer3.observe({type: 'event', durationThreshold: 500}); - observer4.observe({entryTypes: ['event']}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event2', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 20, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event3', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 100, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event4', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 500, - }); - - await jest.runAllTicks(); - observer1.disconnect(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event5', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event6', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 300, - }); - - await jest.runAllTicks(); - observer3.disconnect(); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event7', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 200, - }); - - await jest.runAllTicks(); - observer4.disconnect(); - - expect(entries1.map(e => e.name)).toStrictEqual([ - 'event1', - 'event3', - 'event4', - ]); - expect(entries2.map(e => e.name)).toStrictEqual([ - 'event1', - 'event4', - 'event5', - 'event6', - 'event7', - ]); - expect(entries3.map(e => e.name)).toStrictEqual(['event4', 'event6']); - expect(entries4.map(e => e.name)).toStrictEqual([ - 'event1', - 'event2', - 'event3', - 'event4', - 'event5', - 'event6', - 'event7', - ]); - }); - - it('should guard against errors in observer callbacks', () => { - jest.spyOn(console, 'error').mockImplementation(() => {}); - - const observer1Callback = jest.fn((_entries, _observer, _options) => { - throw new Error('observer 1 callback'); - }); - const observer1 = new PerformanceObserver(observer1Callback); - - const observer2Callback = jest.fn(); - const observer2 = new PerformanceObserver(observer2Callback); - - observer1.observe({type: 'mark'}); - observer2.observe({type: 'mark'}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 200, - }); - - jest.runAllTicks(); - - expect(observer1Callback).toHaveBeenCalled(); - expect(observer2Callback).toHaveBeenCalled(); - - expect(console.error).toHaveBeenCalledWith( - new Error('observer 1 callback'), - ); - }); - - it('should not invoke observers with non-matching entries', () => { - const observer1Callback = jest.fn(); - const observer1 = new PerformanceObserver(observer1Callback); - - const observer2Callback = jest.fn(); - const observer2 = new PerformanceObserver(observer2Callback); - - observer1.observe({type: 'mark'}); - observer2.observe({type: 'measure'}); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 200, - }); - - jest.runAllTicks(); - - expect(observer1Callback).toHaveBeenCalled(); - expect(observer2Callback).not.toHaveBeenCalled(); - }); }); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js index b05f2e74e4e1e6..db1dc5ac442a50 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -27,6 +27,14 @@ export interface Spec extends TurboModule { startMark?: string, endMark?: string, ) => void; + +logEvent: ( + name: string, + startTime: number, + duration: number, + processingStart: number, + processingEnd: number, + interactionId: number, + ) => void; +getSimpleMemoryInfo: () => NativeMemoryInfo; +getReactNativeStartupTiming: () => ReactNativeStartupTiming; } diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index c1a9cb8411d620..7354d1ef496380 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -35,10 +35,10 @@ export type GetPendingEntriesResult = {| |}; export type PerformanceObserverInit = { - entryTypes: $ReadOnlyArray; - type: number; - buffered: boolean; - durationThreshold: number; + entryTypes?: $ReadOnlyArray; + type?: number; + buffered?: boolean; + durationThreshold?: number; }; export interface Spec extends TurboModule { @@ -49,7 +49,7 @@ export interface Spec extends TurboModule { +observe: (observer: OpaqueNativeObserverHandle, options: PerformanceObserverInit) => void; +disconnect: (observer: OpaqueNativeObserverHandle) => void; +takeRecords: (observer: OpaqueNativeObserverHandle) => $ReadOnlyArray; - + +clearEntries: ( entryType?: RawPerformanceEntryType, entryName?: string, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js index b1e7072584fbf8..476a5abd5ed522 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js @@ -11,23 +11,24 @@ import type { NativeMemoryInfo, ReactNativeStartupTiming, - Spec as NativePerformance, } from '../NativePerformance'; import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; -import NativePerformanceObserver from '../NativePerformanceObserver'; +import NativePerformance from '../NativePerformance'; +import { logMockEntry } from './NativePerformanceObserver'; const marks: Map = new Map(); -const NativePerformanceMock: NativePerformance = { +const NativePerformanceMock: typeof NativePerformance = { mark: (name: string, startTime: number): void => { - NativePerformanceObserver?.logRawEntry({ - name, + NativePerformance?.mark(name, startTime); + marks.set(name, startTime); + logMockEntry({ entryType: RawPerformanceEntryTypeValues.MARK, + name, startTime, duration: 0, }); - marks.set(name, startTime); }, measure: ( @@ -40,11 +41,32 @@ const NativePerformanceMock: NativePerformance = { ): void => { const start = startMark != null ? marks.get(startMark) ?? 0 : startTime; const end = endMark != null ? marks.get(endMark) ?? 0 : endTime; - NativePerformanceObserver?.logRawEntry({ - name, + NativePerformance?.measure(name, start, end); + logMockEntry({ entryType: RawPerformanceEntryTypeValues.MEASURE, - startTime: start, - duration: duration ?? (end ? end - start : 0), + name, + startTime, + duration: duration ?? 0, + }); + }, + + logEvent: ( + name: string, + startTime: number, + duration: number, + processingStart: number, + processingEnd: number, + interactionId: number, + ): void => { + NativePerformance?.logEvent(name, startTime, duration, processingStart, processingEnd, interactionId); + logMockEntry({ + entryType: RawPerformanceEntryTypeValues.EVENT, + name, + startTime, + duration: duration ?? 0, + processingStart, + processingEnd, + interactionId, }); }, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js index 1104198efbfb7f..38c9e643a23840 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js @@ -9,90 +9,72 @@ */ import type { - GetPendingEntriesResult, + NativeBatchedObserverCallback, RawPerformanceEntry, RawPerformanceEntryType, + OpaqueNativeObserverHandle, + PerformanceObserverInit, Spec as NativePerformanceObserver, } from '../NativePerformanceObserver'; import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; -const reportingType: Set = new Set(); -const isAlwaysLogged: Set = new Set(); const eventCounts: Map = new Map(); -const durationThresholds: Map = new Map(); +const observers = new WeakSet(); let entries: Array = []; -let onPerformanceEntryCallback: ?() => void; -const NativePerformanceObserverMock: NativePerformanceObserver = { - startReporting: (entryType: RawPerformanceEntryType) => { - reportingType.add(entryType); - }, +export function logMockEntry(entry: RawPerformanceEntry) { + entries.push(entry); +} - stopReporting: (entryType: RawPerformanceEntryType) => { - reportingType.delete(entryType); - durationThresholds.delete(entryType); - }, +type MockObserver = { + callback: NativeBatchedObserverCallback, + entries: Array, + options: PerformanceObserverInit, + droppedEntriesCount: number +}; - setIsBuffered: ( - entryTypes: $ReadOnlyArray, - isBuffered: boolean, - ) => { - for (const entryType of entryTypes) { - if (isBuffered) { - isAlwaysLogged.add(entryType); - } else { - isAlwaysLogged.delete(entryType); - } - } +const NativePerformanceObserverMock: NativePerformanceObserver = { + getEventCounts: (): $ReadOnlyArray<[string, number]> => { + return Array.from(eventCounts.entries()); }, - popPendingEntries: (): GetPendingEntriesResult => { - const res = entries; - entries = []; - return { + createObserver: (callback: NativeBatchedObserverCallback): OpaqueNativeObserverHandle => { + const observer: MockObserver = { + callback, + entries: [], + options: {}, droppedEntriesCount: 0, - entries: res, }; + + return observer; }, - setOnPerformanceEntryCallback: (callback?: () => void) => { - onPerformanceEntryCallback = callback; + getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle): number => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + return mockObserver.droppedEntriesCount; }, - logRawEntry: (entry: RawPerformanceEntry) => { - if ( - reportingType.has(entry.entryType) || - isAlwaysLogged.has(entry.entryType) - ) { - const durationThreshold = durationThresholds.get(entry.entryType); - if ( - durationThreshold !== undefined && - entry.duration < durationThreshold - ) { - return; - } - entries.push(entry); - // $FlowFixMe[incompatible-call] - global.queueMicrotask(() => { - // We want to emulate the way it's done in native (i.e. async/batched) - onPerformanceEntryCallback?.(); - }); - } - if (entry.entryType === RawPerformanceEntryTypeValues.EVENT) { - eventCounts.set(entry.name, (eventCounts.get(entry.name) ?? 0) + 1); - } + observe: (observer: OpaqueNativeObserverHandle, options: PerformanceObserverInit): void => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + mockObserver.options = options; + observers.add(mockObserver); }, - getEventCounts: (): $ReadOnlyArray<[string, number]> => { - return Array.from(eventCounts.entries()); + disconnect: (observer: OpaqueNativeObserverHandle): void => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + observers.delete(mockObserver); }, - setDurationThreshold: ( - entryType: RawPerformanceEntryType, - durationThreshold: number, - ) => { - durationThresholds.set(entryType, durationThreshold); + takeRecords: (observer: OpaqueNativeObserverHandle): $ReadOnlyArray => { + // $FlowFixMe + const mockObserver = (observer: any) as MockObserver; + const observerEntries = mockObserver.entries; + mockObserver.entries = []; + return observerEntries; }, clearEntries: (entryType?: RawPerformanceEntryType, entryName?: string) => { diff --git a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js index be57b29e2bb7bc..be7ac4eec5f32d 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js +++ b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js @@ -29,9 +29,9 @@ describe('NativePerformanceMock', () => { observer.observe({type: 'mark'}); - NativePerformanceMock.mark('mark1', 0); - NativePerformanceMock.mark('mark2', 5); - NativePerformanceMock.mark('mark3', 10); + NativePerformanceMock?.mark('mark1', 0); + NativePerformanceMock?.mark('mark2', 5); + NativePerformanceMock?.mark('mark3', 10); await jest.runAllTicks(); expect(entries).toHaveLength(3); @@ -47,13 +47,13 @@ describe('NativePerformanceMock', () => { observer.observe({entryTypes: ['measure']}); - NativePerformanceMock.mark('mark0', 0.0); - NativePerformanceMock.mark('mark1', 1.0); - NativePerformanceMock.mark('mark2', 2.0); + NativePerformanceMock?.mark('mark0', 0.0); + NativePerformanceMock?.mark('mark1', 1.0); + NativePerformanceMock?.mark('mark2', 2.0); - NativePerformanceMock.measure('measure0', 0, 2); - NativePerformanceMock.measure('measure1', 0, 2, 4); - NativePerformanceMock.measure( + NativePerformanceMock?.measure('measure0', 0, 2); + NativePerformanceMock?.measure('measure1', 0, 2, 4); + NativePerformanceMock?.measure( 'measure2', 0, 0, @@ -61,8 +61,8 @@ describe('NativePerformanceMock', () => { 'mark1', 'mark2', ); - NativePerformanceMock.measure('measure3', 0, 0, 5, 'mark1'); - NativePerformanceMock.measure( + NativePerformanceMock?.measure('measure3', 0, 0, 5, 'mark1'); + NativePerformanceMock?.measure( 'measure4', 1.5, 0, diff --git a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js index 3e3205ce115235..6147c17bbc78a7 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js +++ b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js @@ -9,71 +9,20 @@ * @oncall react_native */ -import NativePerformanceObserverMock from '../__mocks__/NativePerformanceObserver'; +import NativePerformanceObserverMock, { logMockEntry } from '../__mocks__/NativePerformanceObserver'; import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; describe('NativePerformanceObserver', () => { - it('correctly starts and stops listening to entries in a nominal scenario', async () => { - NativePerformanceObserverMock.startReporting( - RawPerformanceEntryTypeValues.MARK, - ); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark1', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 10, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'mark2', - entryType: RawPerformanceEntryTypeValues.MARK, - startTime: 0, - duration: 20, - }); - - NativePerformanceObserverMock.logRawEntry({ - name: 'event1', - entryType: RawPerformanceEntryTypeValues.EVENT, - startTime: 0, - duration: 20, - }); - - const entriesResult = NativePerformanceObserverMock.popPendingEntries(); - expect(entriesResult).not.toBe(undefined); - const entries = entriesResult.entries; - - expect(entries.length).toBe(2); - expect(entries[0].name).toBe('mark1'); - expect(entries[1].name).toBe('mark2'); - - const entriesResult1 = NativePerformanceObserverMock.popPendingEntries(); - expect(entriesResult1).not.toBe(undefined); - const entries1 = entriesResult1.entries; - expect(entries1.length).toBe(0); - - NativePerformanceObserverMock.stopReporting( - RawPerformanceEntryTypeValues.MARK, - ); - }); - it('correctly clears/gets entries', async () => { - NativePerformanceObserverMock.startReporting( - RawPerformanceEntryTypeValues.MARK, - ); - - NativePerformanceObserverMock.startReporting( - RawPerformanceEntryTypeValues.EVENT, - ); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'mark1', entryType: RawPerformanceEntryTypeValues.MARK, startTime: 0, duration: 0, }); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'event1', entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, @@ -84,21 +33,21 @@ describe('NativePerformanceObserver', () => { expect(NativePerformanceObserverMock.getEntries()).toStrictEqual([]); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'entry1', entryType: RawPerformanceEntryTypeValues.MARK, startTime: 0, duration: 0, }); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'entry2', entryType: RawPerformanceEntryTypeValues.MARK, startTime: 0, duration: 0, }); - NativePerformanceObserverMock.logRawEntry({ + logMockEntry({ name: 'entry1', entryType: RawPerformanceEntryTypeValues.EVENT, startTime: 0, From 4f4f6b39b0a7f9be0f0f1f404c03d1584b28a7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 20 Sep 2024 12:13:41 +0200 Subject: [PATCH 54/60] fix: tests --- .../performance/PerformanceObserver.js | 8 ++-- .../performance/__tests__/EventCounts-test.js | 12 +++--- .../specs/__mocks__/NativePerformance.js | 4 +- .../__mocks__/NativePerformanceObserver.js | 41 +++++++++++++++++-- .../__tests__/NativePerformanceMock-test.js | 10 ++--- .../NativePerformanceObserverMock-test.js | 5 ++- 6 files changed, 59 insertions(+), 21 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 811e5760257c32..760864f6dbd0a6 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -135,7 +135,7 @@ export class PerformanceObserver { this.#validateObserveOptions(options); - if (this.#nativeObserverHandle != null) { + if (this.#nativeObserverHandle == null) { this.#nativeObserverHandle = this.#createNativeObserver(); } @@ -182,9 +182,9 @@ export class PerformanceObserver { this.#calledAtLeastOnce = false; return NativePerformanceObserver.createObserver(() => { // $FlowNotNull - const entries = NativePerformanceObserver - .takeRecords(this.#nativeObserverHandle) - .map(rawToPerformanceEntry); + let entries = NativePerformanceObserver + .takeRecords(this.#nativeObserverHandle); + entries = entries.map(rawToPerformanceEntry); const entryList = new PerformanceObserverEntryList(entries); let droppedEntriesCount = 0; diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js index 057af0229a3f39..4dc95e33bd272f 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js @@ -9,12 +9,6 @@ * @oncall react_native */ -// 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 NativePerformanceMock = - require('../specs/__mocks__/NativePerformance').default; - jest.mock( '../specs/NativePerformance', () => require('../specs/__mocks__/NativePerformance').default, @@ -25,6 +19,12 @@ jest.mock( () => 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 NativePerformanceMock = + require('../specs/__mocks__/NativePerformance').default; + describe('EventCounts', () => { it('defines EventCounts for Performance', () => { const eventCounts = new Performance().eventCounts; diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js index 476a5abd5ed522..1d89e90777fb81 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js @@ -45,8 +45,8 @@ const NativePerformanceMock: typeof NativePerformance = { logMockEntry({ entryType: RawPerformanceEntryTypeValues.MEASURE, name, - startTime, - duration: duration ?? 0, + startTime: start, + duration: duration ?? end - start, }); }, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js index 38c9e643a23840..14d57dee5de1bd 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js @@ -19,12 +19,47 @@ import type { import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; +jest.mock( + '../NativePerformance', + () => require('../__mocks__/NativePerformance').default, +); + +jest.mock( + '../NativePerformanceObserver', + () => require('../__mocks__/NativePerformanceObserver').default, +); + const eventCounts: Map = new Map(); -const observers = new WeakSet(); +let observers: MockObserver[] = []; let entries: Array = []; export function logMockEntry(entry: RawPerformanceEntry) { entries.push(entry); + + if (entry.entryType == RawPerformanceEntryTypeValues.EVENT) { + eventCounts.set(entry.name, (eventCounts.get(entry.name) ?? 0) + 1); + } + + for (const observer of observers) { + if (observer.options.type != entry.entryType && + !observer.options.entryTypes?.includes(entry.entryType)) { + continue; + } + + if (entry.entryType == RawPerformanceEntryTypeValues.EVENT) { + if (observer.options.durationThreshold > 0 && entry.duration < observer.options.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 = { @@ -60,13 +95,13 @@ const NativePerformanceObserverMock: NativePerformanceObserver = { // $FlowFixMe const mockObserver = (observer: any) as MockObserver; mockObserver.options = options; - observers.add(mockObserver); + observers.push(mockObserver); }, disconnect: (observer: OpaqueNativeObserverHandle): void => { // $FlowFixMe const mockObserver = (observer: any) as MockObserver; - observers.delete(mockObserver); + observers = observers.filter(e => e !== mockObserver); }, takeRecords: (observer: OpaqueNativeObserverHandle): $ReadOnlyArray => { diff --git a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js index be7ac4eec5f32d..ac41cbc9b48ba1 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js +++ b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceMock-test.js @@ -11,16 +11,16 @@ 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', () => { - jest.mock( - '../NativePerformanceObserver', - () => require('../__mocks__/NativePerformanceObserver').default, - ); - it('marks get reported', async () => { let entries: PerformanceEntryList = []; const observer = new PerformanceObserver((list, _observer) => { diff --git a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js index 6147c17bbc78a7..e57ddee505d9f5 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js +++ b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js @@ -14,7 +14,6 @@ import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; describe('NativePerformanceObserver', () => { it('correctly clears/gets entries', async () => { - logMockEntry({ name: 'mark1', entryType: RawPerformanceEntryTypeValues.MARK, @@ -29,6 +28,10 @@ describe('NativePerformanceObserver', () => { duration: 0, }); + expect( + NativePerformanceObserverMock.getEntries().map(e => e.name), + ).toStrictEqual(['mark1', 'event1']); + NativePerformanceObserverMock.clearEntries(); expect(NativePerformanceObserverMock.getEntries()).toStrictEqual([]); From 8f717c098ce27bb71a10f26b87ef4550804d6616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 20 Sep 2024 12:33:19 +0200 Subject: [PATCH 55/60] undo testing stuff --- .../nativemodule/defaults/CMakeLists.txt | 1 - .../defaults/DefaultTurboModules.cpp | 12 ----- .../React-defaultsnativemodule.podspec | 3 -- .../webperformance/CMakeLists.txt | 26 ---------- .../React-webperformancemodule.podspec | 49 ------------------- .../React-performancetimeline.podspec | 8 +-- .../react-native/scripts/react_native_pods.rb | 1 - 7 files changed, 4 insertions(+), 96 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt delete mode 100644 packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt index 4f152700b193e9..952ffe520cc589 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/CMakeLists.txt @@ -24,5 +24,4 @@ target_link_libraries(react_nativemodule_defaults react_nativemodule_featureflags react_nativemodule_microtasks react_nativemodule_idlecallbacks - react_nativemodule_webperformance ) diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp index 5f96572033ad3f..1e0201666d2c59 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/DefaultTurboModules.cpp @@ -10,8 +10,6 @@ #include #include #include -#include -#include namespace facebook::react { @@ -30,16 +28,6 @@ namespace facebook::react { return std::make_shared(jsInvoker); } - // TEMPORARY (PR only, for testing) - if (name == NativePerformance::kModuleName) { - return std::make_shared(jsInvoker); - } - - // TEMPORARY (PR only, for testing) - if (name == NativePerformanceObserver::kModuleName) { - return std::make_shared(jsInvoker); - } - if (name == NativeDOM::kModuleName) { return std::make_shared(jsInvoker); } diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec index 69976595d29db9..77cc249c1bfec5 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec @@ -48,7 +48,4 @@ Pod::Spec.new do |s| s.dependency "React-featureflagsnativemodule" s.dependency "React-microtasksnativemodule" s.dependency "React-idlecallbacksnativemodule" - - # TEMPORARY (PR only, for testing) - s.dependency "React-webperformancemodule" end diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt b/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt deleted file mode 100644 index f6907e073c4520..00000000000000 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/CMakeLists.txt +++ /dev/null @@ -1,26 +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. - -cmake_minimum_required(VERSION 3.13) -set(CMAKE_VERBOSE_MAKEFILE on) - -add_compile_options( - -fexceptions - -frtti - -std=c++20 - -Wall - -Wpedantic - -DLOG_TAG=\"ReactNative\") - -file(GLOB react_nativemodule_webperformance_SRC CONFIGURE_DEPENDS *.cpp) -add_library(react_nativemodule_webperformance STATIC ${react_nativemodule_webperformance_SRC}) - -target_include_directories(react_nativemodule_webperformance PUBLIC ${REACT_COMMON_DIR}) - -target_link_libraries(react_nativemodule_webperformance - react_codegen_rncore - react_cxxreact - react_render_runtimescheduler -) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec deleted file mode 100644 index 4fa41d5adfbf69..00000000000000 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/React-webperformancemodule.podspec +++ /dev/null @@ -1,49 +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. - -require "json" - -package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json"))) -version = package['version'] - -source = { :git => 'https://github.com/facebook/react-native.git' } -if version == '1000.0.0' - # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. - source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") -else - source[:tag] = "v#{version}" -end - -header_search_paths = [] - -if ENV['USE_FRAMEWORKS'] - header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the module access its own files -end - -Pod::Spec.new do |s| - s.name = "React-webperformancemodule" - s.version = version - s.summary = "React Native web performance native module" - s.homepage = "https://reactnative.dev/" - s.license = package["license"] - s.author = "Meta Platforms, Inc. and its affiliates" - s.platforms = min_supported_versions - s.source = source - s.source_files = "*.{cpp,h}" - s.header_dir = "react/nativemodule/webperformance" - s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", - "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), - "DEFINES_MODULE" => "YES" } - - if ENV['USE_FRAMEWORKS'] - s.module_name = "webperformancemodule" - s.header_mappings_dir = "../.." - end - - install_modules_dependencies(s) - - s.dependency "ReactCommon/turbomodule/core" - s.dependency "React-runtimescheduler" -end diff --git a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec index 41b3520c53330d..a8dbcbe7a01407 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec +++ b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec @@ -1,7 +1,7 @@ -#Copyright(c) Meta Platforms, Inc.and affiliates. +# 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. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. require "json" @@ -10,7 +10,7 @@ version = package['version'] source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' - # This is an unpublished version, use the latest commit hash of the react - native repo, which we’re presumably in. + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") else source[:tag] = "v#{version}" diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 00274aade43f69..221635d4edd1de 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -124,7 +124,6 @@ def use_react_native! ( pod 'React-featureflagsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/featureflags" pod 'React-microtasksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/microtasks" pod 'React-idlecallbacksnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/idlecallbacks" - pod 'React-webperformancemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/webperformance" pod 'React-domnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/dom" pod 'React-defaultsnativemodule', :path => "#{prefix}/ReactCommon/react/nativemodule/defaults" pod 'React-Mapbuffer', :path => "#{prefix}/ReactCommon" From ecf035aa11ecf659b48866501b488c06915dae36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 20 Sep 2024 16:26:52 +0200 Subject: [PATCH 56/60] fix if condition --- .../src/private/webapis/performance/PerformanceObserver.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 760864f6dbd0a6..7e14ac9eefd153 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -166,7 +166,7 @@ export class PerformanceObserver { return; } - if (this.#nativeObserverHandle != null) { + if (this.#nativeObserverHandle == null) { return; } From 3b2027847ac587fa0c390fb4068e7fb73938d843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 20 Sep 2024 17:02:30 +0200 Subject: [PATCH 57/60] remove weak ptrs from registry; --- .../timeline/PerformanceObserverRegistry.cpp | 15 ++------------- .../timeline/PerformanceObserverRegistry.h | 4 ++-- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp index c1ce53881917eb..da962692fb370d 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.cpp @@ -26,19 +26,8 @@ void PerformanceObserverRegistry::queuePerformanceEntry( const PerformanceEntry& entry) { std::lock_guard lock(observersMutex_); - // filter dead observers - for (auto first = observers_.begin(), last = observers_.end(); - first != last;) { - if (first->expired()) - first = observers_.erase(first); - else - ++first; - } - - for (auto& observer_ptr : observers_) { - if (auto observer = observer_ptr.lock()) { - observer->handleEntry(entry); - } + for (auto& observer : observers_) { + observer->handleEntry(entry); } } diff --git a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h index 52bd710b03cbcc..78c01c43697599 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h +++ b/packages/react-native/ReactCommon/react/performance/timeline/PerformanceObserverRegistry.h @@ -46,8 +46,8 @@ class PerformanceObserverRegistry { private: mutable std::mutex observersMutex_; std::set< - std::weak_ptr, - std::owner_less>> + std::shared_ptr, + std::owner_less>> observers_; }; From aeba17827c318d719f6544498585f46d0fbd4c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 20 Sep 2024 17:20:27 +0200 Subject: [PATCH 58/60] remove clearing the observer in native module --- .../nativemodule/webperformance/NativePerformanceObserver.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp index 965689b5baf850..c866fb72d911af 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformanceObserver.cpp @@ -106,7 +106,6 @@ void NativePerformanceObserver::disconnect( return; } - observerObj.setNativeState(rt, nullptr); observer->disconnect(); } From 8cfb905c4cbeda0176fc562f76fd0de6111005c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 23 Sep 2024 12:12:13 +0200 Subject: [PATCH 59/60] rename `logEvent` to `testOnly_logEvent` in NativePerformanceObserver --- .../webperformance/NativePerformance.cpp | 2 +- .../webperformance/NativePerformance.h | 2 +- .../performance/PerformanceObserver.js | 4 ++-- .../performance/__tests__/EventCounts-test.js | 22 +++++++++---------- .../performance/specs/NativePerformance.js | 2 +- .../specs/__mocks__/NativePerformance.js | 4 ++-- .../__mocks__/NativePerformanceObserver.js | 3 ++- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp index 58bd66a49b6423..2b217f9a96e384 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.cpp @@ -137,7 +137,7 @@ void NativePerformance::measure( #endif } -void NativePerformance::logEvent( +void NativePerformance::testOnly_logEvent( jsi::Runtime& rt, std::string name, double startTime, diff --git a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h index 73963c5fe228a6..58dfa7edb16a29 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h +++ b/packages/react-native/ReactCommon/react/nativemodule/webperformance/NativePerformance.h @@ -36,7 +36,7 @@ class NativePerformance : public NativePerformanceCxxSpec { std::optional startMark, std::optional endMark); - void logEvent( + void testOnly_logEvent( jsi::Runtime& rt, std::string name, double startTime, diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 7e14ac9eefd153..5856ef6fa01e76 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -182,9 +182,9 @@ export class PerformanceObserver { this.#calledAtLeastOnce = false; return NativePerformanceObserver.createObserver(() => { // $FlowNotNull - let entries = NativePerformanceObserver + const rawEntries = NativePerformanceObserver .takeRecords(this.#nativeObserverHandle); - entries = entries.map(rawToPerformanceEntry); + const entries = rawEntries.map(rawToPerformanceEntry); const entryList = new PerformanceObserverEntryList(entries); let droppedEntriesCount = 0; diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js index 4dc95e33bd272f..aa76c891c71e67 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js @@ -40,12 +40,12 @@ describe('EventCounts', () => { 100, // processingEnd ]; - NativePerformanceMock?.logEvent('click', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('click', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); const eventCounts = new Performance().eventCounts; expect(eventCounts.size).toBe(3); @@ -71,17 +71,17 @@ describe('EventCounts', () => { expect(Array.from(eventCounts.values())).toStrictEqual([1, 2, 3]); await jest.runAllTicks(); - NativePerformanceMock?.logEvent('input', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('input', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); expect(Array.from(eventCounts.values())).toStrictEqual([1, 3, 5]); await jest.runAllTicks(); - NativePerformanceMock?.logEvent('click', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('click', ...eventDefaultValues, interactionId++); await jest.runAllTicks(); - NativePerformanceMock?.logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); expect(Array.from(eventCounts.values())).toStrictEqual([2, 3, 6]); }); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js index db1dc5ac442a50..05416674e11cbb 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformance.js @@ -27,7 +27,7 @@ export interface Spec extends TurboModule { startMark?: string, endMark?: string, ) => void; - +logEvent: ( + +testOnly_logEvent: ( name: string, startTime: number, duration: number, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js index 1d89e90777fb81..1314ab7e55cca4 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js @@ -50,7 +50,7 @@ const NativePerformanceMock: typeof NativePerformance = { }); }, - logEvent: ( + testOnly_logEvent: ( name: string, startTime: number, duration: number, @@ -58,7 +58,7 @@ const NativePerformanceMock: typeof NativePerformance = { processingEnd: number, interactionId: number, ): void => { - NativePerformance?.logEvent(name, startTime, duration, processingStart, processingEnd, interactionId); + NativePerformance?.testOnly_logEvent(name, startTime, duration, processingStart, processingEnd, interactionId); logMockEntry({ entryType: RawPerformanceEntryTypeValues.EVENT, name, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js index 14d57dee5de1bd..ac5b9537508a06 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js @@ -47,7 +47,8 @@ export function logMockEntry(entry: RawPerformanceEntry) { } if (entry.entryType == RawPerformanceEntryTypeValues.EVENT) { - if (observer.options.durationThreshold > 0 && entry.duration < observer.options.durationThreshold) { + const { durationThreshold = 0 } = observer.options; + if (durationThreshold > 0 && entry.duration < durationThreshold) { continue; } } From f136f656a03948f07795ac95df7e06459c536b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Mon, 23 Sep 2024 14:12:22 +0200 Subject: [PATCH 60/60] style: fix --- .../webapis/performance/Performance.js | 1 - .../performance/PerformanceObserver.js | 18 +++-- .../performance/__tests__/EventCounts-test.js | 66 +++++++++++++++---- .../specs/NativePerformanceObserver.js | 22 ++++--- .../specs/__mocks__/NativePerformance.js | 11 +++- .../__mocks__/NativePerformanceObserver.js | 23 +++++-- .../NativePerformanceObserverMock-test.js | 4 +- 7 files changed, 108 insertions(+), 37 deletions(-) diff --git a/packages/react-native/src/private/webapis/performance/Performance.js b/packages/react-native/src/private/webapis/performance/Performance.js index 7e4dd07f73d1b0..dac3fa6b46bf54 100644 --- a/packages/react-native/src/private/webapis/performance/Performance.js +++ b/packages/react-native/src/private/webapis/performance/Performance.js @@ -40,7 +40,6 @@ declare var global: { const getCurrentTimeStamp: () => DOMHighResTimeStamp = NativePerformance?.now ?? global.nativePerformanceNow ?? (() => Date.now()); - function warnNoNativePerformance() { warnOnce( 'missing-native-performance', diff --git a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js index 5856ef6fa01e76..79a14a8beaa3f0 100644 --- a/packages/react-native/src/private/webapis/performance/PerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/PerformanceObserver.js @@ -22,7 +22,7 @@ import { rawToPerformanceEntryType, } from './RawPerformanceEntry'; import NativePerformanceObserver from './specs/NativePerformanceObserver'; -import type { OpaqueNativeObserverHandle } from './specs/NativePerformanceObserver'; +import type {OpaqueNativeObserverHandle} from './specs/NativePerformanceObserver'; export type PerformanceEntryList = $ReadOnlyArray; @@ -65,7 +65,7 @@ export type PerformanceObserverCallback = ( list: PerformanceObserverEntryList, observer: PerformanceObserver, // The number of buffered entries which got dropped from the buffer due to the buffer being full: - options?: PerformanceObserverCallbackOptions + options?: PerformanceObserverCallbackOptions, ) => void; export type PerformanceObserverInit = { @@ -181,19 +181,23 @@ export class PerformanceObserver { this.#calledAtLeastOnce = false; - return NativePerformanceObserver.createObserver(() => { // $FlowNotNull - const rawEntries = NativePerformanceObserver - .takeRecords(this.#nativeObserverHandle); + return NativePerformanceObserver.createObserver(() => { + // $FlowNotNull + const rawEntries = NativePerformanceObserver.takeRecords( + this.#nativeObserverHandle, + ); const entries = rawEntries.map(rawToPerformanceEntry); const entryList = new PerformanceObserverEntryList(entries); let droppedEntriesCount = 0; if (!this.#calledAtLeastOnce) { - droppedEntriesCount = NativePerformanceObserver.getDroppedEntriesCount(this.#nativeObserverHandle); + droppedEntriesCount = NativePerformanceObserver.getDroppedEntriesCount( + this.#nativeObserverHandle, + ); this.#calledAtLeastOnce = true; } - this.#callback(entryList, this, { droppedEntriesCount }); + this.#callback(entryList, this, {droppedEntriesCount}); }); } diff --git a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js index aa76c891c71e67..9c5416d44f3978 100644 --- a/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js +++ b/packages/react-native/src/private/webapis/performance/__tests__/EventCounts-test.js @@ -40,12 +40,36 @@ describe('EventCounts', () => { 100, // processingEnd ]; - NativePerformanceMock?.testOnly_logEvent('click', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.testOnly_logEvent('input', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.testOnly_logEvent('input', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent( + 'click', + ...eventDefaultValues, + interactionId++, + ); + NativePerformanceMock?.testOnly_logEvent( + 'input', + ...eventDefaultValues, + interactionId++, + ); + NativePerformanceMock?.testOnly_logEvent( + 'input', + ...eventDefaultValues, + interactionId++, + ); + NativePerformanceMock?.testOnly_logEvent( + 'keyup', + ...eventDefaultValues, + interactionId++, + ); + NativePerformanceMock?.testOnly_logEvent( + 'keyup', + ...eventDefaultValues, + interactionId++, + ); + NativePerformanceMock?.testOnly_logEvent( + 'keyup', + ...eventDefaultValues, + interactionId++, + ); const eventCounts = new Performance().eventCounts; expect(eventCounts.size).toBe(3); @@ -71,17 +95,37 @@ describe('EventCounts', () => { expect(Array.from(eventCounts.values())).toStrictEqual([1, 2, 3]); await jest.runAllTicks(); - NativePerformanceMock?.testOnly_logEvent('input', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); - NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent( + 'input', + ...eventDefaultValues, + interactionId++, + ); + NativePerformanceMock?.testOnly_logEvent( + 'keyup', + ...eventDefaultValues, + interactionId++, + ); + NativePerformanceMock?.testOnly_logEvent( + 'keyup', + ...eventDefaultValues, + interactionId++, + ); expect(Array.from(eventCounts.values())).toStrictEqual([1, 3, 5]); await jest.runAllTicks(); - NativePerformanceMock?.testOnly_logEvent('click', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent( + 'click', + ...eventDefaultValues, + interactionId++, + ); await jest.runAllTicks(); - NativePerformanceMock?.testOnly_logEvent('keyup', ...eventDefaultValues, interactionId++); + NativePerformanceMock?.testOnly_logEvent( + 'keyup', + ...eventDefaultValues, + interactionId++, + ); expect(Array.from(eventCounts.values())).toStrictEqual([2, 3, 6]); }); diff --git a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js index 7354d1ef496380..ad8843358e2b74 100644 --- a/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/NativePerformanceObserver.js @@ -35,20 +35,27 @@ export type GetPendingEntriesResult = {| |}; export type PerformanceObserverInit = { - entryTypes?: $ReadOnlyArray; - type?: number; - buffered?: boolean; - durationThreshold?: number; + entryTypes?: $ReadOnlyArray, + type?: number, + buffered?: boolean, + durationThreshold?: number, }; export interface Spec extends TurboModule { +getEventCounts: () => $ReadOnlyArray<[string, number]>; - +createObserver: (callback: NativeBatchedObserverCallback) => OpaqueNativeObserverHandle; + +createObserver: ( + callback: NativeBatchedObserverCallback, + ) => OpaqueNativeObserverHandle; +getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle) => number; - +observe: (observer: OpaqueNativeObserverHandle, options: PerformanceObserverInit) => void; + +observe: ( + observer: OpaqueNativeObserverHandle, + options: PerformanceObserverInit, + ) => void; +disconnect: (observer: OpaqueNativeObserverHandle) => void; - +takeRecords: (observer: OpaqueNativeObserverHandle) => $ReadOnlyArray; + +takeRecords: ( + observer: OpaqueNativeObserverHandle, + ) => $ReadOnlyArray; +clearEntries: ( entryType?: RawPerformanceEntryType, @@ -59,7 +66,6 @@ export interface Spec extends TurboModule { entryName?: string, ) => $ReadOnlyArray; +getSupportedPerformanceEntryTypes: () => $ReadOnlyArray; - } export default (TurboModuleRegistry.get( diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js index 1314ab7e55cca4..e9fb5cf96f9623 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformance.js @@ -15,7 +15,7 @@ import type { import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; import NativePerformance from '../NativePerformance'; -import { logMockEntry } from './NativePerformanceObserver'; +import {logMockEntry} from './NativePerformanceObserver'; const marks: Map = new Map(); @@ -58,7 +58,14 @@ const NativePerformanceMock: typeof NativePerformance = { processingEnd: number, interactionId: number, ): void => { - NativePerformance?.testOnly_logEvent(name, startTime, duration, processingStart, processingEnd, interactionId); + NativePerformance?.testOnly_logEvent( + name, + startTime, + duration, + processingStart, + processingEnd, + interactionId, + ); logMockEntry({ entryType: RawPerformanceEntryTypeValues.EVENT, name, diff --git a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js index ac5b9537508a06..b8793cba99201e 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js +++ b/packages/react-native/src/private/webapis/performance/specs/__mocks__/NativePerformanceObserver.js @@ -41,13 +41,15 @@ export function logMockEntry(entry: RawPerformanceEntry) { } for (const observer of observers) { - if (observer.options.type != entry.entryType && - !observer.options.entryTypes?.includes(entry.entryType)) { + if ( + observer.options.type != entry.entryType && + !observer.options.entryTypes?.includes(entry.entryType) + ) { continue; } if (entry.entryType == RawPerformanceEntryTypeValues.EVENT) { - const { durationThreshold = 0 } = observer.options; + const {durationThreshold = 0} = observer.options; if (durationThreshold > 0 && entry.duration < durationThreshold) { continue; } @@ -67,7 +69,7 @@ type MockObserver = { callback: NativeBatchedObserverCallback, entries: Array, options: PerformanceObserverInit, - droppedEntriesCount: number + droppedEntriesCount: number, }; const NativePerformanceObserverMock: NativePerformanceObserver = { @@ -75,7 +77,9 @@ const NativePerformanceObserverMock: NativePerformanceObserver = { return Array.from(eventCounts.entries()); }, - createObserver: (callback: NativeBatchedObserverCallback): OpaqueNativeObserverHandle => { + createObserver: ( + callback: NativeBatchedObserverCallback, + ): OpaqueNativeObserverHandle => { const observer: MockObserver = { callback, entries: [], @@ -92,7 +96,10 @@ const NativePerformanceObserverMock: NativePerformanceObserver = { return mockObserver.droppedEntriesCount; }, - observe: (observer: OpaqueNativeObserverHandle, options: PerformanceObserverInit): void => { + observe: ( + observer: OpaqueNativeObserverHandle, + options: PerformanceObserverInit, + ): void => { // $FlowFixMe const mockObserver = (observer: any) as MockObserver; mockObserver.options = options; @@ -105,7 +112,9 @@ const NativePerformanceObserverMock: NativePerformanceObserver = { observers = observers.filter(e => e !== mockObserver); }, - takeRecords: (observer: OpaqueNativeObserverHandle): $ReadOnlyArray => { + takeRecords: ( + observer: OpaqueNativeObserverHandle, + ): $ReadOnlyArray => { // $FlowFixMe const mockObserver = (observer: any) as MockObserver; const observerEntries = mockObserver.entries; diff --git a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js index e57ddee505d9f5..6753251ec3c903 100644 --- a/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js +++ b/packages/react-native/src/private/webapis/performance/specs/__tests__/NativePerformanceObserverMock-test.js @@ -9,7 +9,9 @@ * @oncall react_native */ -import NativePerformanceObserverMock, { logMockEntry } from '../__mocks__/NativePerformanceObserver'; +import NativePerformanceObserverMock, { + logMockEntry, +} from '../__mocks__/NativePerformanceObserver'; import {RawPerformanceEntryTypeValues} from '../../RawPerformanceEntry'; describe('NativePerformanceObserver', () => {