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

Make histogram buckets configurable per label

parent c6d2df52
......@@ -599,9 +599,6 @@ private:
// -- member variables -------------------------------------------------------
/// Manages all metrics collected by the system.
telemetry::metric_registry metrics_;
/// Provides system-wide callbacks for several actor operations.
actor_profiler* profiler_;
......@@ -647,6 +644,9 @@ private:
/// The system-wide, user-provided configuration.
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,
/// file handle, etc.
std::atomic<bool> logger_dtor_done_;
......
......@@ -22,7 +22,9 @@
#include "caf/config.hpp"
#include "caf/fwd.hpp"
#include "caf/span.hpp"
#include "caf/telemetry/gauge.hpp"
#include "caf/telemetry/label.hpp"
namespace caf::telemetry {
......@@ -50,7 +52,7 @@ public:
// nop
}
explicit counter(const std::vector<label>&) noexcept {
explicit counter(span<const label>) noexcept {
// nop
}
......
......@@ -23,6 +23,7 @@
#include <atomic>
#include <cstdint>
#include "caf/telemetry/label.hpp"
#include "caf/telemetry/metric_type.hpp"
namespace caf::telemetry {
......@@ -51,6 +52,10 @@ public:
// nop
}
explicit dbl_gauge(span<const label>) noexcept : value_(0) {
// nop
}
// -- modifiers --------------------------------------------------------------
/// Increments the gauge by 1.
......
......@@ -23,9 +23,11 @@
#include "caf/config.hpp"
#include "caf/fwd.hpp"
#include "caf/settings.hpp"
#include "caf/span.hpp"
#include "caf/telemetry/counter.hpp"
#include "caf/telemetry/gauge.hpp"
#include "caf/telemetry/label.hpp"
#include "caf/telemetry/metric_type.hpp"
namespace caf::telemetry {
......@@ -57,22 +59,15 @@ public:
// -- constructors, destructors, and assignment operators --------------------
explicit 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(span<const label> labels, const settings* cfg,
span<const value_type> upper_bounds) {
if (!init_buckets_from_config(labels, cfg))
init_buckets(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
}
......@@ -86,6 +81,8 @@ public:
// -- modifiers --------------------------------------------------------------
/// Increments the bucket where the observed value falls into and increments
/// the sum of all observed values.
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 counters.
......@@ -101,15 +98,45 @@ public:
// -- observers --------------------------------------------------------------
/// Returns the ``counter`` objects with the configured upper bounds.
span<const bucket_type> buckets() const noexcept {
return {buckets_, num_buckets_};
}
/// Returns the sum of all observed values.
value_type sum() const noexcept {
return sum_.value();
}
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_;
bucket_type* buckets_;
gauge_type sum_;
......
......@@ -23,6 +23,9 @@
#include <atomic>
#include <cstdint>
#include "caf/fwd.hpp"
#include "caf/span.hpp"
#include "caf/telemetry/label.hpp"
#include "caf/telemetry/metric_type.hpp"
namespace caf::telemetry {
......@@ -51,6 +54,10 @@ public:
// nop
}
explicit int_gauge(span<const label>) noexcept : value_(0) {
// nop
}
// -- modifiers --------------------------------------------------------------
/// Increments the gauge by 1.
......
......@@ -45,7 +45,7 @@ public:
return labels_;
}
private:
protected:
// -- member variables -------------------------------------------------------
std::vector<label> labels_;
......
......@@ -42,6 +42,19 @@ public:
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>
metric_family_impl(std::string prefix, std::string name,
std::vector<std::string> label_names, std::string helptext,
......@@ -49,6 +62,7 @@ public:
: super(Type::runtime_type, std::move(prefix), std::move(name),
std::move(label_names), std::move(helptext), std::move(unit),
is_sum),
config_(nullptr),
extra_setting_(std::forward<Ts>(xs)...) {
// nop
}
......@@ -68,7 +82,7 @@ public:
if constexpr (std::is_same<extra_setting_type, unit_t>::value)
ptr.reset(new impl_type(std::move(cpy)));
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));
}
return std::addressof(m->get()->impl());
......@@ -80,10 +94,14 @@ public:
// -- properties --
const auto& extra_setting() const noexcept {
const extra_setting_type& extra_setting() const noexcept {
return extra_setting_;
}
const settings* config() const noexcept {
return config_;
}
template <class Collector>
void collect(Collector& collector) const {
std::unique_lock<std::mutex> guard{mx_};
......@@ -92,6 +110,7 @@ public:
}
private:
const settings* config_;
extra_setting_type extra_setting_;
mutable std::mutex mx_;
std::vector<std::unique_ptr<impl_type>> metrics_;
......
......@@ -34,8 +34,8 @@ public:
using family_setting = typename Type::family_setting;
template <class... Ts>
metric_impl(std::vector<label> labels, Ts&&... xs)
: metric(std::move(labels)), impl_(std::forward<Ts>(xs)...) {
explicit metric_impl(std::vector<label> labels, Ts&&... xs)
: metric(std::move(labels)), impl_(this->labels_, std::forward<Ts>(xs)...) {
// nop
}
......
......@@ -38,10 +38,25 @@ namespace caf::telemetry {
/// Manages a collection of metric families.
class CAF_CORE_EXPORT metric_registry {
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 --------------------
metric_registry();
explicit metric_registry(const actor_system_config& cfg);
~metric_registry();
// -- factories --------------------------------------------------------------
......@@ -53,7 +68,7 @@ public:
/// 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 labels Names 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
......@@ -63,19 +78,18 @@ public:
/// interest. For example, the total number of HTTP requests.
template <class ValueType = int64_t>
metric_family_impl<gauge<ValueType>>*
gauge_family(string_view prefix, string_view name,
span<const string_view> label_names, string_view helptext,
string_view unit = "1", bool is_sum = false) {
gauge_family(string_view prefix, string_view name, span_t<string_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, label_names, unit,
is_sum);
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(label_names),
to_sorted_vec(labels),
to_string(helptext),
to_string(unit), is_sum);
auto result = ptr.get();
......@@ -87,12 +101,94 @@ public:
template <class ValueType = int64_t>
metric_family_impl<gauge<ValueType>>*
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 unit = "1", bool is_sum = false) {
auto lbl_span = make_span(labels.begin(), labels.size());
return gauge_family<ValueType>(prefix, name, lbl_span, helptext, unit,
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) {
auto lbl_span = make_span(label_names.begin(), label_names.size());
return gauge_family<ValueType>(prefix, name, lbl_span, helptext, unit,
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,
......@@ -102,7 +198,7 @@ public:
/// 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 labels Names 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
......@@ -113,18 +209,17 @@ public:
template <class ValueType = int64_t>
metric_family_impl<counter<ValueType>>*
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) {
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, label_names, unit,
is_sum);
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(label_names),
to_sorted_vec(labels),
to_string(helptext),
to_string(unit), is_sum);
auto result = ptr.get();
......@@ -136,15 +231,74 @@ public:
template <class ValueType = int64_t>
metric_family_impl<counter<ValueType>>*
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",
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,
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
/// fails if the full name already belongs to a different family.
/// @param prefix The prefix (namespace) this family belongs to. Usually the
......@@ -160,11 +314,11 @@ public:
/// 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,
counter<ValueType>*
counter_singleton(string_view prefix, string_view name, string_view helptext,
string_view unit = "1", bool is_sum = false) {
span<const string_view> lbls;
auto fptr = gauge_family<ValueType>(prefix, name, lbls, helptext, unit,
span_t<string_view> lbls;
auto fptr = counter_family<ValueType>(prefix, name, lbls, helptext, unit,
is_sum);
return fptr->get_or_add({});
}
......@@ -178,8 +332,6 @@ public:
/// @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 metrics_settings Runtime parameters that may override
/// `default_upper_bounds`. May be `nullptr`.
/// @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
......@@ -190,11 +342,12 @@ public:
/// @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>
metric_family_impl<histogram<ValueType>>* histogram_family(
string_view prefix, string_view name, span<const string_view> label_names,
span<const typename histogram<ValueType>::value_type> default_upper_bounds,
const settings* metrics_settings, string_view helptext,
metric_family_impl<histogram<ValueType>>*
histogram_family(string_view prefix, string_view name,
span_t<string_view> label_names,
span_t<ValueType> default_upper_bounds, string_view helptext,
string_view unit = "1", bool is_sum = false) {
using histogram_type = histogram<ValueType>;
using family_type = metric_family_impl<histogram_type>;
......@@ -207,18 +360,20 @@ public:
is_sum);
return static_cast<family_type*>(ptr);
}
const settings* sub_settings = nullptr;
upper_bounds_list upper_bounds;
if (metrics_settings != nullptr)
if (auto grp = get_if<settings>(metrics_settings, name))
if (auto var = get_if<settings>(grp, name))
if (auto lst = get_if<upper_bounds_list>(var, "buckets"))
if (config_ != nullptr)
if (auto grp = get_if<settings>(config_, prefix))
if (sub_settings = get_if<settings>(grp, name); sub_settings != nullptr)
if (auto lst = get_if<upper_bounds_list>(sub_settings, "buckets"))
upper_bounds = std::move(*lst);
if (upper_bounds.empty())
upper_bounds.assign(default_upper_bounds.begin(),
default_upper_bounds.end());
auto ptr = std::make_unique<family_type>(
to_string(prefix), to_string(name), to_sorted_vec(label_names),
to_string(helptext), to_string(unit), is_sum, std::move(upper_bounds));
sub_settings, to_string(prefix), to_string(name),
to_sorted_vec(label_names), to_string(helptext), to_string(unit), is_sum,
std::move(upper_bounds));
auto result = ptr.get();
families_.emplace_back(std::move(ptr));
return result;
......@@ -226,15 +381,56 @@ public:
/// @copydoc gauge_family
template <class ValueType = int64_t>
metric_family_impl<histogram<ValueType>>* histogram_family(
string_view prefix, string_view name,
metric_family_impl<histogram<ValueType>>*
histogram_family(string_view prefix, string_view name,
std::initializer_list<string_view> label_names,
span<const typename histogram<ValueType>::value_type> upper_bounds,
const settings* metrics_settings, string_view helptext,
span_t<ValueType> upper_bounds, string_view helptext,
string_view unit = "1", bool is_sum = false) {
auto lbl_span = make_span(label_names.begin(), label_names.size());
return histogram_family<ValueType>(prefix, name, lbl_span, upper_bounds,
metrics_settings, helptext, unit,
helptext, unit, 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);
}
......@@ -253,17 +449,23 @@ public:
/// @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 actor system config may override `upper_bounds`.
template <class ValueType = int64_t>
histogram<ValueType>*
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) {
span<const string_view> lbls;
span_t<string_view> lbls;
auto fptr = histogram_family<ValueType>(prefix, name, lbls, upper_bounds,
helptext, unit, is_sum);
return fptr->get_or_add({});
}
/// @internal
void config(const settings* ptr) {
config_ = ptr;
}
// -- observers --------------------------------------------------------------
template <class Collector>
......@@ -278,16 +480,9 @@ private:
/// @pre `families_mx_` is locked.
metric_family* fetch(const string_view& prefix, const string_view& name);
static std::vector<std::string> 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;
}
static std::vector<std::string> to_sorted_vec(span_t<string_view> xs);
static std::vector<std::string> to_sorted_vec(span_t<label_view> xs);
template <class F>
static auto visit_family(F& f, const metric_family* ptr) {
......@@ -309,11 +504,16 @@ private:
}
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);
mutable std::mutex families_mx_;
std::vector<std::unique_ptr<metric_family>> families_;
const caf::settings* config_;
};
} // namespace caf::telemetry
......@@ -226,6 +226,7 @@ actor_system::actor_system(actor_system_config& cfg)
await_actors_before_shutdown_(true),
detached_(0),
cfg_(cfg),
metrics_(cfg),
logger_dtor_done_(false),
tracing_context_(cfg.tracing_context) {
CAF_SET_LOGGER_SYS(this);
......
......@@ -18,6 +18,7 @@
#include "caf/telemetry/metric_registry.hpp"
#include "caf/actor_system_config.hpp"
#include "caf/config.hpp"
#include "caf/telemetry/dbl_gauge.hpp"
#include "caf/telemetry/int_gauge.hpp"
......@@ -36,10 +37,14 @@ bool equals(Range1&& xs, Range2&& ys) {
namespace caf::telemetry {
metric_registry::metric_registry() {
metric_registry::metric_registry() : config_(nullptr) {
// nop
}
metric_registry::metric_registry(const actor_system_config& cfg) {
config_ = get_if<settings>(&cfg, "metrics");
}
metric_registry::~metric_registry() {
// nop
}
......@@ -55,6 +60,30 @@ metric_family* metric_registry::fetch(const string_view& prefix,
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,
metric_type type,
span<const string_view> label_names,
......@@ -76,4 +105,49 @@ void metric_registry::assert_properties(const metric_family* ptr,
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
......@@ -49,8 +49,7 @@ CAF_TEST(the Prometheus collector generates text output) {
auto ov = registry.gauge_family("other", "value", {"x"}, "", "seconds", true);
std::vector<int64_t> upper_bounds{1, 2, 4};
auto sr = registry.histogram_family("some", "request-duration", {"x"},
upper_bounds, nullptr, "Some help.",
"seconds");
upper_bounds, "Some help.", "seconds");
fb->get_or_add({})->value(123);
sv->get_or_add({{"a", "1"}, {"b", "2"}})->value(12);
sv->get_or_add({{"b", "1"}, {"a", "2"}})->value(21);
......
......@@ -108,8 +108,7 @@ CAF_TEST(registries lazily create metrics) {
auto f = registry.gauge_family("caf", "running-actors", {"var1", "var2"},
"How many actors are currently running?");
auto g = registry.histogram_family("caf", "response-time", {"var1", "var2"},
upper_bounds, nullptr,
"How long take requests?");
upper_bounds, "How long take requests?");
std::vector<label_view> v1{{"var1", "foo"}, {"var2", "bar"}};
std::vector<label_view> v1_reversed{{"var2", "bar"}, {"var1", "foo"}};
std::vector<label_view> v2{{"var1", "bar"}, {"var2", "foo"}};
......@@ -171,14 +170,41 @@ caf.mailbox-size{name="parser"} 12)");
}
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;
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> alternative_upper_bounds{10, 20, 30};
put(cfg, "caf.response-time.buckets", upper_bounds);
auto h = registry.histogram_family("caf", "response-time", {"var1", "var2"},
upper_bounds, &cfg,
put(cfg, "caf.response-time.var1=foo.buckets", alternative_upper_bounds);
registry.config(&cfg);
auto hf = registry.histogram_family("caf", "response-time", {"var1", "var2"},
default_upper_bounds,
"How long take requests?");
CAF_CHECK_EQUAL(h->extra_setting(), upper_bounds);
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()
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