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 #include "ABI49_0_0PerformanceEntryReporter.h"
9 #include <ABI49_0_0cxxreact/ABI49_0_0JSExecutor.h>
10 #include <ABI49_0_0React/ABI49_0_0renderer/core/EventLogger.h>
11 #include "ABI49_0_0NativePerformanceObserver.h"
12 
13 #include <unordered_map>
14 
15 // All the unflushed entries beyond this amount will get discarded, with
16 // the amount of discarded ones sent back to the observers' callbacks as
17 // "droppedEntryCount" value
18 static constexpr size_t MAX_ENTRY_BUFFER_SIZE = 1024;
19 
20 namespace ABI49_0_0facebook::ABI49_0_0React {
21 EventTag PerformanceEntryReporter::sCurrentEventTag_{0};
22 
toRawPerformanceEntry() const23 RawPerformanceEntry PerformanceMark::toRawPerformanceEntry() const {
24   return {
25       name,
26       static_cast<int>(PerformanceEntryType::MARK),
27       timeStamp,
28       0.0,
29       std::nullopt,
30       std::nullopt,
31       std::nullopt};
32 }
33 
toRawPerformanceEntry() const34 RawPerformanceEntry PerformanceMeasure::toRawPerformanceEntry() const {
35   return {
36       name,
37       static_cast<int>(PerformanceEntryType::MEASURE),
38       timeStamp,
39       duration,
40       std::nullopt,
41       std::nullopt,
42       std::nullopt};
43 }
44 
getInstance()45 PerformanceEntryReporter &PerformanceEntryReporter::getInstance() {
46   static PerformanceEntryReporter instance;
47   return instance;
48 }
49 
setReportingCallback(std::optional<AsyncCallback<>> callback)50 void PerformanceEntryReporter::setReportingCallback(
51     std::optional<AsyncCallback<>> callback) {
52   callback_ = callback;
53 }
54 
startReporting(PerformanceEntryType entryType)55 void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) {
56   int entryTypeIdx = static_cast<int>(entryType);
57   reportingType_[entryTypeIdx] = true;
58   durationThreshold_[entryTypeIdx] = DEFAULT_DURATION_THRESHOLD;
59 }
60 
setDurationThreshold(PerformanceEntryType entryType,double durationThreshold)61 void PerformanceEntryReporter::setDurationThreshold(
62     PerformanceEntryType entryType,
63     double durationThreshold) {
64   durationThreshold_[static_cast<int>(entryType)] = durationThreshold;
65 }
66 
stopReporting(PerformanceEntryType entryType)67 void PerformanceEntryReporter::stopReporting(PerformanceEntryType entryType) {
68   reportingType_[static_cast<int>(entryType)] = false;
69 }
70 
stopReporting()71 void PerformanceEntryReporter::stopReporting() {
72   reportingType_.fill(false);
73 }
74 
popPendingEntries()75 GetPendingEntriesResult PerformanceEntryReporter::popPendingEntries() {
76   std::lock_guard<std::mutex> lock(entriesMutex_);
77 
78   GetPendingEntriesResult res = {std::move(entries_), droppedEntryCount_};
79   entries_ = {};
80   droppedEntryCount_ = 0;
81   return res;
82 }
83 
logEntry(const RawPerformanceEntry & entry)84 void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) {
85   const auto entryType = static_cast<PerformanceEntryType>(entry.entryType);
86   if (entryType == PerformanceEntryType::EVENT) {
87     eventCounts_[entry.name]++;
88   }
89 
90   if (!isReporting(entryType)) {
91     return;
92   }
93 
94   if (entry.duration < durationThreshold_[entry.entryType]) {
95     // The entries duration is lower than the desired reporting threshold, skip
96     // return;
97   }
98 
99   std::lock_guard<std::mutex> lock(entriesMutex_);
100 
101   if (entries_.size() == MAX_ENTRY_BUFFER_SIZE) {
102     // Start dropping entries once reached maximum buffer size.
103     // The number of dropped entries will be reported back to the corresponding
104     // PerformanceObserver callback.
105     droppedEntryCount_ += 1;
106     return;
107   }
108 
109   entries_.emplace_back(entry);
110 
111   if (entries_.size() == 1) {
112     // If the buffer was empty, it signals that JS side just has possibly
113     // consumed it and is ready to get more
114     scheduleFlushBuffer();
115   }
116 }
117 
mark(const std::string & name,double startTime,double duration)118 void PerformanceEntryReporter::mark(
119     const std::string &name,
120     double startTime,
121     double duration) {
122   // Register the mark for further possible "measure" lookup, as well as add
123   // it to a circular buffer:
124   PerformanceMark &mark = marksBuffer_[marksBufferPosition_];
125   marksBufferPosition_ = (marksBufferPosition_ + 1) % marksBuffer_.size();
126   marksCount_ = std::min(marksBuffer_.size(), marksCount_ + 1);
127 
128   if (!mark.name.empty()) {
129     // Drop off the oldest mark out of the queue, but only if that's indeed the
130     // oldest one
131     auto it = marksRegistry_.find(&mark);
132     if (it != marksRegistry_.end() && *it == &mark) {
133       marksRegistry_.erase(it);
134     }
135   }
136 
137   mark.name = name;
138   mark.timeStamp = startTime;
139   marksRegistry_.insert(&mark);
140 
141   logEntry(
142       {name,
143        static_cast<int>(PerformanceEntryType::MARK),
144        startTime,
145        duration,
146        std::nullopt,
147        std::nullopt,
148        std::nullopt});
149 }
150 
clearEntries(PerformanceEntryType entryType,const char * entryName)151 void PerformanceEntryReporter::clearEntries(
152     PerformanceEntryType entryType,
153     const char *entryName) {
154   if (entryType == PerformanceEntryType::MARK ||
155       entryType == PerformanceEntryType::UNDEFINED) {
156     if (entryName != nullptr) {
157       // remove a named mark from the mark/measure registry
158       PerformanceMark mark{{entryName, 0}};
159       marksRegistry_.erase(&mark);
160 
161       clearCircularBuffer(
162           marksBuffer_, marksCount_, marksBufferPosition_, entryName);
163     } else {
164       marksCount_ = 0;
165       marksRegistry_.clear();
166     }
167   }
168 
169   if (entryType == PerformanceEntryType::MEASURE ||
170       entryType == PerformanceEntryType::UNDEFINED) {
171     if (entryName != nullptr) {
172       clearCircularBuffer(
173           measuresBuffer_, measuresCount_, measuresBufferPosition_, entryName);
174     } else {
175       measuresCount_ = 0;
176     }
177   }
178 
179   int lastPos = entries_.size() - 1;
180   int pos = lastPos;
181   while (pos >= 0) {
182     const RawPerformanceEntry &entry = entries_[pos];
183     if (entry.entryType == static_cast<int32_t>(entryType) &&
184         (entryName == nullptr || entry.name == entryName)) {
185       entries_[pos] = entries_[lastPos];
186       lastPos--;
187     }
188     pos--;
189   }
190   entries_.resize(lastPos + 1);
191 }
192 
getEntries(PerformanceEntryType entryType,const char * entryName) const193 std::vector<RawPerformanceEntry> PerformanceEntryReporter::getEntries(
194     PerformanceEntryType entryType,
195     const char *entryName) const {
196   if (entryType == PerformanceEntryType::MARK) {
197     return getCircularBufferContents(
198         marksBuffer_, marksCount_, marksBufferPosition_, entryName);
199   } else if (entryType == PerformanceEntryType::MEASURE) {
200     return getCircularBufferContents(
201         measuresBuffer_, measuresCount_, measuresBufferPosition_, entryName);
202   } else if (entryType == PerformanceEntryType::UNDEFINED) {
203     auto marks = getCircularBufferContents(
204         marksBuffer_, marksCount_, marksBufferPosition_, entryName);
205     auto measures = getCircularBufferContents(
206         measuresBuffer_, measuresCount_, measuresBufferPosition_, entryName);
207     marks.insert(marks.end(), measures.begin(), measures.end());
208     return marks;
209   }
210   return {};
211 }
212 
measure(const std::string & name,double startTime,double endTime,const std::optional<double> & duration,const std::optional<std::string> & startMark,const std::optional<std::string> & endMark)213 void PerformanceEntryReporter::measure(
214     const std::string &name,
215     double startTime,
216     double endTime,
217     const std::optional<double> &duration,
218     const std::optional<std::string> &startMark,
219     const std::optional<std::string> &endMark) {
220   double startTimeVal = startMark ? getMarkTime(*startMark) : startTime;
221   double endTimeVal = endMark ? getMarkTime(*endMark) : endTime;
222   double durationVal = duration ? *duration : endTimeVal - startTimeVal;
223 
224   measuresBuffer_[measuresBufferPosition_] =
225       PerformanceMeasure{name, startTime, endTime};
226   measuresBufferPosition_ =
227       (measuresBufferPosition_ + 1) % measuresBuffer_.size();
228   measuresCount_ = std::min(measuresBuffer_.size(), measuresCount_ + 1);
229 
230   logEntry(
231       {name,
232        static_cast<int>(PerformanceEntryType::MEASURE),
233        startTimeVal,
234        durationVal,
235        std::nullopt,
236        std::nullopt,
237        std::nullopt});
238 }
239 
getMarkTime(const std::string & markName) const240 double PerformanceEntryReporter::getMarkTime(
241     const std::string &markName) const {
242   PerformanceMark mark{{std::move(markName), 0}};
243   auto it = marksRegistry_.find(&mark);
244   if (it != marksRegistry_.end()) {
245     return (*it)->timeStamp;
246   } else {
247     return 0.0;
248   }
249 }
250 
event(std::string name,double startTime,double duration,double processingStart,double processingEnd,uint32_t interactionId)251 void PerformanceEntryReporter::event(
252     std::string name,
253     double startTime,
254     double duration,
255     double processingStart,
256     double processingEnd,
257     uint32_t interactionId) {
258   logEntry(
259       {std::move(name),
260        static_cast<int>(PerformanceEntryType::EVENT),
261        startTime,
262        duration,
263        processingStart,
264        processingEnd,
265        interactionId});
266 }
267 
scheduleFlushBuffer()268 void PerformanceEntryReporter::scheduleFlushBuffer() {
269   if (callback_) {
270     callback_->callWithPriority(SchedulerPriority::IdlePriority);
271   }
272 }
273 
274 struct StrKey {
275   uint32_t key;
StrKeyABI49_0_0facebook::ABI49_0_0React::StrKey276   constexpr StrKey(const char *s)
277       : key(folly::hash::fnv32_buf(s, sizeof(s) - 1)) {}
278 
operator ==ABI49_0_0facebook::ABI49_0_0React::StrKey279   constexpr bool operator==(const StrKey &rhs) const {
280     return key == rhs.key;
281   }
282 };
283 
284 struct StrKeyHash {
operator ()ABI49_0_0facebook::ABI49_0_0React::StrKeyHash285   constexpr size_t operator()(const StrKey &strKey) const {
286     return static_cast<size_t>(strKey.key);
287   }
288 };
289 
290 // Supported events for reporting, see
291 // https://www.w3.org/TR/event-timing/#sec-events-exposed
292 // Not all of these are currently supported by ABI49_0_0RN, but we map them anyway for
293 // future-proofing.
294 using SupportedEventTypeRegistry =
295     std::unordered_map<StrKey, const char *, StrKeyHash>;
296 
getSupportedEvents()297 static const SupportedEventTypeRegistry &getSupportedEvents() {
298   static SupportedEventTypeRegistry SUPPORTED_EVENTS = {
299       {"topAuxClick", "auxclick"},
300       {"topClick", "click"},
301       {"topContextMenu", "contextmenu"},
302       {"topDblClick", "dblclick"},
303       {"topMouseDown", "mousedown"},
304       {"topMouseEnter", "mouseenter"},
305       {"topMouseLeave", "mouseleave"},
306       {"topMouseOut", "mouseout"},
307       {"topMouseOver", "mouseover"},
308       {"topMouseUp", "mouseup"},
309       {"topPointerOver", "pointerover"},
310       {"topPointerEnter", "pointerenter"},
311       {"topPointerDown", "pointerdown"},
312       {"topPointerUp", "pointerup"},
313       {"topPointerCancel", "pointercancel"},
314       {"topPointerOut", "pointerout"},
315       {"topPointerLeave", "pointerleave"},
316       {"topGotPointerCapture", "gotpointercapture"},
317       {"topLostPointerCapture", "lostpointercapture"},
318       {"topTouchStart", "touchstart"},
319       {"topTouchEnd", "touchend"},
320       {"topTouchCancel", "touchcancel"},
321       {"topKeyDown", "keydown"},
322       {"topKeyPress", "keypress"},
323       {"topKeyUp", "keyup"},
324       {"topBeforeInput", "beforeinput"},
325       {"topInput", "input"},
326       {"topCompositionStart", "compositionstart"},
327       {"topCompositionUpdate", "compositionupdate"},
328       {"topCompositionEnd", "compositionend"},
329       {"topDragStart", "dragstart"},
330       {"topDragEnd", "dragend"},
331       {"topDragEnter", "dragenter"},
332       {"topDragLeave", "dragleave"},
333       {"topDragOver", "dragover"},
334       {"topDrop", "drop"},
335   };
336   return SUPPORTED_EVENTS;
337 }
338 
onEventStart(const char * name)339 EventTag PerformanceEntryReporter::onEventStart(const char *name) {
340   if (!isReportingEvents()) {
341     return 0;
342   }
343   const auto &supportedEvents = getSupportedEvents();
344   auto it = supportedEvents.find(name);
345   if (it == supportedEvents.end()) {
346     return 0;
347   }
348 
349   const char *reportedName = it->second;
350 
351   sCurrentEventTag_++;
352   if (sCurrentEventTag_ == 0) {
353     // The tag wrapped around (which is highly unlikely, but still)
354     sCurrentEventTag_ = 1;
355   }
356 
357   auto timeStamp = JSExecutor::performanceNow();
358   {
359     std::lock_guard<std::mutex> lock(eventsInFlightMutex_);
360     eventsInFlight_.emplace(std::make_pair(
361         sCurrentEventTag_, EventEntry{reportedName, timeStamp, 0.0}));
362   }
363   return sCurrentEventTag_;
364 }
365 
onEventDispatch(EventTag tag)366 void PerformanceEntryReporter::onEventDispatch(EventTag tag) {
367   if (!isReportingEvents() || tag == 0) {
368     return;
369   }
370   auto timeStamp = JSExecutor::performanceNow();
371   {
372     std::lock_guard<std::mutex> lock(eventsInFlightMutex_);
373     auto it = eventsInFlight_.find(tag);
374     if (it != eventsInFlight_.end()) {
375       it->second.dispatchTime = timeStamp;
376     }
377   }
378 }
379 
onEventEnd(EventTag tag)380 void PerformanceEntryReporter::onEventEnd(EventTag tag) {
381   if (!isReportingEvents() || tag == 0) {
382     return;
383   }
384   auto timeStamp = JSExecutor::performanceNow();
385   {
386     std::lock_guard<std::mutex> lock(eventsInFlightMutex_);
387     auto it = eventsInFlight_.find(tag);
388     if (it == eventsInFlight_.end()) {
389       return;
390     }
391     auto &entry = it->second;
392     auto &name = entry.name;
393 
394     // TODO: Define the way to assign interaction IDs to the event chains
395     // (T141358175)
396     const uint32_t interactionId = 0;
397     event(
398         name,
399         entry.startTime,
400         timeStamp - entry.startTime,
401         entry.dispatchTime,
402         timeStamp,
403         interactionId);
404     eventsInFlight_.erase(it);
405   }
406 }
407 
408 } // namespace ABI49_0_0facebook::ABI49_0_0React
409