Skip to content

Commit 9504720

Browse files
rshestfacebook-github-bot
authored andcommitted
Separate buffering per entry type in WebPerformance (#36737)
Summary: Pull Request resolved: #36737 ## Changelog: [Internal] - Use `BoundedConsumableBuffer` in WebPerformance This makes use of the `BoundedConsumableBuffer` container type, introduced in D44544057, for buffering/observing arbitrary performance entry types in `WebPerformance/PerformanceEntryReporter`, thus generalizing what was earlier done in an ad-hoc way for marks and measures, and also allowing to have both observable/retrievable/buffered property for arbitrary performance entry type (with the ultimate goal of adding custom entry types, related to e.g. startup timing, something that we currently have an ad hoc API for). Reviewed By: sammy-SC Differential Revision: D44574241 fbshipit-source-id: a858712ff1cf468914a80c99f6b82d060cb0b702
1 parent 1387179 commit 9504720

File tree

3 files changed

+176
-231
lines changed

3 files changed

+176
-231
lines changed

packages/react-native/Libraries/WebPerformance/PerformanceEntryReporter.cpp

Lines changed: 116 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -12,71 +12,66 @@
1212

1313
#include <unordered_map>
1414

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-
2015
namespace facebook::react {
2116
EventTag PerformanceEntryReporter::sCurrentEventTag_{0};
2217

23-
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-
34-
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-
4518
PerformanceEntryReporter &PerformanceEntryReporter::getInstance() {
4619
static PerformanceEntryReporter instance;
4720
return instance;
4821
}
4922

23+
PerformanceEntryReporter::PerformanceEntryReporter() {
24+
// For mark entry types we also want to keep the lookup by name, to make
25+
// sure that marks can be referenced by measures
26+
getBuffer(PerformanceEntryType::MARK).hasNameLookup = true;
27+
}
5028
void PerformanceEntryReporter::setReportingCallback(
5129
std::optional<AsyncCallback<>> callback) {
5230
callback_ = callback;
5331
}
5432

5533
void PerformanceEntryReporter::startReporting(PerformanceEntryType entryType) {
56-
int entryTypeIdx = static_cast<int>(entryType);
57-
reportingType_[entryTypeIdx] = true;
58-
durationThreshold_[entryTypeIdx] = DEFAULT_DURATION_THRESHOLD;
34+
auto &buffer = getBuffer(entryType);
35+
buffer.isReporting = true;
36+
buffer.durationThreshold = DEFAULT_DURATION_THRESHOLD;
5937
}
6038

6139
void PerformanceEntryReporter::setDurationThreshold(
6240
PerformanceEntryType entryType,
6341
double durationThreshold) {
64-
durationThreshold_[static_cast<int>(entryType)] = durationThreshold;
42+
getBuffer(entryType).durationThreshold = durationThreshold;
6543
}
6644

6745
void PerformanceEntryReporter::stopReporting(PerformanceEntryType entryType) {
68-
reportingType_[static_cast<int>(entryType)] = false;
46+
getBuffer(entryType).isReporting = false;
6947
}
7048

7149
void PerformanceEntryReporter::stopReporting() {
72-
reportingType_.fill(false);
50+
for (auto &buffer : buffers_) {
51+
buffer.isReporting = false;
52+
}
7353
}
7454

7555
GetPendingEntriesResult PerformanceEntryReporter::popPendingEntries() {
7656
std::lock_guard<std::mutex> lock(entriesMutex_);
57+
GetPendingEntriesResult res = {
58+
std::vector<RawPerformanceEntry>(), droppedEntryCount_};
59+
for (auto &buffer : buffers_) {
60+
buffer.entries.consume(res.entries);
61+
}
62+
63+
// Sort by starting time (or ending time, if starting times are equal)
64+
std::sort(
65+
res.entries.begin(),
66+
res.entries.end(),
67+
[](const RawPerformanceEntry &lhs, const RawPerformanceEntry &rhs) {
68+
if (lhs.startTime != rhs.startTime) {
69+
return lhs.startTime < rhs.startTime;
70+
} else {
71+
return lhs.duration < rhs.duration;
72+
}
73+
});
7774

78-
GetPendingEntriesResult res = {std::move(entries_), droppedEntryCount_};
79-
entries_ = {};
8075
droppedEntryCount_ = 0;
8176
return res;
8277
}
@@ -91,24 +86,39 @@ void PerformanceEntryReporter::logEntry(const RawPerformanceEntry &entry) {
9186
return;
9287
}
9388

94-
if (entry.duration < durationThreshold_[entry.entryType]) {
89+
std::lock_guard<std::mutex> lock(entriesMutex_);
90+
91+
auto &buffer = buffers_[entry.entryType];
92+
93+
if (entry.duration < buffer.durationThreshold) {
9594
// The entries duration is lower than the desired reporting threshold, skip
96-
// return;
95+
return;
9796
}
9897

99-
std::lock_guard<std::mutex> lock(entriesMutex_);
98+
if (buffer.hasNameLookup) {
99+
auto overwriteCandidate = buffer.entries.getNextOverwriteCandidate();
100+
if (overwriteCandidate != nullptr) {
101+
auto it = buffer.nameLookup.find(overwriteCandidate);
102+
if (it != buffer.nameLookup.end() && *it == overwriteCandidate) {
103+
buffer.nameLookup.erase(it);
104+
}
105+
}
106+
}
100107

101-
if (entries_.size() == MAX_ENTRY_BUFFER_SIZE) {
108+
auto pushResult = buffer.entries.add(std::move(entry));
109+
if (pushResult ==
110+
BoundedConsumableBuffer<RawPerformanceEntry>::PushStatus::DROP) {
102111
// Start dropping entries once reached maximum buffer size.
103112
// The number of dropped entries will be reported back to the corresponding
104113
// PerformanceObserver callback.
105114
droppedEntryCount_ += 1;
106-
return;
107115
}
108116

109-
entries_.emplace_back(entry);
117+
if (buffer.hasNameLookup) {
118+
buffer.nameLookup.insert(&buffer.entries.back());
119+
}
110120

111-
if (entries_.size() == 1) {
121+
if (buffer.entries.getNumToConsume() == 1) {
112122
// If the buffer was empty, it signals that JS side just has possibly
113123
// consumed it and is ready to get more
114124
scheduleFlushBuffer();
@@ -119,95 +129,75 @@ void PerformanceEntryReporter::mark(
119129
const std::string &name,
120130
double startTime,
121131
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});
132+
logEntry(RawPerformanceEntry{
133+
name,
134+
static_cast<int>(PerformanceEntryType::MARK),
135+
startTime,
136+
duration,
137+
std::nullopt,
138+
std::nullopt,
139+
std::nullopt});
149140
}
150141

151142
void PerformanceEntryReporter::clearEntries(
152143
PerformanceEntryType entryType,
153144
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();
145+
if (entryType == PerformanceEntryType::UNDEFINED) {
146+
// Clear all entry types
147+
for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) {
148+
clearEntries(static_cast<PerformanceEntryType>(i), entryName);
166149
}
167-
}
168-
169-
if (entryType == PerformanceEntryType::MEASURE ||
170-
entryType == PerformanceEntryType::UNDEFINED) {
150+
} else {
151+
auto &buffer = getBuffer(entryType);
171152
if (entryName != nullptr) {
172-
clearCircularBuffer(
173-
measuresBuffer_, measuresCount_, measuresBufferPosition_, entryName);
153+
if (buffer.hasNameLookup) {
154+
RawPerformanceEntry entry{
155+
entryName,
156+
static_cast<int>(entryType),
157+
0.0,
158+
0.0,
159+
std::nullopt,
160+
std::nullopt,
161+
std::nullopt};
162+
buffer.nameLookup.erase(&entry);
163+
}
164+
buffer.entries.clear([entryName](const RawPerformanceEntry &entry) {
165+
return std::strcmp(entry.name.c_str(), entryName) == 0;
166+
});
174167
} else {
175-
measuresCount_ = 0;
168+
buffer.entries.clear();
169+
buffer.nameLookup.clear();
176170
}
177171
}
172+
}
178173

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--;
174+
void PerformanceEntryReporter::getEntries(
175+
PerformanceEntryType entryType,
176+
const char *entryName,
177+
std::vector<RawPerformanceEntry> &res) const {
178+
if (entryType == PerformanceEntryType::UNDEFINED) {
179+
// Collect all entry types
180+
for (int i = 1; i < NUM_PERFORMANCE_ENTRY_TYPES; i++) {
181+
getEntries(static_cast<PerformanceEntryType>(i), entryName, res);
182+
}
183+
} else {
184+
const auto &entries = getBuffer(entryType).entries;
185+
if (entryName == nullptr) {
186+
entries.getEntries(res);
187+
} else {
188+
entries.getEntries(res, [entryName](const RawPerformanceEntry &entry) {
189+
return std::strcmp(entry.name.c_str(), entryName) == 0;
190+
});
187191
}
188-
pos--;
189192
}
190-
entries_.resize(lastPos + 1);
191193
}
192194

193195
std::vector<RawPerformanceEntry> PerformanceEntryReporter::getEntries(
194196
PerformanceEntryType entryType,
195197
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 {};
198+
std::vector<RawPerformanceEntry> res;
199+
getEntries(entryType, entryName, res);
200+
return res;
211201
}
212202

213203
void PerformanceEntryReporter::measure(
@@ -220,13 +210,6 @@ void PerformanceEntryReporter::measure(
220210
double startTimeVal = startMark ? getMarkTime(*startMark) : startTime;
221211
double endTimeVal = endMark ? getMarkTime(*endMark) : endTime;
222212
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-
230213
logEntry(
231214
{name,
232215
static_cast<int>(PerformanceEntryType::MEASURE),
@@ -239,10 +222,19 @@ void PerformanceEntryReporter::measure(
239222

240223
double PerformanceEntryReporter::getMarkTime(
241224
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;
225+
RawPerformanceEntry mark{
226+
markName,
227+
static_cast<int>(PerformanceEntryType::MARK),
228+
0.0,
229+
0.0,
230+
std::nullopt,
231+
std::nullopt,
232+
std::nullopt};
233+
234+
const auto &marksBuffer = getBuffer(PerformanceEntryType::MARK);
235+
auto it = marksBuffer.nameLookup.find(&mark);
236+
if (it != marksBuffer.nameLookup.end()) {
237+
return (*it)->startTime;
246238
} else {
247239
return 0.0;
248240
}

0 commit comments

Comments
 (0)