userver: userver/utils/statistics/min_max_avg.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
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