Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): histograms: support observing values a non-integral number of times #637

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
22 changes: 16 additions & 6 deletions core/include/prometheus/histogram.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,29 @@ class PROMETHEUS_CPP_CORE_EXPORT Histogram {
/// \copydoc Histogram::Histogram(const BucketBoundaries&)
explicit Histogram(BucketBoundaries&& buckets);

/// \brief Observe the given amount.
/// \brief Observe the given value.
///
/// The given amount selects the 'observed' bucket. The observed bucket is
/// chosen for which the given amount falls into the half-open interval [b_n,
/// b_n+1). The counter of the observed bucket is incremented. Also the total
/// sum of all observations is incremented.
void Observe(double value);
/// The given value selects the 'observed' bucket. The observed bucket is
/// chosen for which the given value falls into the half-open interval [b_n,
/// b_n+1). The counter of the observed bucket is incremented by the given
/// quantity. Also the total sum of all observations is incremented by
/// value*quantity.
///
/// Passing a quantity!=1 can be seen as a generalization for real numbers of
/// calling Observe(value, 1) in a loop. The collected bucket counts will be
/// truncated but resulting rounding errors (other than those caused by
/// summing floating points) will not accumulate over time nor over cumulative
/// bucket counts. Same for the cumulative sum of the histogram.
void Observe(double value, double quantity = 1.0);

/// \brief Observe multiple data points.
///
/// Increments counters given a count for each bucket. (i.e. the caller of
/// this function must have already sorted the values into buckets).
/// Also increments the total sum of all observations by the given value.
///
/// See the details in Observe(double, double) about floats when passing
/// non-integer values as bucket increments.
void ObserveMultiple(const std::vector<double>& bucket_increments,
double sum_of_values);

Expand Down
8 changes: 4 additions & 4 deletions core/src/histogram.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ Histogram::Histogram(BucketBoundaries&& buckets)
}
}

void Histogram::Observe(const double value) {
void Histogram::Observe(const double value, const double quantity) {
const auto bucket_index = static_cast<std::size_t>(
std::distance(bucket_boundaries_.begin(),
std::lower_bound(bucket_boundaries_.begin(),
bucket_boundaries_.end(), value)));

std::lock_guard<std::mutex> lock(mutex_);
sum_.Increment(value);
bucket_counts_[bucket_index].Increment();
sum_.Increment(quantity * value);
bucket_counts_[bucket_index].Increment(quantity);
}

void Histogram::ObserveMultiple(const std::vector<double>& bucket_increments,
Expand Down Expand Up @@ -77,7 +77,7 @@ ClientMetric Histogram::Collect() const {

auto metric = ClientMetric{};

auto cumulative_count = 0ULL;
auto cumulative_count = 0.0;
metric.histogram.bucket.reserve(bucket_counts_.size());
for (std::size_t i{0}; i < bucket_counts_.size(); ++i) {
cumulative_count += bucket_counts_[i].Value();
Expand Down
25 changes: 25 additions & 0 deletions core/tests/histogram_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,31 @@ TEST(HistogramTest, cumulative_bucket_count) {
EXPECT_EQ(h.bucket.at(2).cumulative_count, 7U);
}

TEST(HistogramTest, observe_float_test_bucket_counts) {
Histogram histogram{{1, 2}};
histogram.Observe(0.1, 2.6);
histogram.Observe(1.2, 3.6);
histogram.Observe(2.3, 4.6);
auto metric = histogram.Collect();
auto h = metric.histogram;
ASSERT_EQ(h.bucket.size(), 3U);
// rounding errors do not accumulate in the cumulative count:
EXPECT_EQ(h.bucket.at(0).cumulative_count, 2U); // 2.6, truncated
EXPECT_EQ(h.bucket.at(1).cumulative_count, 6U); // 2.6 + 3.6, truncated
EXPECT_EQ(h.bucket.at(2).cumulative_count, 10U); // 2.6 + 3.6 + 4.6, truncated
}

TEST(HistogramTest, observe_float_test_total_sum) {
Histogram histogram{{1, 2}};
histogram.Observe(0.1, 2.6);
histogram.Observe(1.2, 3.6);
histogram.Observe(2.3, 4.6);
auto metric = histogram.Collect();
auto h = metric.histogram;
EXPECT_EQ(h.sample_count, 10U);
EXPECT_FLOAT_EQ(h.sample_sum, 15.16);
}

TEST(HistogramTest, observe_multiple_test_bucket_counts) {
Histogram histogram{{1, 2}};
histogram.ObserveMultiple({5, 9, 3}, 20);
Expand Down