Skip to content
Merged
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
30 changes: 27 additions & 3 deletions core/include/prometheus/counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,41 @@
#include "prometheus/metric_type.h"

namespace prometheus {

/// \brief A counter metric to represent a monotonically increasing value.
///
/// This class represents the metric type counter:
/// https://prometheus.io/docs/concepts/metric_types/#counter
///
/// The value of the counter can only increase. Example of counters are:
/// - the number of requests served
/// - tasks completed
/// - errors
///
/// Do not use a counter to expose a value that can decrease - instead use a
/// Gauge.
class Counter {
public:
static const MetricType metric_type = MetricType::Counter;
static const MetricType metric_type{MetricType::Counter};

/// \brief Create a counter that starts at 0.
Counter() = default;

/// \brief Increment the counter by 1.
void Increment();

/// \brief Increment the counter by a given amount.
///
/// The counter will not change if the given amount is negative.
void Increment(double);

/// \brief Get the current value of the counter.
double Value() const;

ClientMetric Collect();
ClientMetric Collect() const;

private:
Gauge gauge_;
Gauge gauge_{0.0};
};

} // namespace prometheus
34 changes: 30 additions & 4 deletions core/include/prometheus/gauge.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,51 @@

namespace prometheus {

/// \brief A gauge metric to represent a value that can arbitrarily go up and
/// down.
///
/// The class represents the metric type gauge:
/// https://prometheus.io/docs/concepts/metric_types/#gauge
///
/// Gauges are typically used for measured values like temperatures or current
/// memory usage, but also "counts" that can go up and down, like the number of
/// running processes.
class Gauge {
public:
static const MetricType metric_type = MetricType::Gauge;
static const MetricType metric_type{MetricType::Gauge};

Gauge();
/// \brief Create a gauge that starts at 0.
Gauge() = default;

/// \brief Create a gauge that starts at the given amount.
Gauge(double);

/// \brief Increment the gauge by 1.
void Increment();

/// \brief Increment the gauge by the given amount.
void Increment(double);

/// \brief Decrement the gauge by 1.
void Decrement();

/// \brief Decrement the gauge by the given amount.
void Decrement(double);

/// \brief Set the gauge to the given value.
void Set(double);

/// \brief Set the gauge to the current unixtime in seconds.
void SetToCurrentTime();

/// \brief Get the current value of the gauge.
double Value() const;

ClientMetric Collect();
ClientMetric Collect() const;

private:
void Change(double);
std::atomic<double> value_;
std::atomic<double> value_{0.0};
};

} // namespace prometheus
37 changes: 34 additions & 3 deletions core/include/prometheus/histogram.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,52 @@
#include "prometheus/metric_type.h"

namespace prometheus {

/// \brief A histogram metric to represent aggregatable distributions of events.
///
/// This class represents the metric type histogram:
/// https://prometheus.io/docs/concepts/metric_types/#histogram
///
/// A histogram tracks the number of observations and the sum of the observed
/// values, allowing to calculate the average of the observed values.
///
/// At its core a histogram has a counter per bucket. The sum of observations
/// also behaves like a counter.
///
/// See https://prometheus.io/docs/practices/histograms/ for detailed
/// explanations of histogram usage and differences to summaries.
class Histogram {
public:
using BucketBoundaries = std::vector<double>;

static const MetricType metric_type = MetricType::Histogram;

static const MetricType metric_type{MetricType::Histogram};

/// \brief Create a histogram with manually choosen buckets.
///
/// The BucketBoundaries are a list of monotonically increasing values
/// representing the bucket boundaries. Each consecutive pair of values is
/// interpreted as a half-open interval [b_n, b_n+1) which defines one bucket.
///
/// There is no limitation on how the buckets are divided, i.e, equal size,
/// exponential etc..
///
/// The bucket boundaries cannot be changed once the histogram is created.
Histogram(const BucketBoundaries& buckets);

/// \brief Observe the given amount.
///
/// 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);

ClientMetric Collect();
ClientMetric Collect() const;

private:
const BucketBoundaries bucket_boundaries_;
std::vector<Counter> bucket_counts_;
Counter sum_;
};

} // namespace prometheus
58 changes: 53 additions & 5 deletions core/include/prometheus/summary.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,75 @@

