userver: userver/utils/statistics/recentperiod.hpp Source File
Loading...
Searching...
No Matches
recentperiod.hpp
1#pragma once
2
3#include <atomic>
4#include <chrono>
5#include <type_traits>
6
7#include <userver/utils/datetime.hpp>
8#include <userver/utils/fixed_array.hpp>
9#include <userver/utils/statistics/fwd.hpp>
10#include <userver/utils/statistics/recentperiod_detail.hpp>
11
12USERVER_NAMESPACE_BEGIN
13
14namespace utils::statistics {
15
16/** \brief Class maintains circular buffer of Counters
17 *
18 * At any time current Counter is accessible for modification via
19 * GetCurrentCounter().
20 * Counter can provide a Reset() member function to clear contents.
21 * @see utils::statistics::Percentile
22 */
23template <typename Counter, typename Result, typename Timer = utils::datetime::SteadyClock>
24class RecentPeriod {
25public:
26 using Duration = typename Timer::duration;
27
28 static_assert(
29 (detail::kResultWantsAddFunction<Result, Counter, Duration> || detail::kResultCanUseAddAssign<Result, Counter>),
30 "The Result template type argument must provide either Add(Counter, "
31 "Duration, Duration) function or add assignment operator"
32 );
33
34 static constexpr bool kUseAddFunction = detail::kResultWantsAddFunction<Result, Counter, Duration>;
35
36 /**
37 * @param epoch_duration duration of epoch.
38 * @param max_duration max duration to calculate statistics for
39 * must be multiple of epoch_duration.
40 */
41 RecentPeriod(Duration epoch_duration = std::chrono::seconds(5), Duration max_duration = std::chrono::seconds(60))
44 epoch_index_(0),
46
47 Counter& GetCurrentCounter() { return items_[GetCurrentIndex()].counter; }
48
49 Counter& GetPreviousCounter(int epochs_ago) { return items_[GetPreviousIndex(epochs_ago)].counter; }
50
51 /** \brief Aggregates counters within given time range
52 *
53 * @param duration Time range. Special value Duration::min() -> use
54 * whole RecentPeriod range.
55 * @param with_current_epoch Include current (possibly unfinished) counter
56 * into aggregation
57 *
58 * Type Result must have method Add(Counter, Duration, Duration) or allow
59 * addition of counter values
60 */
61 // NOLINTNEXTLINE(readability-const-return-type)
62 const Result GetStatsForPeriod(Duration duration = Duration::min(), bool with_current_epoch = false) const {
63 if (duration == Duration::min()) {
64 duration = max_duration_;
65 }
66
67 Result result{};
68 Duration now = Timer::now().time_since_epoch();
69 Duration current_epoch = GetEpochForDuration(now);
70 Duration start_epoch = current_epoch - duration;
71 Duration first_epoch_duration = now - current_epoch;
72 std::size_t index = epoch_index_.load();
73
74 for (std::size_t i = 0; i < items_.size(); i++, index = (index + items_.size() - 1) % items_.size()) {
75 Duration epoch = items_[index].epoch;
76
77 if (epoch > current_epoch) continue;
78 if (epoch == current_epoch && !with_current_epoch) continue;
79 if (epoch < start_epoch) break;
80
81 if constexpr (kUseAddFunction) {
82 Duration this_epoch_duration = (i == 0) ? first_epoch_duration : epoch_duration_;
83
84 Duration before_this_epoch_duration = epoch - start_epoch;
85 result.Add(items_[index].counter, this_epoch_duration, before_this_epoch_duration);
86 } else {
87 result += items_[index].counter;
88 }
89 }
90
91 return result;
92 }
93
94 Duration GetEpochDuration() const { return epoch_duration_; }
95
96 Duration GetMaxDuration() const { return max_duration_; }
97
98 void UpdateEpochIfOld() { [[maybe_unused]] auto ignore = GetCurrentIndex(); }
99
100 void Reset() {
101 for (auto& item : items_) {
102 item.Reset();
103 }
104 }
105
106private:
107 size_t GetCurrentIndex() const {
108 while (true) {
109 Duration now = Timer::now().time_since_epoch();
110 Duration epoch = GetEpochForDuration(now);
111 std::size_t index = epoch_index_.load();
112 Duration bucket_epoch = items_[index].epoch.load();
113
114 if (epoch != bucket_epoch) {
115 std::size_t new_index = (index + 1) % items_.size();
116
117 if (epoch_index_.compare_exchange_weak(index, new_index)) {
118 items_[new_index].epoch = epoch;
119 items_[(new_index + 1) % items_.size()].Reset();
120 return new_index;
121 }
122 } else {
123 return index;
124 }
125 }
126 }
127
128 std::size_t GetPreviousIndex(int epochs_ago) {
129 int index = static_cast<int>(GetCurrentIndex()) - epochs_ago;
130 while (index < 0) index += items_.size();
131 return index % items_.size();
132 }
133
134 Duration GetEpochForDuration(Duration duration) const {
135 auto now = std::chrono::duration_cast<Duration>(duration);
136 return now - now % epoch_duration_;
137 }
138
139 static std::size_t GetSizeForDuration(Duration epoch_duration, Duration max_duration) {
140 /* 3 = current bucket, next zero bucket and extra one to handle
141 possible race. */
142 return max_duration.count() / epoch_duration.count() + 3;
143 }
144
145 struct EpochBucket {
146 static constexpr bool kUseReset = detail::kCanReset<Counter>;
147 std::atomic<Duration> epoch;
148 Counter counter;
149
150 EpochBucket() { Reset(); }
151
152 void Reset() {
153 epoch = Duration::min();
154 if constexpr (kUseReset) {
155 counter.Reset();
156 } else {
157 counter = 0;
158 }
159 }
160 };
161
162 const Duration epoch_duration_;
163 const Duration max_duration_;
164 mutable std::atomic_size_t epoch_index_;
165 mutable utils::FixedArray<EpochBucket> items_;
166};
167
168/// @a Writer support for @a RecentPeriod
169template <typename Counter, typename Result, typename Timer>
170void DumpMetric(Writer& writer, const RecentPeriod<Counter, Result, Timer>& recent_period) {
171 writer = recent_period.GetStatsForPeriod();
172}
173
174/// Reset support for @a RecentPeriod
175template <typename Counter, typename Result, typename Timer>
176void ResetMetric(RecentPeriod<Counter, Result, Timer>& recent_period) {
177 recent_period.Reset();
178}
179
180} // namespace utils::statistics
181
182USERVER_NAMESPACE_END