Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/include/userver/utils/statistics/histogram_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ class HistogramView final {
/// Returns the sum of counts from all buckets.
std::uint64_t GetTotalCount() const noexcept;

/// Returns sum of values from the given bucket.
double GetSumAt(std::size_t index) const;

// Returns sum of values from the "infinity" bucket
/// (greater than the largest bucket boundary).
double GetSumAtInf() const noexcept;

/// Returns sum of values from all buckets.
double GetTotalSum() const noexcept;

private:
friend struct impl::histogram::Access;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct Bucket final {

BoundOrSize upper_bound{0.0};
std::atomic<std::uint64_t> counter{0};
std::atomic<double> sum{0.0};
};

void CopyBounds(Bucket* bucket_array, utils::span<const double> upper_bounds);
Expand Down
1 change: 1 addition & 0 deletions core/src/utils/statistics/histogram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ void Histogram::Account(double value, std::uint64_t count) noexcept {
pre_bucket_index + 1 > bucket_count_ ? 0 : pre_bucket_index + 1;
auto& bucket = buckets_[bucket_index];
bucket.counter.fetch_add(count, std::memory_order_relaxed);
impl::histogram::AddAtomic(bucket.sum, value * count);
}

void ResetMetric(Histogram& histogram) noexcept {
Expand Down
21 changes: 19 additions & 2 deletions core/src/utils/statistics/histogram_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ UTEST(StatisticsHistogram, Reset) {
EXPECT_EQ(histogram.GetView(), zero_histogram.GetView());
}

UTEST(StatisticsHistogram, Sum) {
utils::statistics::Histogram histogram{Bounds()};
AccountSome(histogram);

EXPECT_EQ(histogram.GetView().GetSumAt(0), 1.2);
EXPECT_EQ(histogram.GetView().GetSumAt(1), 1.8);
EXPECT_EQ(histogram.GetView().GetSumAt(2), 10 + 30 * 4);
EXPECT_EQ(histogram.GetView().GetSumAt(3), 0.0);
EXPECT_EQ(histogram.GetView().GetSumAtInf(), 100);
EXPECT_EQ(histogram.GetView().GetTotalSum(), 1.2 + 1.8 + 10 + 30 * 4 + 100);
}

UTEST(StatisticsHistogram, ZeroBuckets) {
utils::statistics::Histogram histogram{std::vector<double>{}};
EXPECT_EQ(histogram.GetView().GetBucketCount(), 0);
Expand Down Expand Up @@ -283,7 +295,8 @@ UTEST_F(StatisticsHistogramFormat, JsonFormat) {
"value": {
"bounds": [1.5, 5.0, 42.0, 60.0],
"buckets": [1, 1, 5, 0],
"inf": 1
"inf": 1,
"sum":233.0
},
"labels": {},
"type": "HIST_RATE"
Expand All @@ -306,6 +319,7 @@ test_bucket{le="42"} 7
test_bucket{le="60"} 7
test_bucket{le="+Inf"} 8
test_count{} 8
test_sum{} 233
)";
EXPECT_EQ(utils::statistics::ToPrometheusFormat(GetStorage()), expected);
}
Expand All @@ -318,6 +332,7 @@ test_bucket{le="42"} 7
test_bucket{le="60"} 7
test_bucket{le="+Inf"} 8
test_count{} 8
test_sum{} 233
)";
EXPECT_EQ(utils::statistics::ToPrometheusFormatUntyped(GetStorage()),
expected);
Expand All @@ -340,6 +355,7 @@ test_bucket{le="42",custom_label_1="1",custom_label_2="2"} 7
test_bucket{le="60",custom_label_1="1",custom_label_2="2"} 7
test_bucket{le="+Inf",custom_label_1="1",custom_label_2="2"} 8
test_count{custom_label_1="1",custom_label_2="2"} 8
test_sum{custom_label_1="1",custom_label_2="2"} 233
)";
EXPECT_EQ(utils::statistics::ToPrometheusFormat(storage), expected);
}
Expand All @@ -364,7 +380,8 @@ UTEST_F(StatisticsHistogramFormat, SolomonFormat) {
"hist": {
"bounds": [1.5, 5.0, 42.0, 60.0],
"buckets": [1, 1, 5, 0],
"inf": 1
"inf": 1,
"sum": 233.0
},
"type": "HIST_RATE"
}
Expand Down
19 changes: 19 additions & 0 deletions core/src/utils/statistics/histogram_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ std::uint64_t HistogramView::GetTotalCount() const noexcept {
return total;
}

double HistogramView::GetSumAt(std::size_t index) const {
UASSERT(index < GetBucketCount());
return buckets_[index + 1].sum.load(std::memory_order_relaxed);
}

double HistogramView::GetSumAtInf() const noexcept {
UASSERT(buckets_);
return buckets_[0].sum.load(std::memory_order_relaxed);
}

double HistogramView::GetTotalSum() const noexcept {
const auto bucket_count = GetBucketCount();
auto total = GetSumAtInf();
for (std::size_t i = 0; i < bucket_count; ++i) {
total += GetSumAt(i);
}
return total;
}

void DumpMetric(Writer& writer, HistogramView histogram) { writer = histogram; }

