1 /*
2  * Copyright (c) Meta Platforms, Inc. and affiliates.
3  *
4  * This source code is licensed under the MIT license found in the
5  * LICENSE file in the root directory of this source tree.
6  */
7 
8 #pragma once
9 
10 #include <ABI49_0_0React/ABI49_0_0bridging/Function.h>
11 #include <ABI49_0_0React/ABI49_0_0renderer/core/EventLogger.h>
12 #include <array>
13 #include <functional>
14 #include <mutex>
15 #include <optional>
16 #include <unordered_map>
17 #include <unordered_set>
18 #include "ABI49_0_0NativePerformanceObserver.h"
19 
20 namespace ABI49_0_0facebook::ABI49_0_0React {
21 
22 struct PerformanceMark {
23   std::string name;
24   double timeStamp;
25 
26   RawPerformanceEntry toRawPerformanceEntry() const;
27 };
28 
29 struct PerformanceMarkHash {
operatorPerformanceMarkHash30   size_t operator()(const PerformanceMark *mark) const {
31     return std::hash<std::string>()(mark->name);
32   }
33 };
34 
35 struct PerformanceMarkEqual {
operatorPerformanceMarkEqual36   bool operator()(const PerformanceMark *lhs, const PerformanceMark *rhs)
37       const {
38     return lhs->name == rhs->name;
39   }
40 };
41 
42 struct PerformanceMeasure {
43   std::string name;
44   double timeStamp;
45   double duration;
46 
47   RawPerformanceEntry toRawPerformanceEntry() const;
48 };
49 
50 using PerformanceMarkRegistryType = std::
51     unordered_set<PerformanceMark *, PerformanceMarkHash, PerformanceMarkEqual>;
52 
53 // Only the MARKS_BUFFER_SIZE amount of the latest marks will be kept in
54 // memory for the sake of the "Performance.measure" mark name lookup
55 constexpr size_t MARKS_BUFFER_SIZE = 1024;
56 
57 // Limit buffer size for the measures kept in memory (only keep the latest ones)
58 constexpr size_t MEASURES_BUFFER_SIZE = 1024;
59 
60 constexpr double DEFAULT_DURATION_THRESHOLD = 0.0;
61 
62 enum class PerformanceEntryType {
63   UNDEFINED = 0,
64   MARK = 1,
65   MEASURE = 2,
66   EVENT = 3,
67   _COUNT = 4,
68 };
69 
70 class PerformanceEntryReporter : public EventLogger {
71  public:
72   PerformanceEntryReporter(PerformanceEntryReporter const &) = delete;
73   void operator=(PerformanceEntryReporter const &) = delete;
74 
75   // NOTE: This class is not thread safe, make sure that the calls are made from
76   // the same thread.
77   // TODO: Consider passing it as a parameter to the corresponding modules at
78   // creation time instead of having the singleton.
79   static PerformanceEntryReporter &getInstance();
80 
81   void setReportingCallback(std::optional<AsyncCallback<>> callback);
82   void startReporting(PerformanceEntryType entryType);
83   void stopReporting(PerformanceEntryType entryType);
84   void stopReporting();
85   void setDurationThreshold(
86       PerformanceEntryType entryType,
87       double durationThreshold);
88 
89   GetPendingEntriesResult popPendingEntries();
90   void logEntry(const RawPerformanceEntry &entry);
91 
isReporting(PerformanceEntryType entryType)92   bool isReporting(PerformanceEntryType entryType) const {
93     return reportingType_[static_cast<int>(entryType)];
94   }
95 
isReportingEvents()96   bool isReportingEvents() const {
97     return isReporting(PerformanceEntryType::EVENT);
98   }
99 
getDroppedEntryCount()100   uint32_t getDroppedEntryCount() const {
101     return droppedEntryCount_;
102   }
103 
104   void mark(const std::string &name, double startTime, double duration);
105 
106   void measure(
107       const std::string &name,
108       double startTime,
109       double endTime,
110       const std::optional<double> &duration = std::nullopt,
111       const std::optional<std::string> &startMark = std::nullopt,
112       const std::optional<std::string> &endMark = std::nullopt);
113 
114   void clearEntries(
115       PerformanceEntryType entryType = PerformanceEntryType::UNDEFINED,
116       const char *entryName = nullptr);
117 
118   std::vector<RawPerformanceEntry> getEntries(
119       PerformanceEntryType entryType = PerformanceEntryType::UNDEFINED,
120       const char *entryName = nullptr) const;
121 
122   void event(
123       std::string name,
124       double startTime,
125       double duration,
126       double processingStart,
127       double processingEnd,
128       uint32_t interactionId);
129 
130   EventTag onEventStart(const char *name) override;
131   void onEventDispatch(EventTag tag) override;
132   void onEventEnd(EventTag tag) override;
133 
getEventCounts()134   const std::unordered_map<std::string, uint32_t> &getEventCounts() const {
135     return eventCounts_;
136   }
137 
138  private:
139   std::optional<AsyncCallback<>> callback_;
140   std::vector<RawPerformanceEntry> entries_;
141   std::mutex entriesMutex_;
142   std::array<bool, (size_t)PerformanceEntryType::_COUNT> reportingType_{false};
143   std::unordered_map<std::string, uint32_t> eventCounts_;
144   std::array<double, (size_t)PerformanceEntryType::_COUNT> durationThreshold_{
145       DEFAULT_DURATION_THRESHOLD};
146 
147   // Mark registry for "measure" lookup
148   PerformanceMarkRegistryType marksRegistry_;
149   std::array<PerformanceMark, MARKS_BUFFER_SIZE> marksBuffer_;
150   size_t marksBufferPosition_{0};
151   size_t marksCount_{0};
152 
153   std::array<PerformanceMeasure, MEASURES_BUFFER_SIZE> measuresBuffer_;
154   size_t measuresBufferPosition_{0};
155   size_t measuresCount_{0};
156 
157   uint32_t droppedEntryCount_{0};
158 
159   struct EventEntry {
160     const char *name;
161     double startTime{0.0};
162     double dispatchTime{0.0};
163   };
164 
165   // Registry to store the events that are currently ongoing.
166   // Note that we could probably use a more efficient container for that,
167   // but since we only report discrete events, the volume is normally low,
168   // so a hash map should be just fine.
169   std::unordered_map<EventTag, EventEntry> eventsInFlight_;
170   std::mutex eventsInFlightMutex_;
171 
172   static EventTag sCurrentEventTag_;
173 
PerformanceEntryReporter()174   PerformanceEntryReporter() {}
175 
176   double getMarkTime(const std::string &markName) const;
177   void scheduleFlushBuffer();
178 
179   template <class T, size_t N>
180   std::vector<RawPerformanceEntry> getCircularBufferContents(
181       const std::array<T, N> &buffer,
182       size_t entryCount,
183       size_t bufferPosition,
184       const char *entryName = nullptr) const {
185     std::vector<RawPerformanceEntry> res;
186     size_t pos = (bufferPosition - entryCount + buffer.size()) % buffer.size();
187     for (size_t i = 0; i < entryCount; i++) {
188       if (entryName == nullptr || buffer[pos].name == entryName) {
189         res.push_back(buffer[pos].toRawPerformanceEntry());
190       }
191       pos = (pos + 1) % buffer.size();
192     }
193     return res;
194   }
195 
196   template <class T, size_t N>
clearCircularBuffer(std::array<T,N> & buffer,size_t & entryCount,size_t & bufferPosition,const char * entryName)197   void clearCircularBuffer(
198       std::array<T, N> &buffer,
199       size_t &entryCount,
200       size_t &bufferPosition,
201       const char *entryName) const {
202     std::array<T, N> newBuffer;
203     size_t newEntryCount = 0;
204 
205     size_t pos = (bufferPosition - entryCount + buffer.size()) % buffer.size();
206     for (size_t i = 0; i < entryCount; i++) {
207       if (buffer[pos].name != entryName) {
208         newBuffer[newEntryCount++] = buffer[pos];
209       }
210       pos = (pos + 1) % buffer.size();
211     }
212 
213     buffer = newBuffer;
214     bufferPosition = newEntryCount;
215     entryCount = newEntryCount;
216   }
217 };
218 
219 } // namespace ABI49_0_0facebook::ABI49_0_0React
220