mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Limit perf entry buffer size and send back 'droppedEntriesCount' to PerformanceObserver callback
Summary: Prevents scenarios when internal performance buffer may grow indefinitely (e.g. due to a broken logging), communicating back to `PerformanceObserver` the corresponding amount of dropped entries, `droppedEntriesCount`, [according to the standard](https://w3c.github.io/performance-timeline/#dom-performanceobservercallbackoptions-droppedentriescount). NOTE: The backwards compatibility check is failing, which is an orthogonal issue. I am looking into it and won't land this one before it is sorted. Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D42008409 fbshipit-source-id: 40d30e44d39e643bfb58a6254572823cb2b3b8df
This commit is contained in:
committed by
Facebook GitHub Bot
parent
70a1e2b053
commit
d09c5fd7a9
@@ -40,7 +40,7 @@ void NativePerformanceObserver::stopReporting(
|
||||
stringToPerformanceEntryType(entryType));
|
||||
}
|
||||
|
||||
std::vector<RawPerformanceEntry> NativePerformanceObserver::popPendingEntries(
|
||||
GetPendingEntriesResult NativePerformanceObserver::popPendingEntries(
|
||||
jsi::Runtime &rt) {
|
||||
return PerformanceEntryReporter::getInstance().popPendingEntries();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,17 @@ struct Bridging<RawPerformanceEntry>
|
||||
std::optional<double>,
|
||||
std::optional<double>> {};
|
||||
|
||||
using GetPendingEntriesResult =
|
||||
NativePerformanceObserverCxxBaseGetPendingEntriesResult<
|
||||
std::vector<RawPerformanceEntry>,
|
||||
uint32_t>;
|
||||
|
||||
template <>
|
||||
struct Bridging<GetPendingEntriesResult>
|
||||
: NativePerformanceObserverCxxBaseGetPendingEntriesResultBridging<
|
||||
std::vector<RawPerformanceEntry>,
|
||||
uint32_t> {};
|
||||
|
||||
#pragma mark - implementation
|
||||
|
||||
class NativePerformanceObserver
|
||||
@@ -51,7 +62,7 @@ class NativePerformanceObserver
|
||||
|
||||
void stopReporting(jsi::Runtime &rt, std::string entryType);
|
||||
|
||||
std::vector<RawPerformanceEntry> popPendingEntries(jsi::Runtime &rt);
|
||||
GetPendingEntriesResult popPendingEntries(jsi::Runtime &rt);
|
||||
|
||||
void setOnPerformanceEntryCallback(
|
||||
jsi::Runtime &rt,
|
||||
|
||||
@@ -31,10 +31,15 @@ export type RawPerformanceEntry = {|
|
||||
interactionId?: number,
|
||||
|};
|
||||
|
||||
export type GetPendingEntriesResult = {|
|
||||
entries: $ReadOnlyArray<RawPerformanceEntry>,
|
||||
droppedEntriesCount: number,
|
||||
|};
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+startReporting: (entryType: string) => void;
|
||||
+stopReporting: (entryType: string) => void;
|
||||
+popPendingEntries: () => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+popPendingEntries: () => GetPendingEntriesResult;
|
||||
+setOnPerformanceEntryCallback: (callback?: () => void) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
|
||||
#include "NativePerformanceObserver.h"
|
||||
|
||||
// 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 {
|
||||
PerformanceEntryReporter &PerformanceEntryReporter::getInstance() {
|
||||
static PerformanceEntryReporter instance;
|
||||
@@ -33,10 +38,11 @@ const std::vector<RawPerformanceEntry>
|
||||
return entries_;
|
||||
}
|
||||
|
||||
std::vector<RawPerformanceEntry> PerformanceEntryReporter::popPendingEntries() {
|
||||
auto entriesToReturn = std::move(entries_);
|
||||
GetPendingEntriesResult PerformanceEntryReporter::popPendingEntries() {
|
||||
GetPendingEntriesResult res = {std::move(entries_), droppedEntryCount_};
|
||||
entries_ = {};
|
||||
return entriesToReturn;
|
||||
droppedEntryCount_ = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
void PerformanceEntryReporter::clearPendingEntries() {
|
||||
@@ -48,6 +54,14 @@ void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entries_.size() == MAX_ENTRY_BUFFER_SIZE) {
|
||||
// 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 (entries_.size() == 1) {
|
||||
@@ -63,21 +77,21 @@ void PerformanceEntryReporter::mark(
|
||||
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();
|
||||
PerformanceMark &mark = marksBuffer_[marksBufferPosition_];
|
||||
marksBufferPosition_ = (marksBufferPosition_ + 1) % marksBuffer_.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);
|
||||
auto it = marksRegistry_.find(&mark);
|
||||
if (it != marksRegistry_.end() && *it == &mark) {
|
||||
marksRegistry_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
mark.name = name;
|
||||
mark.timeStamp = startTime;
|
||||
marks_registry_.insert(&mark);
|
||||
marksRegistry_.insert(&mark);
|
||||
|
||||
logEntry(
|
||||
{name,
|
||||
@@ -93,13 +107,13 @@ void PerformanceEntryReporter::clearMarks(
|
||||
const std::optional<std::string> &markName) {
|
||||
if (markName) {
|
||||
PerformanceMark mark{{*markName, 0}};
|
||||
marks_registry_.erase(&mark);
|
||||
marksRegistry_.erase(&mark);
|
||||
clearEntries([&markName](const RawPerformanceEntry &entry) {
|
||||
return entry.entryType == static_cast<int>(PerformanceEntryType::MARK) &&
|
||||
entry.name == markName;
|
||||
});
|
||||
} else {
|
||||
marks_registry_.clear();
|
||||
marksRegistry_.clear();
|
||||
clearEntries([](const RawPerformanceEntry &entry) {
|
||||
return entry.entryType == static_cast<int>(PerformanceEntryType::MARK);
|
||||
});
|
||||
@@ -135,7 +149,7 @@ void PerformanceEntryReporter::clearMeasures(
|
||||
entry.name == measureName;
|
||||
});
|
||||
} else {
|
||||
marks_registry_.clear();
|
||||
marksRegistry_.clear();
|
||||
clearEntries([](const RawPerformanceEntry &entry) {
|
||||
return entry.entryType == static_cast<int>(PerformanceEntryType::MEASURE);
|
||||
});
|
||||
@@ -145,8 +159,8 @@ void PerformanceEntryReporter::clearMeasures(
|
||||
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()) {
|
||||
auto it = marksRegistry_.find(&mark);
|
||||
if (it != marksRegistry_.end()) {
|
||||
return (*it)->timeStamp;
|
||||
} else {
|
||||
return 0.0;
|
||||
|
||||
@@ -64,7 +64,7 @@ class PerformanceEntryReporter {
|
||||
void stopReporting(PerformanceEntryType entryType);
|
||||
|
||||
const std::vector<RawPerformanceEntry> &getPendingEntries() const;
|
||||
std::vector<RawPerformanceEntry> popPendingEntries();
|
||||
GetPendingEntriesResult popPendingEntries();
|
||||
void clearPendingEntries();
|
||||
void logEntry(const RawPerformanceEntry &entry);
|
||||
|
||||
@@ -72,6 +72,10 @@ class PerformanceEntryReporter {
|
||||
return reportingType_[static_cast<int>(entryType)];
|
||||
}
|
||||
|
||||
uint32_t getDroppedEntryCount() const {
|
||||
return droppedEntryCount_;
|
||||
}
|
||||
|
||||
void mark(const std::string &name, double startTime, double duration);
|
||||
void clearMarks(const std::optional<std::string> &markName);
|
||||
|
||||
@@ -96,9 +100,10 @@ class PerformanceEntryReporter {
|
||||
std::array<bool, (size_t)PerformanceEntryType::_COUNT> reportingType_{false};
|
||||
|
||||
// Mark registry for "measure" lookup
|
||||
PerformanceMarkRegistryType marks_registry_;
|
||||
std::array<PerformanceMark, MARKS_BUFFER_SIZE> marks_buffer_;
|
||||
size_t marks_buffer_position_{0};
|
||||
PerformanceMarkRegistryType marksRegistry_;
|
||||
std::array<PerformanceMark, MARKS_BUFFER_SIZE> marksBuffer_;
|
||||
size_t marksBufferPosition_{0};
|
||||
uint32_t droppedEntryCount_{0};
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -112,6 +112,8 @@ export class PerformanceObserverEntryList {
|
||||
export type PerformanceObserverCallback = (
|
||||
list: PerformanceObserverEntryList,
|
||||
observer: PerformanceObserver,
|
||||
// The number of buffered entries which got dropped from the buffer due to the buffer being full:
|
||||
droppedEntryCount?: number,
|
||||
) => void;
|
||||
|
||||
export type PerformanceObserverInit =
|
||||
@@ -137,7 +139,9 @@ const onPerformanceEntry = () => {
|
||||
if (!NativePerformanceObserver) {
|
||||
return;
|
||||
}
|
||||
const rawEntries = NativePerformanceObserver.popPendingEntries() ?? [];
|
||||
const entryResult = NativePerformanceObserver.popPendingEntries();
|
||||
const rawEntries = entryResult?.entries ?? [];
|
||||
const droppedEntriesCount = entryResult?.droppedEntriesCount;
|
||||
if (rawEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -149,6 +153,7 @@ const onPerformanceEntry = () => {
|
||||
observerConfig.callback(
|
||||
new PerformanceObserverEntryList(entriesForObserver),
|
||||
observer,
|
||||
droppedEntriesCount,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user