bool operator==(HistogramView lhs, HistogramView rhs) noexcept {
Expand Down
5 changes: 4 additions & 1 deletion core/src/utils/statistics/impl/histogram_bucket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ namespace utils::statistics::impl::histogram {

Bucket::Bucket(const Bucket& other) noexcept
: upper_bound(other.upper_bound),
counter(other.counter.load(std::memory_order_relaxed)) {}
counter(other.counter.load(std::memory_order_relaxed)),
sum(other.sum.load(std::memory_order_relaxed)) {}

Bucket& Bucket::operator=(const Bucket& other) noexcept {
if (this == &other) return *this;
upper_bound = other.upper_bound;
counter.store(other.counter.load(std::memory_order_relaxed),
std::memory_order_relaxed);
sum.store(other.sum.load(std::memory_order_relaxed),
std::memory_order_relaxed);
return *this;
}

Expand Down
3 changes: 3 additions & 0 deletions core/src/utils/statistics/impl/histogram_serialization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Value Serialize(HistogramView value, formats::serialize::To<Value>) {
result["bounds"] = impl::histogram::Access::Bounds(value);
result["buckets"] = impl::histogram::Access::Values(value);
result["inf"] = value.GetValueAtInf();
result["sum"] = value.GetTotalSum();
return result.ExtractValue();
}

Expand All @@ -26,6 +27,8 @@ void WriteToStream(HistogramView value, StringBuilder& sw) {
WriteToStream(impl::histogram::Access::Values(value), sw);
sw.Key("inf");
WriteToStream(value.GetValueAtInf(), sw);
sw.Key("sum");
WriteToStream(value.GetTotalSum(), sw);
}

} // namespace utils::statistics
Expand Down
24 changes: 24 additions & 0 deletions core/src/utils/statistics/impl/histogram_view_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ inline void AddNonAtomic(std::atomic<std::uint64_t>& to, std::uint64_t x) {
to.store(to.load(std::memory_order_relaxed) + x, std::memory_order_relaxed);
}

inline void AddNonAtomic(std::atomic<double>& to, std::uint64_t x) {
to.store(to.load(std::memory_order_relaxed) + x, std::memory_order_relaxed);
}

inline void AddAtomic(std::atomic<double>& to, double x) {
#if __cplusplus >= 202002L
to += x;
#else
double expected = to.load();
double desired = expected + x;
while (!to.compare_exchange_weak(expected, desired, std::memory_order_release,
std::memory_order_relaxed)) {
desired = expected + x;
}
#endif
}

inline bool IsBoundPositive(double x) noexcept {
return std::isnormal(x) && x > 0;
}
Expand Down Expand Up @@ -105,6 +122,7 @@ class MutableView final {
void Assign(HistogramView other) const noexcept {
buckets_[0].upper_bound.size = other.GetBucketCount();
buckets_[0].counter.store(other.GetValueAtInf(), std::memory_order_relaxed);
buckets_[0].sum.store(other.GetSumAtInf(), std::memory_order_relaxed);
boost::copy(Access::Buckets(other), Access::Buckets(*this).begin());
}

Expand All @@ -115,13 +133,16 @@ class MutableView final {
boost::upper_bound(bounds_view, value, std::less_equal<>{});
auto& bucket = (iter == bounds_view.end()) ? buckets_[0] : *iter.base();
bucket.counter.fetch_add(count, std::memory_order_relaxed);
AddAtomic(bucket.sum, value * count);
}

// Atomic
void Reset() const noexcept {
buckets_[0].counter.store(0, std::memory_order_relaxed);
buckets_[0].sum.store(0.0, std::memory_order_relaxed);
for (auto& bucket : Access::Buckets(*this)) {
bucket.counter.store(0, std::memory_order_relaxed);
bucket.sum.store(0.0, std::memory_order_relaxed);
}
}

Expand All @@ -131,6 +152,8 @@ class MutableView final {
boost::range::includes(Access::Bounds(other), Access::Bounds(*this)),
"Buckets can be merged, but not added during Histogram conversion.");
AddNonAtomic(buckets_[0].counter, other.GetValueAtInf());
AddNonAtomic(buckets_[0].sum, other.GetSumAtInf());

const auto self_bounds = Access::Bounds(*this);
auto current_self_bound = self_bounds.begin();
for (const auto& other_bucket : Access::Buckets(other)) {
Expand All @@ -142,6 +165,7 @@ class MutableView final {
? buckets_[0]
: *current_self_bound.base();
AddNonAtomic(self_bucket.counter, other_bucket.counter);
AddNonAtomic(self_bucket.sum, other_bucket.sum);
}
}

Expand Down
3 changes: 3 additions & 0 deletions core/src/utils/statistics/prometheus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class FormatBuilder final : public utils::statistics::BaseFormatBuilder {
AppendHistogramMetric("count", prometheus_name,
/* upper_bound */ "",
fmt::to_string(histogram.GetTotalCount()), labels);
AppendHistogramMetric("sum", prometheus_name,
/* upper_bound */ "",
fmt::to_string(histogram.GetTotalSum()), labels);
}

void DumpMetricNameAndType(std::string_view name, const MetricValue& value) {
Expand Down