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