diff --git a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp index 02661350f09..4bffe9e563f 100644 --- a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp +++ b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp @@ -12,71 +12,66 @@ #include -// All the unflushed entries beyond this amount will get discarded, with -// the amount of discarded ones sent back to the observers' callbacks as -// "droppedEntryCount" value -static constexpr size_t MAX_ENTRY_BUFFER_SIZE = 1024; - namespace facebook::react { EventTag PerformanceEntryReporter::sCurrentEventTag_{0}; -RawPerformanceEntry PerformanceMark::toRawPerformanceEntry() const { - return { - name, - static_cast(PerformanceEntryType::MARK), - timeStamp, - 0.0, - std::nullopt, - std::nullopt, - std::nullopt}; -} - -RawPerformanceEntry PerformanceMeasure::toRawPerformanceEntry() const { - return { - name, - static_cast(PerformanceEntryType::MEASURE), - timeStamp, - duration, - std::nullopt, - std::nullopt, - std::nullopt}; -} - PerformanceEntryReporter &PerformanceEntryReporter::getInstance() { static PerformanceEntryReporter instance; 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::optional> callback) { callback_ = callback; } void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) { - int entryTypeIdx = static_cast(entryType); - reportingType_[entryTypeIdx] = true; - durationThreshold_[entryTypeIdx] = DEFAULT_DURATION_THRESHOLD; + auto &buffer = getBuffer(entryType); + buffer.isReporting = true; + buffer.durationThreshold = DEFAULT_DURATION_THRESHOLD; } void PerformanceEntryReporter::setDurationThreshold( PerformanceEntryType entryType, double durationThreshold) { - durationThreshold_[static_cast(entryType)] = durationThreshold; + getBuffer(entryType).durationThreshold = durationThreshold; } void PerformanceEntryReporter::stopReporting(PerformanceEntryType entryType) { - reportingType_[static_cast(entryType)] = false; + getBuffer(entryType).isReporting = false; } void PerformanceEntryReporter::stopReporting() { - reportingType_.fill(false); + for (auto &buffer : buffers_) { + buffer.isReporting = false; + } } GetPendingEntriesResult PerformanceEntryReporter::popPendingEntries() { std::lock_guard lock(entriesMutex_); + GetPendingEntriesResult res = { + std::vector(), droppedEntryCount_}; + for (auto &buffer : buffers_) { + buffer.entries.consume(res.entries); + } + + // Sort by starting time (or ending time, if starting times are equal) + std::sort( + res.entries.begin(), + res.entries.end(), + [](const RawPerformanceEntry &lhs, const RawPerformanceEntry &rhs) { + if (lhs.startTime != rhs.startTime) { + return lhs.startTime < rhs.startTime; + } else { + return lhs.duration < rhs.duration; + } + }); - GetPendingEntriesResult res = {std::move(entries_), droppedEntryCount_}; - entries_ = {}; droppedEntryCount_ = 0; return res; } @@ -91,24 +86,39 @@ void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) { return; } - if (entry.duration < durationThreshold_[entry.entryType]) { - // The entries duration is lower than the desired reporting threshold, skip - // return; - } - std::lock_guard lock(entriesMutex_); - if (entries_.size() == MAX_ENTRY_BUFFER_SIZE) { + auto &buffer = buffers_[entry.entryType]; + + if (entry.duration < buffer.durationThreshold) { + // The entries duration is lower than the desired reporting threshold, skip + return; + } + + if (buffer.hasNameLookup) { + auto overwriteCandidate = buffer.entries.getNextOverwriteCandidate(); + if (overwriteCandidate != nullptr) { + auto it = buffer.nameLookup.find(overwriteCandidate); + if (it != buffer.nameLookup.end() && *it == overwriteCandidate) { + buffer.nameLookup.erase(it); + } + } + } + + 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. droppedEntryCount_ += 1; - return; } - entries_.emplace_back(entry); + if (buffer.hasNameLookup) { + buffer.nameLookup.insert(&buffer.entries.back()); + } - if (entries_.size() == 1) { + 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(); @@ -119,95 +129,75 @@ void PerformanceEntryReporter::mark( const std::string &name, double startTime, double duration) { - // Register the mark for further possible "measure" lookup, as well as add - // it to a circular buffer: - PerformanceMark &mark = marksBuffer_[marksBufferPosition_]; - marksBufferPosition_ = (marksBufferPosition_ + 1) % marksBuffer_.size(); - marksCount_ = std::min(marksBuffer_.size(), marksCount_ + 1); - - if (!mark.name.empty()) { - // Drop off the oldest mark out of the queue, but only if that's indeed the - // oldest one - auto it = marksRegistry_.find(&mark); - if (it != marksRegistry_.end() && *it == &mark) { - marksRegistry_.erase(it); - } - } - - mark.name = name; - mark.timeStamp = startTime; - marksRegistry_.insert(&mark); - - logEntry( - {name, - static_cast(PerformanceEntryType::MARK), - startTime, - duration, - std::nullopt, - std::nullopt, - std::nullopt}); + logEntry(RawPerformanceEntry{ + name, + static_cast(PerformanceEntryType::MARK), + startTime, + duration, + std::nullopt, + std::nullopt, + std::nullopt}); } void PerformanceEntryReporter::clearEntries( PerformanceEntryType entryType, const char *entryName) { - if (entryType == PerformanceEntryType::MARK || - entryType == PerformanceEntryType::UNDEFINED) { + if (entryType == PerformanceEntryType::UNDEFINED) { + // 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 != nullptr) { - // remove a named mark from the mark/measure registry - PerformanceMark mark{{entryName, 0}}; - marksRegistry_.erase(&mark); - - clearCircularBuffer( - marksBuffer_, marksCount_, marksBufferPosition_, entryName); + if (buffer.hasNameLookup) { + RawPerformanceEntry entry{ + entryName, + static_cast(entryType), + 0.0, + 0.0, + std::nullopt, + std::nullopt, + std::nullopt}; + buffer.nameLookup.erase(&entry); + } + buffer.entries.clear([entryName](const RawPerformanceEntry &entry) { + return std::strcmp(entry.name.c_str(), entryName) == 0; + }); } else { - marksCount_ = 0; - marksRegistry_.clear(); + buffer.entries.clear(); + buffer.nameLookup.clear(); } } +} - if (entryType == PerformanceEntryType::MEASURE || - entryType == PerformanceEntryType::UNDEFINED) { - if (entryName != nullptr) { - clearCircularBuffer( - measuresBuffer_, measuresCount_, measuresBufferPosition_, entryName); +void PerformanceEntryReporter::getEntries( + PerformanceEntryType entryType, + const char *entryName, + std::vector &res) const { + if (entryType == PerformanceEntryType::UNDEFINED) { + // Collect all entry types + for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) { + getEntries(static_cast(i), entryName, res); + } + } else { + const auto &entries = getBuffer(entryType).entries; + if (entryName == nullptr) { + entries.getEntries(res); } else { - measuresCount_ = 0; + entries.getEntries(res, [entryName](const RawPerformanceEntry &entry) { + return std::strcmp(entry.name.c_str(), entryName) == 0; + }); } } - - int lastPos = entries_.size() - 1; - int pos = lastPos; - while (pos >= 0) { - const RawPerformanceEntry &entry = entries_[pos]; - if (entry.entryType == static_cast(entryType) && - (entryName == nullptr || entry.name == entryName)) { - entries_[pos] = entries_[lastPos]; - lastPos--; - } - pos--; - } - entries_.resize(lastPos + 1); } std::vector PerformanceEntryReporter::getEntries( PerformanceEntryType entryType, const char *entryName) const { - if (entryType == PerformanceEntryType::MARK) { - return getCircularBufferContents( - marksBuffer_, marksCount_, marksBufferPosition_, entryName); - } else if (entryType == PerformanceEntryType::MEASURE) { - return getCircularBufferContents( - measuresBuffer_, measuresCount_, measuresBufferPosition_, entryName); - } else if (entryType == PerformanceEntryType::UNDEFINED) { - auto marks = getCircularBufferContents( - marksBuffer_, marksCount_, marksBufferPosition_, entryName); - auto measures = getCircularBufferContents( - measuresBuffer_, measuresCount_, measuresBufferPosition_, entryName); - marks.insert(marks.end(), measures.begin(), measures.end()); - return marks; - } - return {}; + std::vector res; + getEntries(entryType, entryName, res); + return res; } void PerformanceEntryReporter::measure( @@ -220,13 +210,6 @@ void PerformanceEntryReporter::measure( double startTimeVal = startMark ? getMarkTime(*startMark) : startTime; double endTimeVal = endMark ? getMarkTime(*endMark) : endTime; double durationVal = duration ? *duration : endTimeVal - startTimeVal; - - measuresBuffer_[measuresBufferPosition_] = - PerformanceMeasure{name, startTime, endTime}; - measuresBufferPosition_ = - (measuresBufferPosition_ + 1) % measuresBuffer_.size(); - measuresCount_ = std::min(measuresBuffer_.size(), measuresCount_ + 1); - logEntry( {name, static_cast(PerformanceEntryType::MEASURE), @@ -239,10 +222,19 @@ void PerformanceEntryReporter::measure( double PerformanceEntryReporter::getMarkTime( const std::string &markName) const { - PerformanceMark mark{{std::move(markName), 0}}; - auto it = marksRegistry_.find(&mark); - if (it != marksRegistry_.end()) { - return (*it)->timeStamp; + RawPerformanceEntry mark{ + markName, + static_cast(PerformanceEntryType::MARK), + 0.0, + 0.0, + std::nullopt, + std::nullopt, + std::nullopt}; + + const auto &marksBuffer = getBuffer(PerformanceEntryType::MARK); + auto it = marksBuffer.nameLookup.find(&mark); + if (it != marksBuffer.nameLookup.end()) { + return (*it)->startTime; } else { return 0.0; } diff --git a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h index 7f7a014fade..0704cb05580 100644 --- a/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h +++ b/packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.h @@ -15,50 +15,45 @@ #include #include #include +#include "BoundedConsumableBuffer.h" #include "NativePerformanceObserver.h" namespace facebook::react { -struct PerformanceMark { - std::string name; - double timeStamp; - - RawPerformanceEntry toRawPerformanceEntry() const; -}; - -struct PerformanceMarkHash { - size_t operator()(const PerformanceMark *mark) const { - return std::hash()(mark->name); +struct PerformanceEntryHash { + size_t operator()(const RawPerformanceEntry *entry) const { + return std::hash()(entry->name); } }; -struct PerformanceMarkEqual { - bool operator()(const PerformanceMark *lhs, const PerformanceMark *rhs) - const { +struct PerformanceEntryEqual { + bool operator()( + const RawPerformanceEntry *lhs, + const RawPerformanceEntry *rhs) const { return lhs->name == rhs->name; } }; -struct PerformanceMeasure { - std::string name; - double timeStamp; - double duration; - - RawPerformanceEntry toRawPerformanceEntry() const; -}; - -using PerformanceMarkRegistryType = std:: - unordered_set; - -// Only the MARKS_BUFFER_SIZE amount of the latest marks will be kept in -// memory for the sake of the "Performance.measure" mark name lookup -constexpr size_t MARKS_BUFFER_SIZE = 1024; - -// Limit buffer size for the measures kept in memory (only keep the latest ones) -constexpr size_t MEASURES_BUFFER_SIZE = 1024; +using PerformanceEntryRegistryType = std::unordered_set< + const RawPerformanceEntry *, + 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}; + double durationThreshold{DEFAULT_DURATION_THRESHOLD}; + bool hasNameLookup{false}; + PerformanceEntryRegistryType nameLookup; +}; + enum class PerformanceEntryType { UNDEFINED = 0, MARK = 1, @@ -67,6 +62,9 @@ enum class PerformanceEntryType { _COUNT = 4, }; +constexpr size_t NUM_PERFORMANCE_ENTRY_TYPES = + (size_t)PerformanceEntryType::_COUNT; + class PerformanceEntryReporter : public EventLogger { public: PerformanceEntryReporter(PerformanceEntryReporter const &) = delete; @@ -87,10 +85,20 @@ class PerformanceEntryReporter : public EventLogger { double durationThreshold); GetPendingEntriesResult popPendingEntries(); + void logEntry(const RawPerformanceEntry &entry); + PerformanceEntryBuffer &getBuffer(PerformanceEntryType entryType) { + return buffers_[static_cast(entryType)]; + } + + const PerformanceEntryBuffer &getBuffer( + PerformanceEntryType entryType) const { + return buffers_[static_cast(entryType)]; + } + bool isReporting(PerformanceEntryType entryType) const { - return reportingType_[static_cast(entryType)]; + return getBuffer(entryType).isReporting; } bool isReportingEvents() const { @@ -137,23 +145,13 @@ class PerformanceEntryReporter : public EventLogger { private: std::optional> callback_; - std::vector entries_; + std::mutex entriesMutex_; - std::array reportingType_{false}; + std::array buffers_; std::unordered_map eventCounts_; - std::array durationThreshold_{ - DEFAULT_DURATION_THRESHOLD}; // Mark registry for "measure" lookup - PerformanceMarkRegistryType marksRegistry_; - std::array marksBuffer_; - size_t marksBufferPosition_{0}; - size_t marksCount_{0}; - - std::array measuresBuffer_; - size_t measuresBufferPosition_{0}; - size_t measuresCount_{0}; - + PerformanceEntryRegistryType marksRegistry_; uint32_t droppedEntryCount_{0}; struct EventEntry { @@ -171,49 +169,15 @@ class PerformanceEntryReporter : public EventLogger { static EventTag sCurrentEventTag_; - PerformanceEntryReporter() {} + PerformanceEntryReporter(); double getMarkTime(const std::string &markName) const; void scheduleFlushBuffer(); - template - std::vector getCircularBufferContents( - const std::array &buffer, - size_t entryCount, - size_t bufferPosition, - const char *entryName = nullptr) const { - std::vector res; - size_t pos = (bufferPosition - entryCount + buffer.size()) % buffer.size(); - for (size_t i = 0; i < entryCount; i++) { - if (entryName == nullptr || buffer[pos].name == entryName) { - res.push_back(buffer[pos].toRawPerformanceEntry()); - } - pos = (pos + 1) % buffer.size(); - } - return res; - } - - template - void clearCircularBuffer( - std::array &buffer, - size_t &entryCount, - size_t &bufferPosition, - const char *entryName) const { - std::array newBuffer; - size_t newEntryCount = 0; - - size_t pos = (bufferPosition - entryCount + buffer.size()) % buffer.size(); - for (size_t i = 0; i < entryCount; i++) { - if (buffer[pos].name != entryName) { - newBuffer[newEntryCount++] = buffer[pos]; - } - pos = (pos + 1) % buffer.size(); - } - - buffer = newBuffer; - bufferPosition = newEntryCount; - entryCount = newEntryCount; - } + void getEntries( + PerformanceEntryType entryType, + const char *entryName, + std::vector &res) const; }; } // namespace facebook::react diff --git a/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp b/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp index badf5396ff5..40a3affe0f2 100644 --- a/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp +++ b/packages/react-native/Libraries/WebPerformance/__tests__/PerformanceEntryReporterTest.cpp @@ -152,17 +152,6 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { ASSERT_EQ(0, res.droppedEntriesCount); - ASSERT_STREQ("mark0", entries[0].name.c_str()); - ASSERT_STREQ("mark1", entries[1].name.c_str()); - ASSERT_STREQ("mark2", entries[2].name.c_str()); - ASSERT_STREQ("measure0", entries[3].name.c_str()); - ASSERT_STREQ("measure1", entries[4].name.c_str()); - ASSERT_STREQ("measure2", entries[5].name.c_str()); - ASSERT_STREQ("measure3", entries[6].name.c_str()); - ASSERT_STREQ("measure4", entries[7].name.c_str()); - - ASSERT_EQ(8, entries.size()); - const std::vector expected = { {"mark0", static_cast(PerformanceEntryType::MARK), @@ -171,20 +160,6 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { std::nullopt, std::nullopt, std::nullopt}, - {"mark1", - static_cast(PerformanceEntryType::MARK), - 1.0, - 3.0, - std::nullopt, - std::nullopt, - std::nullopt}, - {"mark2", - static_cast(PerformanceEntryType::MARK), - 2.0, - 4.0, - std::nullopt, - std::nullopt, - std::nullopt}, {"measure0", static_cast(PerformanceEntryType::MEASURE), 0.0, @@ -206,6 +181,13 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { std::nullopt, std::nullopt, std::nullopt}, + {"mark1", + static_cast(PerformanceEntryType::MARK), + 1.0, + 3.0, + std::nullopt, + std::nullopt, + std::nullopt}, {"measure3", static_cast(PerformanceEntryType::MEASURE), 1.0, @@ -219,6 +201,13 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) { 0.5, std::nullopt, std::nullopt, + std::nullopt}, + {"mark2", + static_cast(PerformanceEntryType::MARK), + 2.0, + 4.0, + std::nullopt, + std::nullopt, std::nullopt}}; ASSERT_EQ(expected, entries);