Commit e1557797 authored by Dominik Charousset's avatar Dominik Charousset

Implement histogram metrics

parent 94e59124
......@@ -310,6 +310,7 @@ caf_add_test_suites(caf-core-test
sum_type
telemetry.collector.prometheus
telemetry.dbl_gauge
telemetry.histogram
telemetry.int_gauge
telemetry.label
telemetry.metric_registry
......
......@@ -250,6 +250,36 @@ class metric_family_impl;
template <class Type>
class metric_impl;
template <class ValueType>
class histogram;
using dbl_histogram = histogram<double>;
using int_histogram = histogram<int64_t>;
} // namespace telemetry
namespace detail {
template <class>
struct gauge_oracle;
template <>
struct gauge_oracle<double> {
using type = telemetry::dbl_gauge;
};
template <>
struct gauge_oracle<int64_t> {
using type = telemetry::int_gauge;
};
} // namespace detail
namespace telemetry {
template <class ValueType>
using gauge = typename detail::gauge_oracle<ValueType>::type;
} // namespace telemetry
// -- I/O classes --------------------------------------------------------------
......
......@@ -31,8 +31,16 @@ namespace caf::telemetry {
/// and down.
class CAF_CORE_EXPORT dbl_gauge {
public:
// -- member types -----------------------------------------------------------
using value_type = double;
// -- constants --------------------------------------------------------------
static constexpr metric_type runtime_type = metric_type::dbl_gauge;
// -- constructors, destructors, and assignment operators --------------------
dbl_gauge() noexcept : value_(0) {
// nop
}
......@@ -41,6 +49,8 @@ public:
// nop
}
// -- modifiers --------------------------------------------------------------
/// Increments the gauge by 1.
void inc() noexcept {
inc(1.0);
......@@ -70,6 +80,8 @@ public:
value_.store(x);
}
// -- observers --------------------------------------------------------------
/// Returns the current value of the gauge.
double value() const noexcept {
return value_.load();
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
// convenience header for including all gauge types
#include "caf/fwd.hpp"
#include "caf/telemetry/dbl_gauge.hpp"
#include "caf/telemetry/int_gauge.hpp"
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <algorithm>
#include <type_traits>
#include "caf/config.hpp"
#include "caf/fwd.hpp"
#include "caf/span.hpp"
#include "caf/telemetry/gauge.hpp"
#include "caf/telemetry/metric_type.hpp"
namespace caf::telemetry {
/// Represent aggregatable distributions of events.
template <class ValueType>
class histogram {
public:
// -- member types -----------------------------------------------------------
using value_type = ValueType;
using gauge_type = gauge<value_type>;
struct bucket_type {
value_type upper_bound;
gauge_type gauge;
};
// -- constants --------------------------------------------------------------
static constexpr metric_type runtime_type
= std::is_same<value_type, double>::value ? metric_type::dbl_histogram
: metric_type::int_histogram;
// -- constructors, destructors, and assignment operators --------------------
histogram(span<const value_type> upper_bounds) {
using limits = std::numeric_limits<value_type>;
CAF_ASSERT(std::is_sorted(upper_bounds.begin(), upper_bounds.end()));
num_buckets_ = upper_bounds.size() + 1;
buckets_ = new bucket_type[num_buckets_];
size_t index = 0;
for (; index < upper_bounds.size(); ++index)
buckets_[index].upper_bound = upper_bounds[index];
if constexpr (limits::has_infinity)
buckets_[index].upper_bound = limits::infinity();
else
buckets_[index].upper_bound = limits::max();
}
histogram(std::initializer_list<value_type> upper_bounds)
: histogram(make_span(upper_bounds.begin(), upper_bounds.size())) {
// nop
}
~histogram() {
delete[] buckets_;
}
// -- modifiers --------------------------------------------------------------
void observe(value_type value) {
// The last bucket has an upper bound of +inf or int_max, so we'll always
// find a bucket and increment the gauges.
for (size_t index = 0;; ++index) {
auto& [upper_bound, gauge] = buckets_[index];
if (value <= upper_bound) {
gauge.inc();
sum_.inc(value);
return;
}
}
}
// -- observers --------------------------------------------------------------
span<const bucket_type> buckets() const noexcept {
return {buckets_, num_buckets_};
}
value_type sum() const noexcept {
return sum_.value();
}
private:
size_t num_buckets_;
bucket_type* buckets_;
gauge_type sum_;
};
///
using dbl_histogram = histogram<double>;
using int_histogram = histogram<int64_t>;
} // namespace caf::telemetry
......@@ -31,8 +31,16 @@ namespace caf::telemetry {
/// and down.
class CAF_CORE_EXPORT int_gauge {
public:
// -- member types -----------------------------------------------------------
using value_type = int64_t;
// -- constants --------------------------------------------------------------
static constexpr metric_type runtime_type = metric_type::int_gauge;
// -- constructors, destructors, and assignment operators --------------------
int_gauge() noexcept : value_(0) {
// nop
}
......@@ -41,6 +49,8 @@ public:
// nop
}
// -- modifiers --------------------------------------------------------------
/// Increments the gauge by 1.
void inc() noexcept {
++value_;
......@@ -66,11 +76,6 @@ public:
value_.store(x);
}
/// Returns the current value of the gauge.
int64_t value() const noexcept {
return value_.load();
}
/// Increments the gauge by 1.
/// @returns The new value of the gauge.
int64_t operator++() noexcept {
......@@ -83,6 +88,13 @@ public:
return --value_;
}
// -- observers --------------------------------------------------------------
/// Returns the current value of the gauge.
int64_t value() const noexcept {
return value_.load();
}
private:
std::atomic<int64_t> value_;
};
......
......@@ -33,16 +33,22 @@ namespace caf::telemetry {
/// Manages a collection of metric families.
class CAF_CORE_EXPORT metric_registry {
public:
// -- member types -----------------------------------------------------------
template <class Type>
using metric_family_ptr = std::unique_ptr<metric_family_impl<Type>>;
template <class Type>
using metric_family_container = std::vector<metric_family_ptr<Type>>;
// -- constructors, destructors, and assignment operators --------------------
metric_registry();
~metric_registry();
// -- factories --------------------------------------------------------------
/// Returns a metric family. Creates the family lazily if necessary, but fails
/// if the full name already belongs to a different family.
/// @param prefix The prefix (namespace) this family belongs to. Usually the
......@@ -112,6 +118,8 @@ public:
return fptr->get_or_add({});
}
// -- observers --------------------------------------------------------------
template <class Collector>
void collect(Collector& collector) const {
std::unique_lock<std::mutex> guard{families_mx_};
......
......@@ -21,8 +21,10 @@
namespace caf::telemetry {
enum class metric_type : uint8_t {
int_gauge,
dbl_gauge,
int_gauge,
dbl_histogram,
int_histogram,
};
} // namespace caf::telemetry
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2020 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#define CAF_SUITE telemetry.histogram
#include "caf/telemetry/histogram.hpp"
#include "caf/test/dsl.hpp"
#include <cmath>
#include <limits>
#include "caf/telemetry/gauge.hpp"
using namespace caf;
using namespace caf::telemetry;
CAF_TEST(double histograms use infinity for the last bucket) {
dbl_histogram h1{.1, .2, .4, .8};
CAF_CHECK_EQUAL(h1.buckets().size(), 5u);
CAF_CHECK_EQUAL(h1.buckets().front().upper_bound, .1);
CAF_CHECK(std::isinf(h1.buckets().back().upper_bound));
CAF_CHECK_EQUAL(h1.sum(), 0.0);
}
CAF_TEST(integer histograms use int_max for the last bucket) {
using limits = std::numeric_limits<int64_t>;
int_histogram h1{1, 2, 4, 8};
CAF_CHECK_EQUAL(h1.buckets().size(), 5u);
CAF_CHECK_EQUAL(h1.buckets().front().upper_bound, 1);
CAF_CHECK_EQUAL(h1.buckets().back().upper_bound, limits::max());
CAF_CHECK_EQUAL(h1.sum(), 0);
}
CAF_TEST(histograms aggregate to buckets and keep a sum) {
int_histogram h1{2, 4, 8};
for (int64_t value = 1; value < 11; ++value)
h1.observe(value);
auto buckets = h1.buckets();
CAF_REQUIRE_EQUAL(buckets.size(), 4u);
CAF_CHECK_EQUAL(buckets[0].gauge.value(), 2); // 1, 2
CAF_CHECK_EQUAL(buckets[1].gauge.value(), 2); // 3, 4
CAF_CHECK_EQUAL(buckets[2].gauge.value(), 4); // 5, 6, 7, 8
CAF_CHECK_EQUAL(buckets[3].gauge.value(), 2); // 9, 10
CAF_CHECK_EQUAL(h1.sum(), 55);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment