Skip to content

Commit d6ee54f

Browse files
hoxyqmeta-codesync[bot]
authored andcommitted
Introduce TimeWindowedBuffer (facebook#54679)
Summary: Pull Request resolved: facebook#54679 # Changelog: [Internal] Adding a data structure that could be configured to preserve entries that are inside of a specified time window. At the same time, this data structure could be used as a simple buffer, if no arguments were specified during constructing. This follows the approach that was added to `PerformanceTracer`, but it doesn't use pointers to avoid dealing with updates during copies or moves. Reviewed By: sbuggay Differential Revision: D87776299 fbshipit-source-id: d20bf6dd08af7971aaf34ffc11ec5993b8edaf4b
1 parent ec92f12 commit d6ee54f

File tree

2 files changed

+510
-0
lines changed

2 files changed

+510
-0
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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 <algorithm>
11+
#include <functional>
12+
#include <optional>
13+
#include <vector>
14+
15+
#include <react/timing/primitives.h>
16+
17+
namespace facebook::react::jsinspector_modern::tracing {
18+
19+
/**
20+
* The currentBufferStartTime_ is initialized once first element is pushed.
21+
*/
22+
constexpr HighResTimeStamp kCurrentBufferStartTimeUninitialized = HighResTimeStamp::min();
23+
24+
template <typename T>
25+
class TimeWindowedBuffer {
26+
public:
27+
using TimestampAccessor = std::function<HighResTimeStamp(const T &)>;
28+
29+
TimeWindowedBuffer() : timestampAccessor_(std::nullopt), windowSize_(std::nullopt) {}
30+
31+
TimeWindowedBuffer(TimestampAccessor timestampAccessor, HighResDuration windowSize)
32+
: timestampAccessor_(std::move(timestampAccessor)), windowSize_(windowSize)
33+
{
34+
}
35+
36+
void push(const T &element)
37+
{
38+
if (timestampAccessor_) {
39+
auto timestamp = (*timestampAccessor_)(element);
40+
enqueueElement(element, timestamp);
41+
} else {
42+
enqueueElement(element, HighResTimeStamp::now());
43+
}
44+
}
45+
46+
void push(T &&element)
47+
{
48+
if (timestampAccessor_) {
49+
auto timestamp = (*timestampAccessor_)(element);
50+
enqueueElement(std::move(element), timestamp);
51+
} else {
52+
enqueueElement(std::move(element), HighResTimeStamp::now());
53+
}
54+
}
55+
56+
void clear()
57+
{
58+
primaryBuffer_.clear();
59+
alternativeBuffer_.clear();
60+
currentBufferIndex_ = BufferIndex::Primary;
61+
currentBufferStartTime_ = kCurrentBufferStartTimeUninitialized;
62+
}
63+
64+
/**
65+
* Forces immediate removal of elements that are outside the time window.
66+
* The right boundary of the window is the reference timestamp passed as an argument.
67+
*/
68+
std::vector<T> pruneExpiredAndExtract(HighResTimeStamp windowRightBoundary = HighResTimeStamp::now())
69+
{
70+
std::vector<T> result;
71+
72+
for (auto &wrappedElement : getPreviousBuffer()) {
73+
if (isInsideTimeWindow(wrappedElement, windowRightBoundary)) {
74+
result.push_back(std::move(wrappedElement.element));
75+
}
76+
}
77+
78+
for (auto &wrappedElement : getCurrentBuffer()) {
79+
if (isInsideTimeWindow(wrappedElement, windowRightBoundary)) {
80+
result.push_back(std::move(wrappedElement.element));
81+
}
82+
}
83+
84+
clear();
85+
return result;
86+
}
87+
88+
private:
89+
enum class BufferIndex { Primary, Alternative };
90+
91+
struct TimestampedElement {
92+
T element;
93+
HighResTimeStamp timestamp;
94+
};
95+
96+
std::vector<TimestampedElement> &getCurrentBuffer()
97+
{
98+
return currentBufferIndex_ == BufferIndex::Primary ? primaryBuffer_ : alternativeBuffer_;
99+
}
100+
101+
std::vector<TimestampedElement> &getPreviousBuffer()
102+
{
103+
return currentBufferIndex_ == BufferIndex::Primary ? alternativeBuffer_ : primaryBuffer_;
104+
}
105+
106+
void enqueueElement(const T &element, HighResTimeStamp timestamp)
107+
{
108+
if (windowSize_) {
109+
if (currentBufferStartTime_ == kCurrentBufferStartTimeUninitialized) {
110+
currentBufferStartTime_ = timestamp;
111+
} else if (timestamp > currentBufferStartTime_ + *windowSize_) {
112+
// We moved past the current buffer. We need to switch the other buffer as current.
113+
currentBufferIndex_ =
114+
currentBufferIndex_ == BufferIndex::Primary ? BufferIndex::Alternative : BufferIndex::Primary;
115+
getCurrentBuffer().clear();
116+
currentBufferStartTime_ = timestamp;
117+
}
118+
}
119+
120+
getCurrentBuffer().push_back({element, timestamp});
121+
}
122+
123+
void enqueueElement(T &&element, HighResTimeStamp timestamp)
124+
{
125+
if (windowSize_) {
126+
if (currentBufferStartTime_ == kCurrentBufferStartTimeUninitialized) {
127+
currentBufferStartTime_ = timestamp;
128+
} else if (timestamp > currentBufferStartTime_ + *windowSize_) {
129+
// We moved past the current buffer. We need to switch the other buffer as current.
130+
currentBufferIndex_ =
131+
currentBufferIndex_ == BufferIndex::Primary ? BufferIndex::Alternative : BufferIndex::Primary;
132+
getCurrentBuffer().clear();
133+
currentBufferStartTime_ = timestamp;
134+
}
135+
}
136+
137+
getCurrentBuffer().push_back({std::move(element), timestamp});
138+
}
139+
140+
bool isInsideTimeWindow(const TimestampedElement &element, HighResTimeStamp windowRightBoundary) const
141+
{
142+
if (!windowSize_) {
143+
return true;
144+
}
145+
146+
return element.timestamp >= windowRightBoundary - *windowSize_ && element.timestamp <= windowRightBoundary;
147+
}
148+
149+
std::optional<TimestampAccessor> timestampAccessor_;
150+
std::optional<HighResDuration> windowSize_;
151+
152+
std::vector<TimestampedElement> primaryBuffer_;
153+
std::vector<TimestampedElement> alternativeBuffer_;
154+
BufferIndex currentBufferIndex_ = BufferIndex::Primary;
155+
HighResTimeStamp currentBufferStartTime_{kCurrentBufferStartTimeUninitialized};
156+
};
157+
158+
} // namespace facebook::react::jsinspector_modern::tracing

0 commit comments

Comments
 (0)