11#include <userver/utils/statistics/writer.hpp>
13USERVER_NAMESPACE_BEGIN
15namespace utils::statistics {
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64template <std::size_t M,
typename Counter = std::uint32_t,
65 std::size_t ExtraBuckets = 0, std::size_t ExtraBucketSize = 500>
66class Percentile final {
68 Percentile()
noexcept {
69 for (
auto& value : values_) value.store(0, std::memory_order_relaxed);
70 for (
auto& value : extra_values_) value.store(0, std::memory_order_relaxed);
71 count_.store(0, std::memory_order_release);
74 Percentile(
const Percentile<M, Counter, ExtraBuckets, ExtraBucketSize>&
79 Percentile& operator=(
const Percentile& rhs)
noexcept {
80 if (
this == &rhs)
return *
this;
83 for (std::size_t i = 0; i < values_.size(); i++) {
84 const auto value = rhs.values_[i].load(std::memory_order_relaxed);
85 values_[i].store(value, std::memory_order_relaxed);
89 for (std::size_t i = 0; i < extra_values_.size(); i++) {
90 const auto value = rhs.extra_values_[i].load(std::memory_order_relaxed);
91 extra_values_[i].store(value, std::memory_order_relaxed);
103 if (value < values_.size()) {
104 values_[value].fetch_add(1, std::memory_order_relaxed);
106 if (!extra_values_.empty()) {
107 std::size_t extra_bucket =
108 (value - values_.size() + ExtraBucketSize / 2) / ExtraBucketSize;
109 if (extra_bucket >= extra_values_.size()) {
110 extra_bucket = extra_values_.size() - 1;
112 extra_values_[extra_bucket].fetch_add(1, std::memory_order_relaxed);
114 values_.back().fetch_add(1, std::memory_order_relaxed);
117 count_.fetch_add(1, std::memory_order_release);
126 if (count_ == 0)
return 0;
129 std::size_t want_sum = count_.load(std::memory_order_acquire) * percent;
130 std::size_t max_value = 0;
131 for (std::size_t i = 0; i < values_.size(); i++) {
132 const auto value = values_[i].load(std::memory_order_relaxed);
134 if (sum * 100 > want_sum)
return i;
136 if (value) max_value = i;
139 for (size_t i = 0; i < extra_values_.size(); i++) {
140 const auto value = extra_values_[i].load(std::memory_order_relaxed);
142 if (sum * 100 > want_sum)
return ExtraBucketToValue(i);
144 if (value) max_value = ExtraBucketToValue(i);
150 template <
class Duration = std::chrono::seconds>
151 void Add(
const Percentile<M, Counter, ExtraBuckets, ExtraBucketSize>& other,
152 [[maybe_unused]] Duration this_epoch_duration = Duration(),
153 [[maybe_unused]] Duration before_this_epoch_duration = Duration()) {
155 for (size_t i = 0; i < values_.size(); i++) {
156 const auto value = other.values_[i].load(std::memory_order_relaxed);
158 values_[i].fetch_add(value, std::memory_order_relaxed);
161 for (size_t i = 0; i < extra_values_.size(); i++) {
162 const auto value = other.extra_values_[i].load(std::memory_order_relaxed);
164 extra_values_[i].fetch_add(value, std::memory_order_relaxed);
166 count_.fetch_add(sum, std::memory_order_release);
171 for (
auto& value : values_) value.store(0, std::memory_order_relaxed);
172 for (
auto& value : extra_values_) value.store(0, std::memory_order_relaxed);
177 Counter
Count()
const noexcept {
return count_; }
180 size_t ExtraBucketToValue(std::size_t bucket)
const noexcept {
181 return values_.size() + bucket * ExtraBucketSize;
184 static_assert(std::atomic<Counter>::is_always_lock_free,
185 "`std::atomic<Counter>` is not lock-free. Please choose some "
186 "other `Counter` type");
188 std::array<std::atomic<Counter>, M> values_;
189 std::array<std::atomic<Counter>, ExtraBuckets> extra_values_;
190 std::atomic<Counter> count_;
193std::string GetPercentileFieldName(
double perc);
195template <size_t M,
typename Counter, size_t ExtraBuckets,
196 size_t ExtraBucketSize>
199 const Percentile<M, Counter, ExtraBuckets, ExtraBucketSize>& perc,
200 std::initializer_list<
double> percents = {0, 50, 90, 95, 98, 99, 99.6, 99.9,
202 for (
double percent : percents) {
203 writer.ValueWithLabels(
204 perc.GetPercentile(percent),
205 {
"percentile", statistics::GetPercentileFieldName(percent)});