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(std::is_integral_v<ValueType> &&
25 !std::is_same_v<ValueType, bool>,
26 "only integral value types are supported in MinMaxAvg");
27 static_assert(std::is_same_v<AverageType, ValueType> ||
28 std::is_floating_point_v<AverageType>,
29 "MinMaxAvg average type must either be equal to value type or "
30 "be a floating point type");
31 static_assert(std::atomic<ValueType>::is_always_lock_free &&
32 std::atomic<size_t>::is_always_lock_free,
33 "refusing to use locking atomics");
34
35 public:
36 struct Current {
37 ValueType minimum;
38 ValueType maximum;
39 AverageType average;
40 };
41
42 constexpr MinMaxAvg() noexcept
43 : minimum_(0), maximum_(0), sum_(0), count_(0) {}
44
45 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
46 MinMaxAvg(const MinMaxAvg& other) noexcept { *this = other; }
47
48 MinMaxAvg& operator=(const MinMaxAvg& rhs) noexcept {
49 if (this == &rhs) return *this;
50
51 const auto count = rhs.count_.load(std::memory_order_acquire);
52 minimum_ = rhs.minimum_.load(std::memory_order_relaxed);
53 maximum_ = rhs.maximum_.load(std::memory_order_relaxed);
54 sum_ = rhs.sum_.load(std::memory_order_relaxed);
55 count_ = count;
56 return *this;
57 }
58
59 Current GetCurrent() const {
60 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
61 Current current;
62 const auto count = count_.load(std::memory_order_acquire);
63 UASSERT(count >= 0);
64 current.minimum = minimum_.load(std::memory_order_relaxed);
65 current.maximum = maximum_.load(std::memory_order_relaxed);
66 current.average =
67 count ? static_cast<AverageType>(sum_.load(std::memory_order_relaxed)) /
68 static_cast<AverageType>(count)
69 : AverageType{0};
70 return current;
71 }
72
73 void Account(ValueType value) {
74 ValueType current_minimum = minimum_.load(std::memory_order_relaxed);
75 while (current_minimum > value || !count_.load(std::memory_order_relaxed)) {
76 if (minimum_.compare_exchange_weak(current_minimum, value,
77 std::memory_order_relaxed)) {
78 break;
79 }
80 }
81 ValueType current_maximum = maximum_.load(std::memory_order_relaxed);
82 while (current_maximum < value || !count_.load(std::memory_order_relaxed)) {
83 if (maximum_.compare_exchange_weak(current_maximum, value,
84 std::memory_order_relaxed)) {
85 break;
86 }
87 }
88 sum_.fetch_add(value, std::memory_order_relaxed);
89 count_.fetch_add(1, std::memory_order_release);
90 }
91
92 template <class Duration = std::chrono::seconds>
93 void Add(const MinMaxAvg& other,
94 [[maybe_unused]] Duration this_epoch_duration = Duration(),
95 [[maybe_unused]] Duration before_this_epoch_duration = Duration()) {
96 ValueType current_minimum = minimum_.load(std::memory_order_relaxed);
97 while (current_minimum > other.minimum_.load(std::memory_order_relaxed) ||
98 !count_.load(std::memory_order_relaxed)) {
99 if (minimum_.compare_exchange_weak(
100 current_minimum, other.minimum_.load(std::memory_order_relaxed),
101 std::memory_order_relaxed)) {
102 break;
103 }
104 }
105 ValueType current_maximum = maximum_.load(std::memory_order_relaxed);
106 while (current_maximum < other.maximum_.load(std::memory_order_relaxed) ||
107 !count_.load(std::memory_order_relaxed)) {
108 if (maximum_.compare_exchange_weak(
109 current_maximum, other.maximum_.load(std::memory_order_relaxed),
110 std::memory_order_relaxed)) {
111 break;
112 }
113 }
114 sum_.fetch_add(other.sum_.load(std::memory_order_relaxed),
115 std::memory_order_relaxed);
116 count_.fetch_add(other.count_.load(std::memory_order_acquire),
117 std::memory_order_release);
118 }
119
120 void Reset() {
121 minimum_.store(0, std::memory_order_relaxed);
122 maximum_.store(0, std::memory_order_relaxed);
123 sum_.store(0, std::memory_order_relaxed);
124 count_ = 0;
125 }
126
127 bool IsEmpty() const noexcept { return count_.load() == 0; }
128
129 private:
130 std::atomic<ValueType> minimum_;
131 std::atomic<ValueType> maximum_;
132 std::atomic<ValueType> sum_;
133 std::atomic<ssize_t> count_;
134};
135
136template <typename ValueType, typename AverageType>
137auto Serialize(const MinMaxAvg<ValueType, AverageType>& mma,
138 formats::serialize::To<formats::json::Value>) {
139 const auto current = mma.GetCurrent();
140 return formats::json::MakeObject("min", current.minimum, "max",
141 current.maximum, "avg", current.average);
142}
143
144template <typename ValueType, typename AverageType>
145void DumpMetric(Writer& writer,
146 const MinMaxAvg<ValueType, AverageType>& value) {
147 const auto current = value.GetCurrent();
148 writer["min"] = current.minimum;
149 writer["max"] = current.maximum;
150 writer["avg"] = current.average;
151}
152
153} // namespace utils::statistics
154
155USERVER_NAMESPACE_END