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
} }
......
...@@ -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