diff --git a/Libraries/WebPerformance/NativePerformance.cpp b/Libraries/WebPerformance/NativePerformance.cpp index 355f3f2f531..51db30395df 100644 --- a/Libraries/WebPerformance/NativePerformance.cpp +++ b/Libraries/WebPerformance/NativePerformance.cpp @@ -24,7 +24,9 @@ void NativePerformance::mark( void NativePerformance::clearMarks( jsi::Runtime &rt, - std::optional markName) {} + std::optional markName) { + PerformanceEntryReporter::getInstance().clearMarks(markName); +} void NativePerformance::measure( jsi::Runtime &rt, @@ -33,10 +35,15 @@ void NativePerformance::measure( double endTime, std::optional duration, std::optional startMark, - std::optional endMark) {} + std::optional endMark) { + PerformanceEntryReporter::getInstance().measure( + name, startTime, endTime, duration, startMark, endMark); +} void NativePerformance::clearMeasures( jsi::Runtime &rt, - std::optional measureName) {} + std::optional measureName) { + PerformanceEntryReporter::getInstance().clearMeasures(measureName); +} } // namespace facebook::react diff --git a/Libraries/WebPerformance/NativePerformance.h b/Libraries/WebPerformance/NativePerformance.h index 2c23b959d0a..7078f50a943 100644 --- a/Libraries/WebPerformance/NativePerformance.h +++ b/Libraries/WebPerformance/NativePerformance.h @@ -37,7 +37,6 @@ class NativePerformance : public NativePerformanceCxxSpec, std::optional duration, std::optional startMark, std::optional endMark); - void clearMeasures(jsi::Runtime &rt, std::optional measureName); private: diff --git a/Libraries/WebPerformance/NativePerformanceObserver.cpp b/Libraries/WebPerformance/NativePerformanceObserver.cpp index 74ca8382ad2..bd10d51ac80 100644 --- a/Libraries/WebPerformance/NativePerformanceObserver.cpp +++ b/Libraries/WebPerformance/NativePerformanceObserver.cpp @@ -15,6 +15,8 @@ static PerformanceEntryType stringToPerformanceEntryType( const std::string &entryType) { if (entryType == "mark") { return PerformanceEntryType::MARK; + } else if (entryType == "measure") { + return PerformanceEntryType::MEASURE; } else { return PerformanceEntryType::UNDEFINED; } diff --git a/Libraries/WebPerformance/PerformanceEntryReporter.cpp b/Libraries/WebPerformance/PerformanceEntryReporter.cpp index 4af4404f229..409fc09bb29 100644 --- a/Libraries/WebPerformance/PerformanceEntryReporter.cpp +++ b/Libraries/WebPerformance/PerformanceEntryReporter.cpp @@ -59,6 +59,24 @@ 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 = marks_buffer_[marks_buffer_position_]; + marks_buffer_position_ = (marks_buffer_position_ + 1) % marks_buffer_.size(); + + if (!mark.name.empty()) { + // Drop off the oldest mark out of the queue, but only if that's indeed the + // oldest one + auto it = marks_registry_.find(&mark); + if (it != marks_registry_.end() && *it == &mark) { + marks_registry_.erase(it); + } + } + + mark.name = name; + mark.timeStamp = startTime; + marks_registry_.insert(&mark); + logEntry( {name, static_cast(PerformanceEntryType::MARK), @@ -68,4 +86,83 @@ void PerformanceEntryReporter::mark( std::nullopt, std::nullopt}); } + +void PerformanceEntryReporter::clearMarks( + const std::optional &markName) { + if (markName) { + PerformanceMark mark{{*markName, 0}}; + marks_registry_.erase(&mark); + clearEntries([&markName](const RawPerformanceEntry &entry) { + return entry.entryType == static_cast(PerformanceEntryType::MARK) && + entry.name == markName; + }); + } else { + marks_registry_.clear(); + clearEntries([](const RawPerformanceEntry &entry) { + return entry.entryType == static_cast(PerformanceEntryType::MARK); + }); + } +} + +void PerformanceEntryReporter::measure( + const std::string &name, + double startTime, + double endTime, + const std::optional &duration, + const std::optional &startMark, + const std::optional &endMark) { + double startTimeVal = startMark ? getMarkTime(*startMark) : startTime; + double endTimeVal = endMark ? getMarkTime(*endMark) : endTime; + double durationVal = duration ? *duration : endTimeVal - startTimeVal; + logEntry( + {name, + static_cast(PerformanceEntryType::MEASURE), + startTimeVal, + durationVal, + std::nullopt, + std::nullopt, + std::nullopt}); +} + +void PerformanceEntryReporter::clearMeasures( + const std::optional &measureName) { + if (measureName) { + clearEntries([&measureName](const RawPerformanceEntry &entry) { + return entry.entryType == + static_cast(PerformanceEntryType::MEASURE) && + entry.name == measureName; + }); + } else { + marks_registry_.clear(); + clearEntries([](const RawPerformanceEntry &entry) { + return entry.entryType == static_cast(PerformanceEntryType::MEASURE); + }); + } +} + +double PerformanceEntryReporter::getMarkTime( + const std::string &markName) const { + PerformanceMark mark{{std::move(markName), 0}}; + auto it = marks_registry_.find(&mark); + if (it != marks_registry_.end()) { + return (*it)->timeStamp; + } else { + return 0.0; + } +} + +void PerformanceEntryReporter::clearEntries( + std::function predicate) { + int lastPos = entries_.size() - 1; + int pos = lastPos; + while (pos >= 0) { + if (predicate(entries_[pos])) { + entries_[pos] = entries_[lastPos]; + lastPos--; + } + pos--; + } + entries_.resize(lastPos + 1); +} + } // namespace facebook::react diff --git a/Libraries/WebPerformance/PerformanceEntryReporter.h b/Libraries/WebPerformance/PerformanceEntryReporter.h index a270b1dbeb8..61e2a10c3e1 100644 --- a/Libraries/WebPerformance/PerformanceEntryReporter.h +++ b/Libraries/WebPerformance/PerformanceEntryReporter.h @@ -9,15 +9,43 @@ #include #include +#include #include +#include #include "NativePerformanceObserver.h" namespace facebook::react { +struct PerformanceMark { + std::string name; + double timeStamp; +}; + +struct PerformanceMarkHash { + size_t operator()(const PerformanceMark *mark) const { + return std::hash()(mark->name); + } +}; + +struct PerformanceMarkEqual { + bool operator()(const PerformanceMark *lhs, const PerformanceMark *rhs) + const { + return lhs->name == rhs->name; + } +}; + +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; + enum class PerformanceEntryType { UNDEFINED = 0, MARK = 1, - _COUNT = 2, + MEASURE = 2, + _COUNT = 3, }; class PerformanceEntryReporter { @@ -45,13 +73,31 @@ class PerformanceEntryReporter { } void mark(const std::string &name, double startTime, double duration); + void clearMarks(const std::optional &markName); + + void measure( + const std::string &name, + double startTime, + double endTime, + const std::optional &duration, + const std::optional &startMark, + const std::optional &endMark); + void clearMeasures(const std::optional &measureName); private: PerformanceEntryReporter() {} + double getMarkTime(const std::string &markName) const; + void clearEntries(std::function predicate); + std::optional> callback_; std::vector entries_; std::array reportingType_{false}; + + // Mark registry for "measure" lookup + PerformanceMarkRegistryType marks_registry_; + std::array marks_buffer_; + size_t marks_buffer_position_{0}; }; } // namespace facebook::react