userver: userver/utils/statistics/min_max_avg.hpp Source File
Loading...
Searching...
No Matches
min_max_avg.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/statistics/min_max_avg.hpp
4/// @brief @copybrief utils::statistics::MinMaxAvg
5
6#include <atomic>
7#include <chrono>
8#include <cstddef>
9#include <type_traits>
10
11#include <userver/formats/json/inline.hpp>
12#include <userver/formats/serialize/to.hpp>
13#include <userver/utils/assert.hpp>
14#include <userver/utils/statistics/writer.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace utils::statistics {
19
20/// @brief Class for concurrent safe calculation of minimum, maximum and
21/// average over series of values.
22template <typename ValueType, typename AverageType = ValueType>
23class MinMaxAvg final {
24 static_assert(
25 std::is_integral_v<ValueType> && !std::is_same_v<ValueType, bool>,
26 "only integral value types are supported in MinMaxAvg"
27 );
28 static_assert(
29 std::is_same_v<AverageType, ValueType> || std::is_floating_point_v<AverageType>,
30 "MinMaxAvg average type must either be equal to value type or "
31 "be a floating point type"
32 );
33 static_assert(
34 std::atomic<ValueType>::is_always_lock_free && std::atomic<size_t>::is_always_lock_free,
35 "refusing to use locking atomics"
36 );
37
38public:
39 struct Current {
40 ValueType minimum;
41 ValueType maximum;
42 AverageType average;
43 };
44
45 constexpr MinMaxAvg() noexcept : minimum_(0), maximum_(0), sum_(0), count_(0) {}
46
47 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
48 MinMaxAvg(const MinMaxAvg& other) noexcept { *this = other; }
49
50 MinMaxAvg& operator=(const MinMaxAvg& rhs) noexcept {
51 if (this == &rhs) {
52 return *this;
53 }
54
55 const auto count = rhs.count_.load(std::memory_order_acquire);
56 minimum_ = rhs.minimum_.load(std::memory_order_relaxed);
57 maximum_ = rhs.maximum_.load(std::memory_order_relaxed);
58 sum_ = rhs.sum_.load(std::memory_order_relaxed);
59 count_ = count;
60 return *this;
61 }
62
63 Current GetCurrent() const {
64 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
65 Current current;
66 const auto count = count_.load(std::memory_order_acquire);
67 UASSERT(count >= 0);
68 current.minimum = minimum_.load(std::memory_order_relaxed);
69 current.maximum = maximum_.load(std::memory_order_relaxed);
70 current.average =
71 count ? static_cast<AverageType>(sum_.load(std::memory_order_relaxed)) / static_cast<AverageType>(count)
72 : AverageType{0};
73 return current;
74 }
75
76 void Account(ValueType value) {
77 ValueType current_minimum = minimum_.load(std::memory_order_relaxed);
78 while (current_minimum > value || !count_.load(std::memory_order_relaxed)) {
79 if (minimum_.compare_exchange_weak(current_minimum, value, std::memory_order_relaxed)) {
80 break;
81 }
82 }
83 ValueType current_maximum = maximum_.load(std::memory_order_relaxed);
84 while (current_maximum < value || !count_.load(std::memory_order_relaxed)) {
85 if (maximum_.compare_exchange_weak(current_maximum, value, std::memory_order_relaxed)) {
86 break;
87 }
88 }
89 sum_.fetch_add(value, std::memory_order_relaxed);
90 count_.fetch_add(1, std::memory_order_release);
91 }
92
93 template <class Duration = std::chrono::seconds>
94 void Add(
95 const MinMaxAvg& other,
96 [[maybe_unused]] Duration this_epoch_duration = Duration(),
97 [[maybe_unused]] Duration before_this_epoch_duration = Duration()
98 ) {
99 ValueType current_minimum = minimum_.load(std::memory_order_relaxed);
100 while (current_minimum > other.minimum_.load(std::memory_order_relaxed) ||
101 !count_.load(std::memory_order_relaxed))
102 {
103 if (minimum_.compare_exchange_weak(
104 current_minimum,
105 other.minimum_.load(std::memory_order_relaxed),
106 std::memory_order_relaxed
107 ))
108 {
109 break;
110 }
111 }
112 ValueType current_maximum = maximum_.load(std::memory_order_relaxed);
113 while (current_maximum < other.maximum_.load(std::memory_order_relaxed) ||
114 !count_.load(std::memory_order_relaxed))
115 {
116 if (maximum_.compare_exchange_weak(
117 current_maximum,
118 other.maximum_.load(std::memory_order_relaxed),
119 std::memory_order_relaxed
120 ))
121 {
122 break;
123 }
124 }
125 sum_.fetch_add(other.sum_.load(std::memory_order_relaxed), std::memory_order_relaxed);
126 count_.fetch_add(other.count_.load(std::memory_order_acquire), std::memory_order_release);
127 }
128
129 void Reset() {
130 minimum_.store(0, std::memory_order_relaxed);
131 maximum_.store(0, std::memory_order_relaxed);
132 sum_.store(0, std::memory_order_relaxed);
133 count_ = 0;
134 }
135
136 bool IsEmpty() const noexcept { return count_.load() == 0; }
137
138private:
139 std::atomic<ValueType> minimum_;
140 std::atomic<ValueType> maximum_;
141 std::atomic<ValueType> sum_;
142 std::atomic<ssize_t> count_;
143};
144
145template <typename ValueType, typename AverageType>
146auto Serialize(const MinMaxAvg<ValueType, AverageType>& mma, formats::serialize::To<formats::json::Value>) {
147 const auto current = mma.GetCurrent();
148 return formats::json::MakeObject("min", current.minimum, "max", current.maximum, "avg", current.average);
149}
150
151template <typename ValueType, typename AverageType>
152void DumpMetric(Writer& writer, const MinMaxAvg<ValueType, AverageType>& value) {
153 const auto current = value.GetCurrent();
154 writer["min"] = current.minimum;
155 writer["max"] = current.maximum;
156 writer["avg"] = current.average;
157}
158
159} // namespace utils::statistics
160
161USERVER_NAMESPACE_END