histogram.h 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. #pragma once
  2. #include <vector>
  3. #include <cassert>
  4. #include <algorithm>
  5. #include "prometheus/metric.h"
  6. #include "prometheus/family.h"
  7. #include "prometheus/counter.h"
  8. namespace prometheus {
  9. /// \brief A histogram metric to represent aggregatable distributions of events.
  10. ///
  11. /// This class represents the metric type histogram:
  12. /// https://prometheus.io/docs/concepts/metric_types/#histogram
  13. ///
  14. /// A histogram tracks the number of observations and the sum of the observed
  15. /// values, allowing to calculate the average of the observed values.
  16. ///
  17. /// At its core a histogram has a counter per bucket. The sum of observations
  18. /// also behaves like a counter as long as there are no negative observations.
  19. ///
  20. /// See https://prometheus.io/docs/practices/histograms/ for detailed
  21. /// explanations of histogram usage and differences to summaries.
  22. ///
  23. /// The class is thread-safe. No concurrent call to any API of this type causes
  24. /// a data race.
  25. template <typename Value_ = uint64_t>
  26. class Histogram : public Metric {
  27. public:
  28. using Value = Value_;
  29. using BucketBoundaries = std::vector<Value_>;
  30. using Family = CustomFamily<Histogram<Value>>;
  31. static const Metric::Type static_type = Metric::Type::Histogram;
  32. /// \brief Create a histogram with manually chosen buckets.
  33. ///
  34. /// The BucketBoundaries are a list of monotonically increasing values
  35. /// representing the bucket boundaries. Each consecutive pair of values is
  36. /// interpreted as a half-open interval [b_n, b_n+1) which defines one bucket.
  37. ///
  38. /// There is no limitation on how the buckets are divided, i.e, equal size,
  39. /// exponential etc..
  40. ///
  41. /// The bucket boundaries cannot be changed once the histogram is created.
  42. Histogram (const BucketBoundaries& buckets)
  43. : Metric(static_type), bucket_boundaries_{ buckets }, bucket_counts_{ buckets.size() + 1 }, sum_{} {
  44. assert(std::is_sorted(std::begin(bucket_boundaries_),
  45. std::end(bucket_boundaries_)));
  46. }
  47. /// \brief Observe the given amount.
  48. ///
  49. /// The given amount selects the 'observed' bucket. The observed bucket is
  50. /// chosen for which the given amount falls into the half-open interval [b_n,
  51. /// b_n+1). The counter of the observed bucket is incremented. Also the total
  52. /// sum of all observations is incremented.
  53. void Observe(const Value value) {
  54. // TODO: determine bucket list size at which binary search would be faster
  55. const auto bucket_index = static_cast<std::size_t>(std::distance(
  56. bucket_boundaries_.begin(),
  57. std::find_if(
  58. std::begin(bucket_boundaries_), std::end(bucket_boundaries_),
  59. [value](const Value boundary) { return boundary >= value; })));
  60. sum_.Increment(value);
  61. bucket_counts_[bucket_index].Increment();
  62. }
  63. /// \brief Observe multiple data points.
  64. ///
  65. /// Increments counters given a count for each bucket. (i.e. the caller of
  66. /// this function must have already sorted the values into buckets).
  67. /// Also increments the total sum of all observations by the given value.
  68. void ObserveMultiple(const std::vector<Value>& bucket_increments,
  69. const Value sum_of_values) {
  70. if (bucket_increments.size() != bucket_counts_.size()) {
  71. throw std::length_error(
  72. "The size of bucket_increments was not equal to"
  73. "the number of buckets in the histogram.");
  74. }
  75. sum_.Increment(sum_of_values);
  76. for (std::size_t i{ 0 }; i < bucket_counts_.size(); ++i) {
  77. bucket_counts_[i].Increment(bucket_increments[i]);
  78. }
  79. }
  80. /// \brief Get the current value of the counter.
  81. ///
  82. /// Collect is called by the Registry when collecting metrics.
  83. virtual ClientMetric Collect() const {
  84. auto metric = ClientMetric{};
  85. auto cumulative_count = 0ULL;
  86. metric.histogram.bucket.reserve(bucket_counts_.size());
  87. for (std::size_t i{0}; i < bucket_counts_.size(); ++i) {
  88. cumulative_count += static_cast<std::size_t>(bucket_counts_[i].Get());
  89. auto bucket = ClientMetric::Bucket{};
  90. bucket.cumulative_count = cumulative_count;
  91. bucket.upper_bound = (i == bucket_boundaries_.size()
  92. ? std::numeric_limits<double>::infinity()
  93. : static_cast<double>(bucket_boundaries_[i]));
  94. metric.histogram.bucket.push_back(std::move(bucket));
  95. }
  96. metric.histogram.sample_count = cumulative_count;
  97. metric.histogram.sample_sum = sum_.Get();
  98. return metric;
  99. }
  100. private:
  101. const BucketBoundaries bucket_boundaries_;
  102. std::vector<Counter<Value_>> bucket_counts_;
  103. Gauge<Value_> sum_;
  104. };
  105. /// \brief Return a builder to configure and register a Histogram metric.
  106. ///
  107. /// @copydetails Family<>::Family()
  108. ///
  109. /// Example usage:
  110. ///
  111. /// \code
  112. /// auto registry = std::make_shared<Registry>();
  113. /// auto& histogram_family = prometheus::BuildHistogram()
  114. /// .Name("some_name")
  115. /// .Help("Additional description.")
  116. /// .Labels({{"key", "value"}})
  117. /// .Register(*registry);
  118. ///
  119. /// ...
  120. /// \endcode
  121. ///
  122. /// \return An object of unspecified type T, i.e., an implementation detail
  123. /// except that it has the following members:
  124. ///
  125. /// - Name(const std::string&) to set the metric name,
  126. /// - Help(const std::string&) to set an additional description.
  127. /// - Label(const std::map<std::string, std::string>&) to assign a set of
  128. /// key-value pairs (= labels) to the metric.
  129. ///
  130. /// To finish the configuration of the Histogram metric register it with
  131. /// Register(Registry&).
  132. using BuildHistogram = Builder<Histogram<double>>;
  133. } // namespace prometheus