Commit 50d010ca authored by Dominik Charousset's avatar Dominik Charousset

Make histogram buckets configurable per label

parent c6d2df52
...@@ -599,9 +599,6 @@ private: ...@@ -599,9 +599,6 @@ private:
// -- member variables ------------------------------------------------------- // -- member variables -------------------------------------------------------
/// Manages all metrics collected by the system.
telemetry::metric_registry metrics_;
/// Provides system-wide callbacks for several actor operations. /// Provides system-wide callbacks for several actor operations.
actor_profiler* profiler_; actor_profiler* profiler_;
...@@ -647,6 +644,9 @@ private: ...@@ -647,6 +644,9 @@ private:
/// The system-wide, user-provided configuration. /// The system-wide, user-provided configuration.
actor_system_config& cfg_; actor_system_config& cfg_;
/// Manages all metrics collected by the system.
telemetry::metric_registry metrics_;
/// Stores whether the logger has run its destructor and stopped any thread, /// Stores whether the logger has run its destructor and stopped any thread,
/// file handle, etc. /// file handle, etc.
std::atomic<bool> logger_dtor_done_; std::atomic<bool> logger_dtor_done_;
......
...@@ -22,7 +22,9 @@ ...@@ -22,7 +22,9 @@
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/span.hpp"
#include "caf/telemetry/gauge.hpp" #include "caf/telemetry/gauge.hpp"
#include "caf/telemetry/label.hpp"
namespace caf::telemetry { namespace caf::telemetry {
...@@ -50,7 +52,7 @@ public: ...@@ -50,7 +52,7 @@ public:
// nop // nop
} }
explicit counter(const std::vector<label>&) noexcept { explicit counter(span<const label>) noexcept {
// nop // nop
} }
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include "caf/telemetry/label.hpp"
#include "caf/telemetry/metric_type.hpp" #include "caf/telemetry/metric_type.hpp"
namespace caf::telemetry { namespace caf::telemetry {
...@@ -51,6 +52,10 @@ public: ...@@ -51,6 +52,10 @@ public:
// nop // nop
} }
explicit dbl_gauge(span<const label>) noexcept : value_(0) {
// nop
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Increments the gauge by 1. /// Increments the gauge by 1.
......
...@@ -23,9 +23,11 @@ ...@@ -23,9 +23,11 @@
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp" #include "caf/span.hpp"
#include "caf/telemetry/counter.hpp" #include "caf/telemetry/counter.hpp"
#include "caf/telemetry/gauge.hpp" #include "caf/telemetry/gauge.hpp"
#include "caf/telemetry/label.hpp"
#include "caf/telemetry/metric_type.hpp" #include "caf/telemetry/metric_type.hpp"
namespace caf::telemetry { namespace caf::telemetry {
...@@ -57,22 +59,15 @@ public: ...@@ -57,22 +59,15 @@ public:
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
explicit histogram(span<const value_type> upper_bounds) { histogram(span<const label> labels, const settings* cfg,
using limits = std::numeric_limits<value_type>; span<const value_type> upper_bounds) {
CAF_ASSERT(std::is_sorted(upper_bounds.begin(), upper_bounds.end())); if (!init_buckets_from_config(labels, cfg))
num_buckets_ = upper_bounds.size() + 1; init_buckets(upper_bounds);
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();
} }
explicit histogram(std::initializer_list<value_type> upper_bounds) explicit histogram(std::initializer_list<value_type> upper_bounds)
: histogram(make_span(upper_bounds.begin(), upper_bounds.size())) { : histogram({}, nullptr,
make_span(upper_bounds.begin(), upper_bounds.size())) {
// nop // nop
} }
...@@ -86,6 +81,8 @@ public: ...@@ -86,6 +81,8 @@ public:
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Increments the bucket where the observed value falls into and increments
/// the sum of all observed values.
void observe(value_type value) { void observe(value_type value) {
// The last bucket has an upper bound of +inf or int_max, so we'll always // The last bucket has an upper bound of +inf or int_max, so we'll always
// find a bucket and increment the counters. // find a bucket and increment the counters.
...@@ -101,15 +98,45 @@ public: ...@@ -101,15 +98,45 @@ public:
// -- observers -------------------------------------------------------------- // -- observers --------------------------------------------------------------
/// Returns the ``counter`` objects with the configured upper bounds.
span<const bucket_type> buckets() const noexcept { span<const bucket_type> buckets() const noexcept {
return {buckets_, num_buckets_}; return {buckets_, num_buckets_};
} }
/// Returns the sum of all observed values.
value_type sum() const noexcept { value_type sum() const noexcept {
return sum_.value(); return sum_.value();
} }
private: private:
void init_buckets(span<const value_type> upper_bounds) {
CAF_ASSERT(std::is_sorted(upper_bounds.begin(), upper_bounds.end()));
using limits = std::numeric_limits<value_type>;
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();
}
bool init_buckets_from_config(span<const label> labels, const settings* cfg) {
if (cfg == nullptr || labels.empty())
return false;
for (const auto& lbl : labels) {
if (auto ptr = get_if<settings>(cfg, lbl.str())) {
if (auto bounds = get_if<std::vector<value_type>>(ptr, "buckets")) {
init_buckets(*bounds);
return true;
}
}
}
return false;
}
size_t num_buckets_; size_t num_buckets_;
bucket_type* buckets_; bucket_type* buckets_;
gauge_type sum_; gauge_type sum_;
......
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
#include <atomic> #include <atomic>
#include <cstdint> #include <cstdint>
#include "caf/fwd.hpp"
#include "caf/span.hpp"
#include "caf/telemetry/label.hpp"
#include "caf/telemetry/metric_type.hpp" #include "caf/telemetry/metric_type.hpp"
namespace caf::telemetry { namespace caf::telemetry {
...@@ -51,6 +54,10 @@ public: ...@@ -51,6 +54,10 @@ public:
// nop // nop
} }
explicit int_gauge(span<const label>) noexcept : value_(0) {
// nop
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
/// Increments the gauge by 1. /// Increments the gauge by 1.
......
...@@ -45,7 +45,7 @@ public: ...@@ -45,7 +45,7 @@ public:
return labels_; return labels_;
} }
private: protected:
// -- member variables ------------------------------------------------------- // -- member variables -------------------------------------------------------
std::vector<label> labels_; std::vector<label> labels_;
......
...@@ -42,6 +42,19 @@ public: ...@@ -42,6 +42,19 @@ public:
using extra_setting_type = typename impl_type::family_setting; using extra_setting_type = typename impl_type::family_setting;
template <class... Ts>
metric_family_impl(const settings* config, std::string prefix,
std::string name, std::vector<std::string> label_names,
std::string helptext, std::string unit, bool is_sum,
Ts&&... xs)
: super(Type::runtime_type, std::move(prefix), std::move(name),
std::move(label_names), std::move(helptext), std::move(unit),
is_sum),
config_(config),
extra_setting_(std::forward<Ts>(xs)...) {
// nop
}
template <class... Ts> template <class... Ts>
metric_family_impl(std::string prefix, std::string name, metric_family_impl(std::string prefix, std::string name,
std::vector<std::string> label_names, std::string helptext, std::vector<std::string> label_names, std::string helptext,
...@@ -49,6 +62,7 @@ public: ...@@ -49,6 +62,7 @@ public:
: super(Type::runtime_type, std::move(prefix), std::move(name), : super(Type::runtime_type, std::move(prefix), std::move(name),
std::move(label_names), std::move(helptext), std::move(unit), std::move(label_names), std::move(helptext), std::move(unit),
is_sum), is_sum),
config_(nullptr),
extra_setting_(std::forward<Ts>(xs)...) { extra_setting_(std::forward<Ts>(xs)...) {
// nop // nop
} }
...@@ -68,7 +82,7 @@ public: ...@@ -68,7 +82,7 @@ public:
if constexpr (std::is_same<extra_setting_type, unit_t>::value) if constexpr (std::is_same<extra_setting_type, unit_t>::value)
ptr.reset(new impl_type(std::move(cpy))); ptr.reset(new impl_type(std::move(cpy)));
else else
ptr.reset(new impl_type(std::move(cpy), extra_setting_)); ptr.reset(new impl_type(std::move(cpy), config_, extra_setting_));
m = metrics_.emplace(m, std::move(ptr)); m = metrics_.emplace(m, std::move(ptr));
} }
return std::addressof(m->get()->impl()); return std::addressof(m->get()->impl());
...@@ -80,10 +94,14 @@ public: ...@@ -80,10 +94,14 @@ public:
// -- properties -- // -- properties --
const auto& extra_setting() const noexcept { const extra_setting_type& extra_setting() const noexcept {
return extra_setting_; return extra_setting_;
} }
const settings* config() const noexcept {
return config_;
}
template <class Collector> template <class Collector>
void collect(Collector& collector) const { void collect(Collector& collector) const {
std::unique_lock<std::mutex> guard{mx_}; std::unique_lock<std::mutex> guard{mx_};
...@@ -92,6 +110,7 @@ public: ...@@ -92,6 +110,7 @@ public:
} }
private: private:
const settings* config_;
extra_setting_type extra_setting_; extra_setting_type extra_setting_;
mutable std::mutex mx_; mutable std::mutex mx_;
std::vector<std::unique_ptr<impl_type>> metrics_; std::vector<std::unique_ptr<impl_type>> metrics_;
......
...@@ -34,8 +34,8 @@ public: ...@@ -34,8 +34,8 @@ public:
using family_setting = typename Type::family_setting; using family_setting = typename Type::family_setting;
template <class... Ts> template <class... Ts>
metric_impl(std::vector<label> labels, Ts&&... xs) explicit metric_impl(std::vector<label> labels, Ts&&... xs)
: metric(std::move(labels)), impl_(std::forward<Ts>(xs)...) { : metric(std::move(labels)), impl_(this->labels_, std::forward<Ts>(xs)...) {
// nop // nop
} }
......
...@@ -38,10 +38,25 @@ namespace caf::telemetry { ...@@ -38,10 +38,25 @@ namespace caf::telemetry {
/// Manages a collection of metric families. /// Manages a collection of metric families.
class CAF_CORE_EXPORT metric_registry { class CAF_CORE_EXPORT metric_registry {
public: public:
// -- member types -----------------------------------------------------------
/// Forces the compiler to use the type `span<const T>` instead of trying to
/// match paremeters to a `span`.
template <class T>
struct span_type {
using type = span<const T>;
};
/// Convenience alias to safe some typing.
template <class T>
using span_t = typename span_type<T>::type;
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
metric_registry(); metric_registry();
explicit metric_registry(const actor_system_config& cfg);
~metric_registry(); ~metric_registry();
// -- factories -------------------------------------------------------------- // -- factories --------------------------------------------------------------
...@@ -53,7 +68,7 @@ public: ...@@ -53,7 +68,7 @@ public:
/// as well as prefixes starting with an underscore are /// as well as prefixes starting with an underscore are
/// reserved. /// reserved.
/// @param name The human-readable name of the metric, e.g., `requests`. /// @param name The human-readable name of the metric, e.g., `requests`.
/// @param label_names Names for all label dimensions of the metric. /// @param labels Names for all label dimensions of the metric.
/// @param helptext Short explanation of the metric. /// @param helptext Short explanation of the metric.
/// @param unit Unit of measurement. Please use base units such as `bytes` or /// @param unit Unit of measurement. Please use base units such as `bytes` or
/// `seconds` (prefer lowercase). The pseudo-unit `1` identifies /// `seconds` (prefer lowercase). The pseudo-unit `1` identifies
...@@ -63,19 +78,18 @@ public: ...@@ -63,19 +78,18 @@ public:
/// interest. For example, the total number of HTTP requests. /// interest. For example, the total number of HTTP requests.
template <class ValueType = int64_t> template <class ValueType = int64_t>
metric_family_impl<gauge<ValueType>>* metric_family_impl<gauge<ValueType>>*
gauge_family(string_view prefix, string_view name, gauge_family(string_view prefix, string_view name, span_t<string_view> labels,
span<const string_view> label_names, string_view helptext, string_view helptext, string_view unit = "1",
string_view unit = "1", bool is_sum = false) { bool is_sum = false) {
using gauge_type = gauge<ValueType>; using gauge_type = gauge<ValueType>;
using family_type = metric_family_impl<gauge_type>; using family_type = metric_family_impl<gauge_type>;
std::unique_lock<std::mutex> guard{families_mx_}; std::unique_lock<std::mutex> guard{families_mx_};
if (auto ptr = fetch(prefix, name)) { if (auto ptr = fetch(prefix, name)) {
assert_properties(ptr, gauge_type::runtime_type, label_names, unit, assert_properties(ptr, gauge_type::runtime_type, labels, unit, is_sum);
is_sum);
return static_cast<family_type*>(ptr); return static_cast<family_type*>(ptr);
} }
auto ptr = std::make_unique<family_type>(to_string(prefix), to_string(name), auto ptr = std::make_unique<family_type>(to_string(prefix), to_string(name),
to_sorted_vec(label_names), to_sorted_vec(labels),
to_string(helptext), to_string(helptext),
to_string(unit), is_sum); to_string(unit), is_sum);
auto result = ptr.get(); auto result = ptr.get();
...@@ -87,14 +101,96 @@ public: ...@@ -87,14 +101,96 @@ public:
template <class ValueType = int64_t> template <class ValueType = int64_t>
metric_family_impl<gauge<ValueType>>* metric_family_impl<gauge<ValueType>>*
gauge_family(string_view prefix, string_view name, gauge_family(string_view prefix, string_view name,
std::initializer_list<string_view> label_names, std::initializer_list<string_view> labels, string_view helptext,
string_view helptext, string_view unit = "1", string_view unit = "1", bool is_sum = false) {
bool is_sum = false) { auto lbl_span = make_span(labels.begin(), labels.size());
auto lbl_span = make_span(label_names.begin(), label_names.size());
return gauge_family<ValueType>(prefix, name, lbl_span, helptext, unit, return gauge_family<ValueType>(prefix, name, lbl_span, helptext, unit,
is_sum); is_sum);
} }
/// @copydoc gauge_family
template <class ValueType = int64_t>
metric_family_impl<gauge<ValueType>>*
gauge_family(string_view prefix, string_view name, span_t<label_view> labels,
string_view helptext, string_view unit = "1",
bool is_sum = false) {
using gauge_type = gauge<ValueType>;
using family_type = metric_family_impl<gauge_type>;
std::unique_lock<std::mutex> guard{families_mx_};
if (auto ptr = fetch(prefix, name)) {
assert_properties(ptr, gauge_type::runtime_type, labels, unit, is_sum);
return static_cast<family_type*>(ptr);
}
auto ptr = std::make_unique<family_type>(to_string(prefix), to_string(name),
to_sorted_vec(labels),
to_string(helptext),
to_string(unit), is_sum);
auto result = ptr.get();
families_.emplace_back(std::move(ptr));
return result;
}
/// Returns a gauge. Creates all objects 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
/// application or protocol name, e.g., `http`. The prefix `caf`
/// as well as prefixes starting with an underscore are
/// reserved.
/// @param name The human-readable name of the metric, e.g., `requests`.
/// @param labels Values for all label dimensions of the metric.
/// @param helptext Short explanation of the metric.
/// @param unit Unit of measurement. Please use base units such as `bytes` or
/// `seconds` (prefer lowercase). The pseudo-unit `1` identifies
/// dimensionless counts.
/// @param is_sum Setting this to `true` indicates that this metric adds
/// something up to a total, where only the total value is of
/// interest. For example, the total number of HTTP requests.
template <class ValueType = int64_t>
gauge<ValueType>* gauge_instance(string_view prefix, string_view name,
span_t<label_view> labels,
string_view helptext, string_view unit = "1",
bool is_sum = false) {
auto fptr = gauge_family<ValueType>(prefix, name, labels, helptext, unit,
is_sum);
return fptr->get_or_add(labels);
}
/// @copydoc gauge_instance
template <class ValueType = int64_t>
gauge<ValueType>* gauge_instance(string_view prefix, string_view name,
std::initializer_list<label_view> labels,
string_view helptext, string_view unit = "1",
bool is_sum = false) {
span_t<label_view> lbls{labels.begin(), labels.size()};
return gauge_instance<ValueType>(prefix, name, lbls, helptext, unit,
is_sum);
}
/// Returns a gauge metric singleton, i.e., the single instance of a family
/// without label dimensions. Creates all objects 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
/// application or protocol name, e.g., `http`. The prefix `caf`
/// as well as prefixes starting with an underscore are
/// reserved.
/// @param name The human-readable name of the metric, e.g., `requests`.
/// @param helptext Short explanation of the metric.
/// @param unit Unit of measurement. Please use base units such as `bytes` or
/// `seconds` (prefer lowercase). The pseudo-unit `1` identifies
/// dimensionless counts.
/// @param is_sum Setting this to `true` indicates that this metric adds
/// something up to a total, where only the total value is of
/// interest. For example, the total number of HTTP requests.
template <class ValueType = int64_t>
gauge<ValueType>*
gauge_singleton(string_view prefix, string_view name, string_view helptext,
string_view unit = "1", bool is_sum = false) {
span_t<string_view> lbls;
auto fptr = gauge_family<ValueType>(prefix, name, lbls, helptext, unit,
is_sum);
return fptr->get_or_add({});
}
/// Returns a counter metric family. Creates the family lazily if necessary, /// Returns a counter metric family. Creates the family lazily if necessary,
/// but fails if the full name already belongs to a different family. /// but fails if the full name already belongs to a different family.
/// @param prefix The prefix (namespace) this family belongs to. Usually the /// @param prefix The prefix (namespace) this family belongs to. Usually the
...@@ -102,7 +198,7 @@ public: ...@@ -102,7 +198,7 @@ public:
/// as well as prefixes starting with an underscore are /// as well as prefixes starting with an underscore are
/// reserved. /// reserved.
/// @param name The human-readable name of the metric, e.g., `requests`. /// @param name The human-readable name of the metric, e.g., `requests`.
/// @param label_names Names for all label dimensions of the metric. /// @param labels Names for all label dimensions of the metric.
/// @param helptext Short explanation of the metric. /// @param helptext Short explanation of the metric.
/// @param unit Unit of measurement. Please use base units such as `bytes` or /// @param unit Unit of measurement. Please use base units such as `bytes` or
/// `seconds` (prefer lowercase). The pseudo-unit `1` identifies /// `seconds` (prefer lowercase). The pseudo-unit `1` identifies
...@@ -113,18 +209,17 @@ public: ...@@ -113,18 +209,17 @@ public:
template <class ValueType = int64_t> template <class ValueType = int64_t>
metric_family_impl<counter<ValueType>>* metric_family_impl<counter<ValueType>>*
counter_family(string_view prefix, string_view name, counter_family(string_view prefix, string_view name,
span<const string_view> label_names, string_view helptext, span_t<string_view> labels, string_view helptext,
string_view unit = "1", bool is_sum = false) { string_view unit = "1", bool is_sum = false) {
using counter_type = counter<ValueType>; using counter_type = counter<ValueType>;
using family_type = metric_family_impl<counter_type>; using family_type = metric_family_impl<counter_type>;
std::unique_lock<std::mutex> guard{families_mx_}; std::unique_lock<std::mutex> guard{families_mx_};
if (auto ptr = fetch(prefix, name)) { if (auto ptr = fetch(prefix, name)) {
assert_properties(ptr, counter_type::runtime_type, label_names, unit, assert_properties(ptr, counter_type::runtime_type, labels, unit, is_sum);
is_sum);
return static_cast<family_type*>(ptr); return static_cast<family_type*>(ptr);
} }
auto ptr = std::make_unique<family_type>(to_string(prefix), to_string(name), auto ptr = std::make_unique<family_type>(to_string(prefix), to_string(name),
to_sorted_vec(label_names), to_sorted_vec(labels),
to_string(helptext), to_string(helptext),
to_string(unit), is_sum); to_string(unit), is_sum);
auto result = ptr.get(); auto result = ptr.get();
...@@ -136,15 +231,74 @@ public: ...@@ -136,15 +231,74 @@ public:
template <class ValueType = int64_t> template <class ValueType = int64_t>
metric_family_impl<counter<ValueType>>* metric_family_impl<counter<ValueType>>*
counter_family(string_view prefix, string_view name, counter_family(string_view prefix, string_view name,
std::initializer_list<string_view> label_names, std::initializer_list<string_view> labels,
string_view helptext, string_view unit = "1", string_view helptext, string_view unit = "1",
bool is_sum = false) { bool is_sum = false) {
auto lbl_span = make_span(label_names.begin(), label_names.size()); auto lbl_span = make_span(labels.begin(), labels.size());
return counter_family<ValueType>(prefix, name, lbl_span, helptext, unit, return counter_family<ValueType>(prefix, name, lbl_span, helptext, unit,
is_sum); is_sum);
} }
/// Returns a gauge metric singleton, i.e., the single instance of a family /// @copydoc counter_family
template <class ValueType = int64_t>
metric_family_impl<counter<ValueType>>*
counter_family(string_view prefix, string_view name,
span_t<label_view> labels, string_view helptext,
string_view unit = "1", bool is_sum = false) {
using counter_type = counter<ValueType>;
using family_type = metric_family_impl<counter_type>;
std::unique_lock<std::mutex> guard{families_mx_};
if (auto ptr = fetch(prefix, name)) {
assert_properties(ptr, counter_type::runtime_type, labels, unit, is_sum);
return static_cast<family_type*>(ptr);
}
auto ptr = std::make_unique<family_type>(to_string(prefix), to_string(name),
to_sorted_vec(labels),
to_string(helptext),
to_string(unit), is_sum);
auto result = ptr.get();
families_.emplace_back(std::move(ptr));
return result;
}
/// Returns a counter. Creates all objects 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
/// application or protocol name, e.g., `http`. The prefix `caf`
/// as well as prefixes starting with an underscore are
/// reserved.
/// @param name The human-readable name of the metric, e.g., `requests`.
/// @param labels Values for all label dimensions of the metric.
/// @param helptext Short explanation of the metric.
/// @param unit Unit of measurement. Please use base units such as `bytes` or
/// `seconds` (prefer lowercase). The pseudo-unit `1` identifies
/// dimensionless counts.
/// @param is_sum Setting this to `true` indicates that this metric adds
/// something up to a total, where only the total value is of
/// interest. For example, the total number of HTTP requests.
template <class ValueType = int64_t>
counter<ValueType>*
counter_instance(string_view prefix, string_view name,
span_t<label_view> labels, string_view helptext,
string_view unit = "1", bool is_sum = false) {
auto fptr = counter_family<ValueType>(prefix, name, labels, helptext, unit,
is_sum);
return fptr->get_or_add(labels);
}
/// @copydoc counter_instance
template <class ValueType = int64_t>
counter<ValueType>* counter_instance(string_view prefix, string_view name,
std::initializer_list<label_view> labels,
string_view helptext,
string_view unit = "1",
bool is_sum = false) {
span_t<label_view> lbls{labels.begin(), labels.size()};
return counter_instance<ValueType>(prefix, name, lbls, helptext, unit,
is_sum);
}
/// Returns a counter metric singleton, i.e., the single instance of a family
/// without label dimensions. Creates all objects lazily if necessary, but /// without label dimensions. Creates all objects lazily if necessary, but
/// fails if the full name already belongs to a different family. /// fails if the full name already belongs to a different family.
/// @param prefix The prefix (namespace) this family belongs to. Usually the /// @param prefix The prefix (namespace) this family belongs to. Usually the
...@@ -160,12 +314,12 @@ public: ...@@ -160,12 +314,12 @@ public:
/// something up to a total, where only the total value is of /// something up to a total, where only the total value is of
/// interest. For example, the total number of HTTP requests. /// interest. For example, the total number of HTTP requests.
template <class ValueType = int64_t> template <class ValueType = int64_t>
gauge<ValueType>* counter<ValueType>*
gauge_singleton(string_view prefix, string_view name, string_view helptext, counter_singleton(string_view prefix, string_view name, string_view helptext,
string_view unit = "1", bool is_sum = false) { string_view unit = "1", bool is_sum = false) {
span<const string_view> lbls; span_t<string_view> lbls;
auto fptr = gauge_family<ValueType>(prefix, name, lbls, helptext, unit, auto fptr = counter_family<ValueType>(prefix, name, lbls, helptext, unit,
is_sum); is_sum);
return fptr->get_or_add({}); return fptr->get_or_add({});
} }
...@@ -178,8 +332,6 @@ public: ...@@ -178,8 +332,6 @@ public:
/// @param name The human-readable name of the metric, e.g., `requests`. /// @param name The human-readable name of the metric, e.g., `requests`.
/// @param label_names Names for all label dimensions of the metric. /// @param label_names Names for all label dimensions of the metric.
/// @param default_upper_bounds Upper bounds for the metric buckets. /// @param default_upper_bounds Upper bounds for the metric buckets.
/// @param metrics_settings Runtime parameters that may override
/// `default_upper_bounds`. May be `nullptr`.
/// @param helptext Short explanation of the metric. /// @param helptext Short explanation of the metric.
/// @param unit Unit of measurement. Please use base units such as `bytes` or /// @param unit Unit of measurement. Please use base units such as `bytes` or
/// `seconds` (prefer lowercase). The pseudo-unit `1` identifies /// `seconds` (prefer lowercase). The pseudo-unit `1` identifies
...@@ -190,12 +342,13 @@ public: ...@@ -190,12 +342,13 @@ public:
/// @note The first call wins when calling this function multiple times with /// @note The first call wins when calling this function multiple times with
/// different bucket settings. Later calls skip checking the bucket /// different bucket settings. Later calls skip checking the bucket
/// settings, mainly because this check would be rather expensive. /// settings, mainly because this check would be rather expensive.
/// @note The actor system config may override `upper_bounds`.
template <class ValueType = int64_t> template <class ValueType = int64_t>
metric_family_impl<histogram<ValueType>>* histogram_family( metric_family_impl<histogram<ValueType>>*
string_view prefix, string_view name, span<const string_view> label_names, histogram_family(string_view prefix, string_view name,
span<const typename histogram<ValueType>::value_type> default_upper_bounds, span_t<string_view> label_names,
const settings* metrics_settings, string_view helptext, span_t<ValueType> default_upper_bounds, string_view helptext,
string_view unit = "1", bool is_sum = false) { string_view unit = "1", bool is_sum = false) {
using histogram_type = histogram<ValueType>; using histogram_type = histogram<ValueType>;
using family_type = metric_family_impl<histogram_type>; using family_type = metric_family_impl<histogram_type>;
using upper_bounds_list = std::vector<ValueType>; using upper_bounds_list = std::vector<ValueType>;
...@@ -207,18 +360,20 @@ public: ...@@ -207,18 +360,20 @@ public:
is_sum); is_sum);
return static_cast<family_type*>(ptr); return static_cast<family_type*>(ptr);
} }
const settings* sub_settings = nullptr;
upper_bounds_list upper_bounds; upper_bounds_list upper_bounds;
if (metrics_settings != nullptr) if (config_ != nullptr)
if (auto grp = get_if<settings>(metrics_settings, name)) if (auto grp = get_if<settings>(config_, prefix))
if (auto var = get_if<settings>(grp, name)) if (sub_settings = get_if<settings>(grp, name); sub_settings != nullptr)
if (auto lst = get_if<upper_bounds_list>(var, "buckets")) if (auto lst = get_if<upper_bounds_list>(sub_settings, "buckets"))
upper_bounds = std::move(*lst); upper_bounds = std::move(*lst);
if (upper_bounds.empty()) if (upper_bounds.empty())
upper_bounds.assign(default_upper_bounds.begin(), upper_bounds.assign(default_upper_bounds.begin(),
default_upper_bounds.end()); default_upper_bounds.end());
auto ptr = std::make_unique<family_type>( auto ptr = std::make_unique<family_type>(
to_string(prefix), to_string(name), to_sorted_vec(label_names), sub_settings, to_string(prefix), to_string(name),
to_string(helptext), to_string(unit), is_sum, std::move(upper_bounds)); to_sorted_vec(label_names), to_string(helptext), to_string(unit), is_sum,
std::move(upper_bounds));
auto result = ptr.get(); auto result = ptr.get();
families_.emplace_back(std::move(ptr)); families_.emplace_back(std::move(ptr));
return result; return result;
...@@ -226,16 +381,57 @@ public: ...@@ -226,16 +381,57 @@ public:
/// @copydoc gauge_family /// @copydoc gauge_family
template <class ValueType = int64_t> template <class ValueType = int64_t>
metric_family_impl<histogram<ValueType>>* histogram_family( metric_family_impl<histogram<ValueType>>*
string_view prefix, string_view name, histogram_family(string_view prefix, string_view name,
std::initializer_list<string_view> label_names, std::initializer_list<string_view> label_names,
span<const typename histogram<ValueType>::value_type> upper_bounds, span_t<ValueType> upper_bounds, string_view helptext,
const settings* metrics_settings, string_view helptext, string_view unit = "1", bool is_sum = false) {
string_view unit = "1", bool is_sum = false) {
auto lbl_span = make_span(label_names.begin(), label_names.size()); auto lbl_span = make_span(label_names.begin(), label_names.size());
return histogram_family<ValueType>(prefix, name, lbl_span, upper_bounds, return histogram_family<ValueType>(prefix, name, lbl_span, upper_bounds,
metrics_settings, helptext, unit, helptext, unit, is_sum);
is_sum); }
/// Returns a histogram. 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
/// application or protocol name, e.g., `http`. The prefix `caf`
/// as well as prefixes starting with an underscore are
/// reserved.
/// @param name The human-readable name of the metric, e.g., `requests`.
/// @param label_names Names for all label dimensions of the metric.
/// @param default_upper_bounds Upper bounds for the metric buckets.
/// @param helptext Short explanation of the metric.
/// @param unit Unit of measurement. Please use base units such as `bytes` or
/// `seconds` (prefer lowercase). The pseudo-unit `1` identifies
/// dimensionless counts.
/// @param is_sum Setting this to `true` indicates that this metric adds
/// something up to a total, where only the total value is of
/// interest. For example, the total number of HTTP requests.
/// @note The first call wins when calling this function multiple times with
/// different bucket settings. Later calls skip checking the bucket
/// settings, mainly because this check would be rather expensive.
/// @note The actor system config may override `upper_bounds`.
template <class ValueType = int64_t>
histogram<ValueType>*
histogram_instance(string_view prefix, string_view name,
span_t<label_view> labels, span_t<ValueType> upper_bounds,
string_view helptext, string_view unit = "1",
bool is_sum = false) {
auto fptr = histogram_family<ValueType>(prefix, name, labels, upper_bounds,
helptext, unit, is_sum);
return fptr->get_or_add(labels);
}
/// @copdoc histogram_instance
template <class ValueType = int64_t>
histogram<ValueType>*
histogram_instance(string_view prefix, string_view name,
std::initializer_list<label_view> labels,
span_t<ValueType> upper_bounds, string_view helptext,
string_view unit = "1", bool is_sum = false) {
span_t<label_view> lbls{labels.begin(), labels.size()};
return histogram_instance(prefix, name, lbls, upper_bounds, helptext, unit,
is_sum);
} }
/// Returns a histogram metric singleton, i.e., the single instance of a /// Returns a histogram metric singleton, i.e., the single instance of a
...@@ -253,17 +449,23 @@ public: ...@@ -253,17 +449,23 @@ public:
/// @param is_sum Setting this to `true` indicates that this metric adds /// @param is_sum Setting this to `true` indicates that this metric adds
/// something up to a total, where only the total value is of /// something up to a total, where only the total value is of
/// interest. For example, the total number of HTTP requests. /// interest. For example, the total number of HTTP requests.
/// @note The actor system config may override `upper_bounds`.
template <class ValueType = int64_t> template <class ValueType = int64_t>
histogram<ValueType>* histogram<ValueType>*
histogram_singleton(string_view prefix, string_view name, histogram_singleton(string_view prefix, string_view name,
string_view helptext, span<const ValueType> upper_bounds, string_view helptext, span_t<ValueType> upper_bounds,
string_view unit = "1", bool is_sum = false) { string_view unit = "1", bool is_sum = false) {
span<const string_view> lbls; span_t<string_view> lbls;
auto fptr = histogram_family<ValueType>(prefix, name, lbls, upper_bounds, auto fptr = histogram_family<ValueType>(prefix, name, lbls, upper_bounds,
helptext, unit, is_sum); helptext, unit, is_sum);
return fptr->get_or_add({}); return fptr->get_or_add({});
} }
/// @internal
void config(const settings* ptr) {
config_ = ptr;
}
// -- observers -------------------------------------------------------------- // -- observers --------------------------------------------------------------
template <class Collector> template <class Collector>
...@@ -278,16 +480,9 @@ private: ...@@ -278,16 +480,9 @@ private:
/// @pre `families_mx_` is locked. /// @pre `families_mx_` is locked.
metric_family* fetch(const string_view& prefix, const string_view& name); metric_family* fetch(const string_view& prefix, const string_view& name);
static std::vector<std::string> to_sorted_vec(span<const string_view> xs) { static std::vector<std::string> to_sorted_vec(span_t<string_view> xs);
std::vector<std::string> result;
if (!xs.empty()) { static std::vector<std::string> to_sorted_vec(span_t<label_view> xs);
result.reserve(xs.size());
for (auto x : xs)
result.emplace_back(to_string(x));
std::sort(result.begin(), result.end());
}
return result;
}
template <class F> template <class F>
static auto visit_family(F& f, const metric_family* ptr) { static auto visit_family(F& f, const metric_family* ptr) {
...@@ -309,11 +504,16 @@ private: ...@@ -309,11 +504,16 @@ private:
} }
void assert_properties(const metric_family* ptr, metric_type type, void assert_properties(const metric_family* ptr, metric_type type,
span<const string_view> label_names, string_view unit, span_t<string_view> label_names, string_view unit,
bool is_sum);
void assert_properties(const metric_family* ptr, metric_type type,
span_t<label_view> label_names, string_view unit,
bool is_sum); bool is_sum);
mutable std::mutex families_mx_; mutable std::mutex families_mx_;
std::vector<std::unique_ptr<metric_family>> families_; std::vector<std::unique_ptr<metric_family>> families_;
const caf::settings* config_;
}; };
} // namespace caf::telemetry } // namespace caf::telemetry
...@@ -226,6 +226,7 @@ actor_system::actor_system(actor_system_config& cfg) ...@@ -226,6 +226,7 @@ actor_system::actor_system(actor_system_config& cfg)
await_actors_before_shutdown_(true), await_actors_before_shutdown_(true),
detached_(0), detached_(0),
cfg_(cfg), cfg_(cfg),
metrics_(cfg),
logger_dtor_done_(false), logger_dtor_done_(false),
tracing_context_(cfg.tracing_context) { tracing_context_(cfg.tracing_context) {
CAF_SET_LOGGER_SYS(this); CAF_SET_LOGGER_SYS(this);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "caf/telemetry/metric_registry.hpp" #include "caf/telemetry/metric_registry.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/telemetry/dbl_gauge.hpp" #include "caf/telemetry/dbl_gauge.hpp"
#include "caf/telemetry/int_gauge.hpp" #include "caf/telemetry/int_gauge.hpp"
...@@ -36,10 +37,14 @@ bool equals(Range1&& xs, Range2&& ys) { ...@@ -36,10 +37,14 @@ bool equals(Range1&& xs, Range2&& ys) {
namespace caf::telemetry { namespace caf::telemetry {
metric_registry::metric_registry() { metric_registry::metric_registry() : config_(nullptr) {
// nop // nop
} }
metric_registry::metric_registry(const actor_system_config& cfg) {
config_ = get_if<settings>(&cfg, "metrics");
}
metric_registry::~metric_registry() { metric_registry::~metric_registry() {
// nop // nop
} }
...@@ -55,6 +60,30 @@ metric_family* metric_registry::fetch(const string_view& prefix, ...@@ -55,6 +60,30 @@ metric_family* metric_registry::fetch(const string_view& prefix,
return nullptr; return nullptr;
} }
std::vector<std::string>
metric_registry::to_sorted_vec(span<const string_view> xs) {
std::vector<std::string> result;
if (!xs.empty()) {
result.reserve(xs.size());
for (auto x : xs)
result.emplace_back(to_string(x));
std::sort(result.begin(), result.end());
}
return result;
}
std::vector<std::string>
metric_registry::to_sorted_vec(span<const label_view> xs) {
std::vector<std::string> result;
if (!xs.empty()) {
result.reserve(xs.size());
for (auto x : xs)
result.emplace_back(to_string(x.name()));
std::sort(result.begin(), result.end());
}
return result;
}
void metric_registry::assert_properties(const metric_family* ptr, void metric_registry::assert_properties(const metric_family* ptr,
metric_type type, metric_type type,
span<const string_view> label_names, span<const string_view> label_names,
...@@ -76,4 +105,49 @@ void metric_registry::assert_properties(const metric_family* ptr, ...@@ -76,4 +105,49 @@ void metric_registry::assert_properties(const metric_family* ptr,
CAF_RAISE_ERROR("full name with different is-sum flag found"); CAF_RAISE_ERROR("full name with different is-sum flag found");
} }
namespace {
struct label_name_eq {
bool operator()(string_view x, string_view y) const noexcept {
return x == y;
}
bool operator()(string_view x, const label_view& y) const noexcept {
return x == y.name();
}
bool operator()(const label_view& x, string_view y) const noexcept {
return x.name() == y;
}
bool operator()(label_view x, label_view y) const noexcept {
return x == y;
}
};
} // namespace
void metric_registry::assert_properties(const metric_family* ptr,
metric_type type,
span<const label_view> labels,
string_view unit, bool is_sum) {
auto labels_match = [&] {
const auto& xs = ptr->label_names();
const auto& ys = labels;
label_name_eq eq;
return std::is_sorted(ys.begin(), ys.end())
? std::equal(xs.begin(), xs.end(), ys.begin(), ys.end(), eq)
: std::is_permutation(xs.begin(), xs.end(), ys.begin(), ys.end(),
eq);
};
if (ptr->type() != type)
CAF_RAISE_ERROR("full name with different metric type found");
if (!labels_match())
CAF_RAISE_ERROR("full name with different label dimensions found");
if (ptr->unit() != unit)
CAF_RAISE_ERROR("full name with different unit found");
if (ptr->is_sum() != is_sum)
CAF_RAISE_ERROR("full name with different is-sum flag found");
}
} // namespace caf::telemetry } // namespace caf::telemetry
...@@ -49,8 +49,7 @@ CAF_TEST(the Prometheus collector generates text output) { ...@@ -49,8 +49,7 @@ CAF_TEST(the Prometheus collector generates text output) {
auto ov = registry.gauge_family("other", "value", {"x"}, "", "seconds", true); auto ov = registry.gauge_family("other", "value", {"x"}, "", "seconds", true);
std::vector<int64_t> upper_bounds{1, 2, 4}; std::vector<int64_t> upper_bounds{1, 2, 4};
auto sr = registry.histogram_family("some", "request-duration", {"x"}, auto sr = registry.histogram_family("some", "request-duration", {"x"},
upper_bounds, nullptr, "Some help.", upper_bounds, "Some help.", "seconds");
"seconds");
fb->get_or_add({})->value(123); fb->get_or_add({})->value(123);
sv->get_or_add({{"a", "1"}, {"b", "2"}})->value(12); sv->get_or_add({{"a", "1"}, {"b", "2"}})->value(12);
sv->get_or_add({{"b", "1"}, {"a", "2"}})->value(21); sv->get_or_add({{"b", "1"}, {"a", "2"}})->value(21);
......
...@@ -108,8 +108,7 @@ CAF_TEST(registries lazily create metrics) { ...@@ -108,8 +108,7 @@ CAF_TEST(registries lazily create metrics) {
auto f = registry.gauge_family("caf", "running-actors", {"var1", "var2"}, auto f = registry.gauge_family("caf", "running-actors", {"var1", "var2"},
"How many actors are currently running?"); "How many actors are currently running?");
auto g = registry.histogram_family("caf", "response-time", {"var1", "var2"}, auto g = registry.histogram_family("caf", "response-time", {"var1", "var2"},
upper_bounds, nullptr, upper_bounds, "How long take requests?");
"How long take requests?");
std::vector<label_view> v1{{"var1", "foo"}, {"var2", "bar"}}; std::vector<label_view> v1{{"var1", "foo"}, {"var2", "bar"}};
std::vector<label_view> v1_reversed{{"var2", "bar"}, {"var1", "foo"}}; std::vector<label_view> v1_reversed{{"var2", "bar"}, {"var1", "foo"}};
std::vector<label_view> v2{{"var1", "bar"}, {"var2", "foo"}}; std::vector<label_view> v2{{"var1", "bar"}, {"var2", "foo"}};
...@@ -171,14 +170,41 @@ caf.mailbox-size{name="parser"} 12)"); ...@@ -171,14 +170,41 @@ caf.mailbox-size{name="parser"} 12)");
} }
CAF_TEST(buckets for histograms are configurable via runtime settings){ CAF_TEST(buckets for histograms are configurable via runtime settings){
auto bounds = [](auto&& buckets) {
std::vector<int64_t> result;
for (auto&& bucket : buckets)
result.emplace_back(bucket.upper_bound);
result.pop_back();
return result;
};
settings cfg; settings cfg;
std::vector<int64_t> default_upper_bounds{1, 2, 4, 8}; std::vector<int64_t> default_upper_bounds{1, 2, 4, 8};
std::vector<int64_t> upper_bounds{1, 2, 3, 5, 7}; std::vector<int64_t> upper_bounds{1, 2, 3, 5, 7};
std::vector<int64_t> alternative_upper_bounds{10, 20, 30};
put(cfg, "caf.response-time.buckets", upper_bounds); put(cfg, "caf.response-time.buckets", upper_bounds);
auto h = registry.histogram_family("caf", "response-time", {"var1", "var2"}, put(cfg, "caf.response-time.var1=foo.buckets", alternative_upper_bounds);
upper_bounds, &cfg, registry.config(&cfg);
"How long take requests?"); auto hf = registry.histogram_family("caf", "response-time", {"var1", "var2"},
CAF_CHECK_EQUAL(h->extra_setting(), upper_bounds); default_upper_bounds,
"How long take requests?");
CAF_CHECK_EQUAL(hf->config(), get_if<settings>(&cfg, "caf.response-time"));
CAF_CHECK_EQUAL(hf->extra_setting(), upper_bounds);
auto h1 = hf->get_or_add({{"var1", "bar"}, {"var2", "baz"}});
CAF_CHECK_EQUAL(bounds(h1->buckets()), upper_bounds);
auto h2 = hf->get_or_add({{"var1", "foo"}, {"var2", "bar"}});
CAF_CHECK_NOT_EQUAL(h1, h2);
CAF_CHECK_EQUAL(bounds(h2->buckets()), alternative_upper_bounds);
}
CAF_TEST(counter_instance is a shortcut for using the family manually) {
auto fptr = registry.counter_family("http", "requests", {"method"},
"Number of HTTP requests.", "seconds",
true);
auto count = fptr->get_or_add({{"method", "put"}});
auto count2
= registry.counter_instance("http", "requests", {{"method", "put"}},
"Number of HTTP requests.", "seconds", true);
CAF_CHECK_EQUAL(count, count2);
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
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