namespace prometheus {

/// \brief A summary metric samples observations over a sliding window of time.
///
/// This class represents the metric type summary:
/// https://prometheus.io/docs/instrumenting/writing_clientlibs/#summary
///
/// A summary provides a total count of observations and a sum of all observed
/// values. In contrast to a histogram metric it also calculates configurable
/// Phi-quantiles over a sliding window of time.
///
/// The essential difference between summaries and histograms is that summaries
/// calculate streaming Phi-quantiles on the client side and expose them
/// directly, while histograms expose bucketed observation counts and the
/// calculation of quantiles from the buckets of a histogram happens on the
/// server side:
/// https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile.
///
/// Note that Phi designates the probability density function of the standard
/// Gaussian distribution.
///
/// See https://prometheus.io/docs/practices/histograms/ for detailed
/// explanations of Phi-quantiles, summary usage, and differences to histograms.
class Summary {
public:
using Quantiles = std::vector<detail::CKMSQuantiles::Quantile>;

static const MetricType metric_type = MetricType::Summary;
static const MetricType metric_type{MetricType::Summary};

/// \brief Create a summary metric.
///
/// \param quantiles A list of 'targeted' Phi-quantiles. A targeted
/// Phi-quantile is specified in the form of a Phi-quantile and tolerated
/// error. For example a Quantile{0.5, 0.1} means that the median (= 50th
/// percentile) should be returned with 10 percent error or a Quantile{0.2,
/// 0.05} means the 20th percentile with 5 percent tolerated error. Note that
/// percentiles and quantiles are the same concept, except percentiles are
/// expressed as percentages. The Phi-quantile must be in the interval [0, 1].
/// Note that a lower tolerated error for a Phi-quantile results in higher
/// usage of resources (memory and cpu) to calculate the summary.
///
/// The Phi-quantiles are calculated over a sliding window of time. The
/// sliding window of time is configured by max_age and age_buckets.
///
/// \param max_age Set the duration of the time window, i.e., how long
/// observations are kept before they are discarded. The default value is 60
/// seconds.
///
/// \param age_buckets Set the number of buckets of the time window. It
/// determines the number of buckets used to exclude observations that are
/// older than max_age from the summary, e.g., if max_age is 60 seconds and
/// age_buckets is 5, buckets will be switched every 12 seconds. The value is
/// a trade-off between resources (memory and cpu for maintaining the bucket)
/// and how smooth the time window is moved. With only one age bucket it
/// effectively results in a complete reset of the summary each time max_age
/// has passed. The default value is 5.
Summary(const Quantiles& quantiles,
std::chrono::milliseconds max_age_seconds = std::chrono::seconds(60),
std::chrono::milliseconds max_age = std::chrono::seconds{60},
int age_buckets = 5);

/// \brief Observe the given amount.
void Observe(double value);

ClientMetric Collect();

private:
const Quantiles quantiles_;

std::mutex mutex_;

double count_;
std::uint64_t count_;
double sum_;
detail::TimeWindowQuantiles quantile_values_;
};

} // namespace prometheus
7 changes: 4 additions & 3 deletions core/src/counter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ namespace prometheus {

void Counter::Increment() { gauge_.Increment(); }

void Counter::Increment(double val) { gauge_.Increment(val); }
void Counter::Increment(const double val) { gauge_.Increment(val); }

double Counter::Value() const { return gauge_.Value(); }

ClientMetric Counter::Collect() {
ClientMetric Counter::Collect() const {
ClientMetric metric;
metric.counter.value = Value();
return metric;
}
}

} // namespace prometheus
17 changes: 9 additions & 8 deletions core/src/gauge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
#include <ctime>

namespace prometheus {
Gauge::Gauge() : value_{0} {}

Gauge::Gauge(double value) : value_{value} {}
Gauge::Gauge(const double value) : value_{value} {}

void Gauge::Increment() { Increment(1.0); }
void Gauge::Increment(double value) {

void Gauge::Increment(const double value) {
if (value < 0.0) {
return;
}
Expand All @@ -18,31 +18,32 @@ void Gauge::Increment(double value) {

void Gauge::Decrement() { Decrement(1.0); }

void Gauge::Decrement(double value) {
void Gauge::Decrement(const double value) {
if (value < 0.0) {
return;
}
Change(-1.0 * value);
}

void Gauge::Set(double value) { value_.store(value); }
void Gauge::Set(const double value) { value_.store(value); }

void Gauge::Change(double value) {
void Gauge::Change(const double value) {
auto current = value_.load();
while (!value_.compare_exchange_weak(current, current + value))
;
}

void Gauge::SetToCurrentTime() {
auto time = std::time(nullptr);
const auto time = std::time(nullptr);
Set(static_cast<double>(time));
}

double Gauge::Value() const { return value_; }

ClientMetric Gauge::Collect() {
ClientMetric Gauge::Collect() const {
ClientMetric metric;
metric.gauge.value = Value();
return metric;
}

} // namespace prometheus
16 changes: 9 additions & 7 deletions core/src/histogram.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,27 @@
namespace prometheus {

Histogram::Histogram(const BucketBoundaries& buckets)
: bucket_boundaries_(buckets), bucket_counts_(buckets.size() + 1) {
: bucket_boundaries_{buckets}, bucket_counts_{buckets.size() + 1}, sum_{} {
assert(std::is_sorted(std::begin(bucket_boundaries_),
std::end(bucket_boundaries_)));
}

void Histogram::Observe(double value) {
void Histogram::Observe(const double value) {
// TODO: determine bucket list size at which binary search would be faster
auto bucket_index = static_cast<std::size_t>(std::distance(
const auto bucket_index = static_cast<std::size_t>(std::distance(
bucket_boundaries_.begin(),
std::find_if(bucket_boundaries_.begin(), bucket_boundaries_.end(),
[value](double boundary) { return boundary >= value; })));
std::find_if(
std::begin(bucket_boundaries_), std::end(bucket_boundaries_),
[value](const double boundary) { return boundary >= value; })));
sum_.Increment(value);
bucket_counts_[bucket_index].Increment();
}

ClientMetric Histogram::Collect() {
ClientMetric Histogram::Collect() const {
auto metric = ClientMetric{};

auto cumulative_count = 0ULL;
for (std::size_t i = 0; i < bucket_counts_.size(); i++) {
for (std::size_t i{0}; i < bucket_counts_.size(); ++i) {
cumulative_count += bucket_counts_[i].Value();
auto bucket = ClientMetric::Bucket{};
bucket.cumulative_count = cumulative_count;
Expand All @@ -41,4 +42,5 @@ ClientMetric Histogram::Collect() {

return metric;
}

} // namespace prometheus
12 changes: 6 additions & 6 deletions core/src/summary.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
namespace prometheus {

Summary::Summary(const Quantiles& quantiles,
std::chrono::milliseconds max_age_seconds, int age_buckets)
: quantiles_(quantiles),
count_(0),
sum_(0),
quantile_values_(quantiles_, max_age_seconds, age_buckets) {}
const std::chrono::milliseconds max_age, const int age_buckets)
: quantiles_{quantiles},
count_{0},
sum_{0},
quantile_values_{quantiles_, max_age, age_buckets} {}

void Summary::Observe(double value) {
void Summary::Observe(const double value) {
std::lock_guard<std::mutex> lock(mutex_);

count_ += 1;
Expand Down
7 changes: 7 additions & 0 deletions core/tests/counter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@ TEST_F(CounterTest, inc_multiple) {
counter.Increment(5);
EXPECT_EQ(counter.Value(), 7.0);
}

TEST_F(CounterTest, inc_negative_value) {
Counter counter;
counter.Increment(5.0);
counter.Increment(-5.0);
EXPECT_EQ(counter.Value(), 5.0);
}