Commit 4dc849fa authored by Dominik Charousset's avatar Dominik Charousset

Streamline metric registry API and metric naming

parent 0db5680e
...@@ -75,12 +75,22 @@ public: ...@@ -75,12 +75,22 @@ public:
void operator()(const metric_family* family, const metric* instance, void operator()(const metric_family* family, const metric* instance,
const int_gauge* gauge); const int_gauge* gauge);
void operator()(const metric_family* family, const metric* instance,
const dbl_histogram* val);
void operator()(const metric_family* family, const metric* instance,
const int_histogram* val);
private: private:
/// Sets `current_family_` if not pointing to `family` already. When setting /// Sets `current_family_` if not pointing to `family` already. When setting
/// the member variable, also writes meta information to `buf_`. /// the member variable, also writes meta information to `buf_`.
void set_current_family(const metric_family* family, void set_current_family(const metric_family* family,
string_view prometheus_type); string_view prometheus_type);
template <class ValueType>
void append_histogram(const metric_family* family, const metric* instance,
const histogram<ValueType>* val);
/// Stores the generated text output. /// Stores the generated text output.
char_buffer buf_; char_buffer buf_;
...@@ -90,6 +100,9 @@ private: ...@@ -90,6 +100,9 @@ private:
/// Caches type information and help text for a metric. /// Caches type information and help text for a metric.
std::unordered_map<const metric_family*, char_buffer> meta_info_; std::unordered_map<const metric_family*, char_buffer> meta_info_;
/// Caches type information and help text for a metric.
std::unordered_map<const metric*, std::vector<char_buffer>> virtual_metrics_;
/// Caches which metric family is currently collected. /// Caches which metric family is currently collected.
const metric_family* current_family_ = nullptr; const metric_family* current_family_ = nullptr;
......
...@@ -35,6 +35,8 @@ public: ...@@ -35,6 +35,8 @@ public:
using value_type = double; using value_type = double;
using family_setting = unit_t;
// -- constants -------------------------------------------------------------- // -- constants --------------------------------------------------------------
static constexpr metric_type runtime_type = metric_type::dbl_gauge; static constexpr metric_type runtime_type = metric_type::dbl_gauge;
......
...@@ -39,6 +39,8 @@ public: ...@@ -39,6 +39,8 @@ public:
using gauge_type = gauge<value_type>; using gauge_type = gauge<value_type>;
using family_setting = std::vector<value_type>;
struct bucket_type { struct bucket_type {
value_type upper_bound; value_type upper_bound;
gauge_type gauge; gauge_type gauge;
...@@ -52,7 +54,7 @@ public: ...@@ -52,7 +54,7 @@ public:
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
histogram(span<const value_type> upper_bounds) { explicit histogram(span<const value_type> upper_bounds) {
using limits = std::numeric_limits<value_type>; using limits = std::numeric_limits<value_type>;
CAF_ASSERT(std::is_sorted(upper_bounds.begin(), upper_bounds.end())); CAF_ASSERT(std::is_sorted(upper_bounds.begin(), upper_bounds.end()));
num_buckets_ = upper_bounds.size() + 1; num_buckets_ = upper_bounds.size() + 1;
...@@ -66,11 +68,15 @@ public: ...@@ -66,11 +68,15 @@ public:
buckets_[index].upper_bound = limits::max(); buckets_[index].upper_bound = limits::max();
} }
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(make_span(upper_bounds.begin(), upper_bounds.size())) {
// nop // nop
} }
histogram(const histogram&) = delete;
histogram& operator=(const histogram&) = delete;
~histogram() { ~histogram() {
delete[] buckets_; delete[] buckets_;
} }
...@@ -106,9 +112,10 @@ private: ...@@ -106,9 +112,10 @@ private:
gauge_type sum_; gauge_type sum_;
}; };
/// /// Convenience alias for a histogram with value type `double`.
using dbl_histogram = histogram<double>; using dbl_histogram = histogram<double>;
/// Convenience alias for a histogram with value type `int64_t`.
using int_histogram = histogram<int64_t>; using int_histogram = histogram<int64_t>;
} // namespace caf::telemetry } // namespace caf::telemetry
...@@ -35,6 +35,8 @@ public: ...@@ -35,6 +35,8 @@ public:
using value_type = int64_t; using value_type = int64_t;
using family_setting = unit_t;
// -- constants -------------------------------------------------------------- // -- constants --------------------------------------------------------------
static constexpr metric_type runtime_type = metric_type::int_gauge; static constexpr metric_type runtime_type = metric_type::int_gauge;
......
...@@ -59,6 +59,8 @@ public: ...@@ -59,6 +59,8 @@ public:
str_.size() - name_length_ - 1}; str_.size() - name_length_ - 1};
} }
void value(string_view new_value);
/// Returns the label in `name=value` notation. /// Returns the label in `name=value` notation.
const std::string& str() const noexcept { const std::string& str() const noexcept {
return str_; return str_;
......
...@@ -36,9 +36,21 @@ namespace caf::telemetry { ...@@ -36,9 +36,21 @@ namespace caf::telemetry {
template <class Type> template <class Type>
class metric_family_impl : public metric_family { class metric_family_impl : public metric_family {
public: public:
using super = metric_family;
using impl_type = metric_impl<Type>; using impl_type = metric_impl<Type>;
using metric_family::metric_family; using extra_setting_type = typename impl_type::family_setting;
template <class... Ts>
metric_family_impl(std::string prefix, std::string name,
std::vector<std::string> label_names, std::string helptext,
std::string unit, bool is_sum, Ts&&... xs)
: super(std::move(prefix), std::move(name), std::move(label_names),
std::move(helptext), std::move(unit), is_sum),
extra_setting_(std::forward<Ts>(xs)...) {
// nop
}
metric_type type() const noexcept override { metric_type type() const noexcept override {
return Type::runtime_type; return Type::runtime_type;
...@@ -55,7 +67,12 @@ public: ...@@ -55,7 +67,12 @@ public:
if (m == metrics_.end()) { if (m == metrics_.end()) {
std::vector<label> cpy{labels.begin(), labels.end()}; std::vector<label> cpy{labels.begin(), labels.end()};
std::sort(cpy.begin(), cpy.end()); std::sort(cpy.begin(), cpy.end());
m = metrics_.emplace(m, std::make_unique<impl_type>(std::move(cpy))); std::unique_ptr<impl_type> ptr;
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_));
m = metrics_.emplace(m, std::move(ptr));
} }
return std::addressof(m->get()->impl()); return std::addressof(m->get()->impl());
} }
...@@ -64,6 +81,12 @@ public: ...@@ -64,6 +81,12 @@ public:
return get_or_add(span<const label_view>{labels.begin(), labels.size()}); return get_or_add(span<const label_view>{labels.begin(), labels.size()});
} }
// -- properties --
const auto& extra_setting() const noexcept {
return extra_setting_;
}
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_};
...@@ -72,6 +95,7 @@ public: ...@@ -72,6 +95,7 @@ public:
} }
private: private:
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_;
}; };
......
...@@ -31,6 +31,8 @@ namespace caf::telemetry { ...@@ -31,6 +31,8 @@ namespace caf::telemetry {
template <class Type> template <class Type>
class metric_impl : public metric { class metric_impl : public metric {
public: public:
using family_setting = typename Type::family_setting;
template <class... Ts> template <class... Ts>
metric_impl(std::vector<label> labels, Ts&&... xs) metric_impl(std::vector<label> labels, Ts&&... xs)
: metric(std::move(labels)), impl_(std::forward<Ts>(xs)...) { : metric(std::move(labels)), impl_(std::forward<Ts>(xs)...) {
......
...@@ -25,7 +25,10 @@ ...@@ -25,7 +25,10 @@
#include "caf/detail/core_export.hpp" #include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp" #include "caf/fwd.hpp"
#include "caf/raise_error.hpp"
#include "caf/string_view.hpp" #include "caf/string_view.hpp"
#include "caf/telemetry/gauge.hpp"
#include "caf/telemetry/histogram.hpp"
#include "caf/telemetry/metric_family_impl.hpp" #include "caf/telemetry/metric_family_impl.hpp"
namespace caf::telemetry { namespace caf::telemetry {
...@@ -49,8 +52,8 @@ public: ...@@ -49,8 +52,8 @@ public:
// -- factories -------------------------------------------------------------- // -- factories --------------------------------------------------------------
/// Returns a metric family. Creates the family lazily if necessary, but fails /// Returns a gauge metric family. Creates the family lazily if necessary, but
/// 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
/// application or protocol name, e.g., `http`. The prefix `caf` /// application or protocol name, e.g., `http`. The prefix `caf`
/// as well as prefixes starting with an underscore are /// as well as prefixes starting with an underscore are
...@@ -64,41 +67,43 @@ public: ...@@ -64,41 +67,43 @@ 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.
template <class Type> template <class ValueType = int64_t>
metric_family_impl<Type>* family(string_view prefix, string_view name, metric_family_impl<gauge<ValueType>>*
span<const string_view> label_names, gauge_family(string_view prefix, string_view name,
string_view helptext, string_view unit = "1", span<const string_view> label_names, string_view helptext,
bool is_sum = false) { string_view unit = "1", bool is_sum = false) {
using family_type = metric_family_impl<Type>; using gauge_type = gauge<ValueType>;
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, Type::runtime_type, label_names, unit, is_sum); assert_properties(ptr, gauge_type::runtime_type, label_names, unit,
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(label_names),
to_string(helptext), to_string(helptext),
to_string(unit), is_sum); to_string(unit), is_sum);
auto& families = container_by_type<Type>(); auto& families = container_by_type<gauge_type>();
families.emplace_back(std::move(ptr)); families.emplace_back(std::move(ptr));
return families.back().get(); return families.back().get();
} }
/// @copydoc family /// @copydoc gauge_family
template <class Type> template <class ValueType = int64_t>
metric_family_impl<Type>* metric_family_impl<gauge<ValueType>>*
family(string_view prefix, string_view name, gauge_family(string_view prefix, string_view name,
std::initializer_list<string_view> label_names, string_view helptext, std::initializer_list<string_view> label_names,
string_view unit = "1", bool is_sum = false) { string_view helptext, string_view unit = "1",
return family<Type>(prefix, name, bool is_sum = false) {
span<const string_view>{label_names.begin(), auto lbl_span = make_span(label_names.begin(), label_names.size());
label_names.size()}, return gauge_family<ValueType>(prefix, name, lbl_span, helptext, unit,
helptext, unit, is_sum); is_sum);
} }
/// Returns a metric singleton, i.e., the single instance of a family without /// Returns a gauge metric singleton, i.e., the single instance of a family
/// label dimensions. Creates all objects lazily if necessary, but fails if /// without label dimensions. Creates all objects lazily if necessary, but
/// 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
/// application or protocol name, e.g., `http`. The prefix `caf` /// application or protocol name, e.g., `http`. The prefix `caf`
/// as well as prefixes starting with an underscore are /// as well as prefixes starting with an underscore are
...@@ -111,10 +116,91 @@ public: ...@@ -111,10 +116,91 @@ 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.
template <class Type> template <class ValueType = int64_t>
Type* singleton(string_view prefix, string_view name, string_view helptext, gauge<ValueType>*
gauge_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) {
auto fptr = family<Type>(prefix, name, {}, helptext, unit, is_sum); span<const string_view> lbls;
auto fptr = gauge_family<ValueType>(prefix, name, lbls, helptext, unit,
is_sum);
return fptr->get_or_add({});
}
/// Returns a histogram metric family. Creates the family lazily if necessary,
/// but fails if the full name already belongs to a different family.
/// @param prefix The prefix (namespace) this family belongs to. Usually the
/// 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 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.
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> upper_bounds,
string_view helptext, string_view unit = "1", bool is_sum = false) {
if (upper_bounds.empty())
CAF_RAISE_ERROR("at least one bucket must exist");
using histogram_type = histogram<ValueType>;
using family_type = metric_family_impl<histogram_type>;
std::unique_lock<std::mutex> guard{families_mx_};
if (auto ptr = fetch(prefix, name)) {
assert_histogram_properties(ptr, histogram_type::runtime_type,
label_names, upper_bounds, 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_string(helptext), to_string(unit), is_sum, upper_bounds.begin(),
upper_bounds.end());
auto& families = container_by_type<histogram_type>();
families.emplace_back(std::move(ptr));
return families.back().get();
}
/// @copydoc gauge_family
template <class ValueType = int64_t>
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,
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,
helptext, unit, is_sum);
}
/// Returns a histogram 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>
histogram<ValueType>*
histogram_singleton(string_view prefix, string_view name,
string_view helptext, span<const ValueType> upper_bounds,
string_view unit = "1", bool is_sum = false) {
span<const string_view> lbls;
auto fptr = histogram_family<ValueType>(prefix, name, lbls, upper_bounds,
helptext, unit, is_sum);
return fptr->get_or_add({}); return fptr->get_or_add({});
} }
...@@ -127,6 +213,10 @@ public: ...@@ -127,6 +213,10 @@ public:
ptr->collect(collector); ptr->collect(collector);
for (auto& ptr : int_gauges_) for (auto& ptr : int_gauges_)
ptr->collect(collector); ptr->collect(collector);
for (auto& ptr : dbl_histograms_)
ptr->collect(collector);
for (auto& ptr : int_histograms_)
ptr->collect(collector);
} }
private: private:
...@@ -144,11 +234,23 @@ private: ...@@ -144,11 +234,23 @@ private:
return result; return result;
} }
void assert_properties(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<const string_view> label_names, string_view unit,
bool is_sum); bool is_sum);
void assert_equal(metric_family* old_ptr, metric_family* new_ptr); template <class ValueType>
void assert_histogram_properties(const metric_family* ptr, metric_type type,
span<const string_view> label_names,
span<const ValueType> upper_bounds,
string_view unit, bool is_sum) {
assert_properties(ptr, type, label_names, unit, is_sum);
using family_type = metric_family_impl<histogram<ValueType>>;
auto dptr = static_cast<const family_type*>(ptr);
auto& xs = dptr->extra_setting();
if (!std::equal(xs.begin(), xs.end(), upper_bounds.begin(),
upper_bounds.end()))
CAF_RAISE_ERROR("full name with different bucket settings found");
}
template <class Type> template <class Type>
metric_family_container<Type>& container_by_type(); metric_family_container<Type>& container_by_type();
...@@ -157,6 +259,8 @@ private: ...@@ -157,6 +259,8 @@ private:
metric_family_container<telemetry::dbl_gauge> dbl_gauges_; metric_family_container<telemetry::dbl_gauge> dbl_gauges_;
metric_family_container<telemetry::int_gauge> int_gauges_; metric_family_container<telemetry::int_gauge> int_gauges_;
metric_family_container<telemetry::dbl_histogram> dbl_histograms_;
metric_family_container<telemetry::int_histogram> int_histograms_;
}; };
template <> template <>
...@@ -171,4 +275,16 @@ metric_registry::container_by_type<int_gauge>() { ...@@ -171,4 +275,16 @@ metric_registry::container_by_type<int_gauge>() {
return int_gauges_; return int_gauges_;
} }
template <>
inline metric_registry::metric_family_container<int_histogram>&
metric_registry::container_by_type<int_histogram>() {
return int_histograms_;
}
template <>
inline metric_registry::metric_family_container<dbl_histogram>&
metric_registry::container_by_type<dbl_histogram>() {
return dbl_histograms_;
}
} // namespace caf::telemetry } // namespace caf::telemetry
...@@ -49,7 +49,7 @@ actor_registry::~actor_registry() { ...@@ -49,7 +49,7 @@ actor_registry::~actor_registry() {
} }
actor_registry::actor_registry(actor_system& sys) : system_(sys) { actor_registry::actor_registry(actor_system& sys) : system_(sys) {
running_ = sys.telemetry().singleton<telemetry::int_gauge>( running_ = sys.telemetry().gauge_singleton(
"caf", "running_actors", "Number of currently running actors."); "caf", "running_actors", "Number of currently running actors.");
} }
......
...@@ -48,6 +48,10 @@ struct ms_timestamp { ...@@ -48,6 +48,10 @@ struct ms_timestamp {
ms_timestamp& operator=(const ms_timestamp&) = default; ms_timestamp& operator=(const ms_timestamp&) = default;
}; };
struct underline_to_hyphen {
string_view str;
};
void append(prometheus::char_buffer&) { void append(prometheus::char_buffer&) {
// End of recursion. // End of recursion.
} }
...@@ -55,6 +59,9 @@ void append(prometheus::char_buffer&) { ...@@ -55,6 +59,9 @@ void append(prometheus::char_buffer&) {
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer&, string_view, Ts&&...); void append(prometheus::char_buffer&, string_view, Ts&&...);
template <class... Ts>
void append(prometheus::char_buffer&, underline_to_hyphen, Ts&&...);
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer&, char, Ts&&...); void append(prometheus::char_buffer&, char, Ts&&...);
...@@ -68,18 +75,31 @@ append(prometheus::char_buffer& buf, T val, Ts&&... xs); ...@@ -68,18 +75,31 @@ append(prometheus::char_buffer& buf, T val, Ts&&... xs);
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer&, const metric_family*, Ts&&...); void append(prometheus::char_buffer&, const metric_family*, Ts&&...);
template <class... Ts>
void append(prometheus::char_buffer&, const std::vector<label>&, Ts&&...);
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer&, const metric*, Ts&&...); void append(prometheus::char_buffer&, const metric*, Ts&&...);
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer&, ms_timestamp, Ts&&...); void append(prometheus::char_buffer&, ms_timestamp, Ts&&...);
template <class... Ts>
void append(prometheus::char_buffer&, const prometheus::char_buffer&, Ts&&...);
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer& buf, string_view str, Ts&&... xs) { void append(prometheus::char_buffer& buf, string_view str, Ts&&... xs) {
buf.insert(buf.end(), str.begin(), str.end()); buf.insert(buf.end(), str.begin(), str.end());
append(buf, std::forward<Ts>(xs)...); append(buf, std::forward<Ts>(xs)...);
} }
template <class... Ts>
void append(prometheus::char_buffer& buf, underline_to_hyphen x, Ts&&... xs) {
for (auto c : x.str)
buf.emplace_back(c != '-' ? c : '_');
append(buf, std::forward<Ts>(xs)...);
}
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer& buf, char ch, Ts&&... xs) { void append(prometheus::char_buffer& buf, char ch, Ts&&... xs) {
buf.emplace_back(ch); buf.emplace_back(ch);
...@@ -111,7 +131,8 @@ append(prometheus::char_buffer& buf, T val, Ts&&... xs) { ...@@ -111,7 +131,8 @@ append(prometheus::char_buffer& buf, T val, Ts&&... xs) {
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer& buf, const metric_family* family, void append(prometheus::char_buffer& buf, const metric_family* family,
Ts&&... xs) { Ts&&... xs) {
append(buf, family->prefix(), '_', family->name()); append(buf, underline_to_hyphen{family->prefix()}, '_',
underline_to_hyphen{family->name()});
if (family->unit() != "1"_sv) if (family->unit() != "1"_sv)
append(buf, '_', family->unit()); append(buf, '_', family->unit());
if (family->is_sum()) if (family->is_sum())
...@@ -120,8 +141,8 @@ void append(prometheus::char_buffer& buf, const metric_family* family, ...@@ -120,8 +141,8 @@ void append(prometheus::char_buffer& buf, const metric_family* family,
} }
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer& buf, const metric* instance, Ts&&... xs) { void append(prometheus::char_buffer& buf, const std::vector<label>& labels,
const auto& labels = instance->labels(); Ts&&... xs) {
if (!labels.empty()) { if (!labels.empty()) {
append(buf, '{'); append(buf, '{');
auto i = labels.begin(); auto i = labels.begin();
...@@ -133,12 +154,24 @@ void append(prometheus::char_buffer& buf, const metric* instance, Ts&&... xs) { ...@@ -133,12 +154,24 @@ void append(prometheus::char_buffer& buf, const metric* instance, Ts&&... xs) {
append(buf, std::forward<Ts>(xs)...); append(buf, std::forward<Ts>(xs)...);
} }
template <class... Ts>
void append(prometheus::char_buffer& buf, const metric* instance, Ts&&... xs) {
append(buf, instance->labels(), std::forward<Ts>(xs)...);
}
template <class... Ts> template <class... Ts>
void append(prometheus::char_buffer& buf, ms_timestamp ts, Ts&&... xs) { void append(prometheus::char_buffer& buf, ms_timestamp ts, Ts&&... xs) {
append(buf, ts.value); append(buf, ts.value);
append(buf, std::forward<Ts>(xs)...); append(buf, std::forward<Ts>(xs)...);
} }
template <class... Ts>
void append(prometheus::char_buffer& buf, const prometheus::char_buffer& x,
Ts&&... xs) {
buf.insert(buf.end(), x.begin(), x.end());
append(buf, std::forward<Ts>(xs)...);
}
} // namespace } // namespace
string_view prometheus::collect_from(const metric_registry& registry, string_view prometheus::collect_from(const metric_registry& registry,
...@@ -170,6 +203,16 @@ void prometheus::operator()(const metric_family* family, const metric* instance, ...@@ -170,6 +203,16 @@ void prometheus::operator()(const metric_family* family, const metric* instance,
'\n'); '\n');
} }
void prometheus::operator()(const metric_family* family, const metric* instance,
const dbl_histogram* val) {
append_histogram(family, instance, val);
}
void prometheus::operator()(const metric_family* family, const metric* instance,
const int_histogram* val) {
append_histogram(family, instance, val);
}
void prometheus::set_current_family(const metric_family* family, void prometheus::set_current_family(const metric_family* family,
string_view prometheus_type) { string_view prometheus_type) {
if (current_family_ == family) if (current_family_ == family)
...@@ -185,4 +228,61 @@ void prometheus::set_current_family(const metric_family* family, ...@@ -185,4 +228,61 @@ void prometheus::set_current_family(const metric_family* family,
buf_.insert(buf_.end(), i->second.begin(), i->second.end()); buf_.insert(buf_.end(), i->second.begin(), i->second.end());
} }
namespace {
template <class ValueType>
auto make_virtual_metrics(const metric_family* family, const metric* instance,
const histogram<ValueType>* val) {
std::vector<prometheus::char_buffer> result;
auto add_result = [&](auto&&... xs) {
result.emplace_back();
append(result.back(), std::forward<decltype(xs)>(xs)...);
};
auto buckets = val->buckets();
auto num_buckets = buckets.size();
CAF_ASSERT(num_buckets > 1);
auto labels = instance->labels();
labels.emplace_back("le", "");
result.reserve(num_buckets + 2);
size_t index = 0;
// Create bucket variable names for 1..N-1.
for (; index < num_buckets - 1; ++index) {
auto upper_bound = std::to_string(buckets[index].upper_bound);
labels.back().value(upper_bound);
add_result(family, "_bucket", labels, ' ');
}
// The last bucket always sets le="+Inf"
labels.back().value("+Inf");
add_result(family, "_bucket", labels, ' ');
labels.pop_back();
add_result(family, "_sum", labels, ' ');
add_result(family, "_count", labels, ' ');
return result;
}
} // namespace
template <class ValueType>
void prometheus::append_histogram(const metric_family* family,
const metric* instance,
const histogram<ValueType>* val) {
auto i = virtual_metrics_.find(instance);
if (i == virtual_metrics_.end()) {
auto metrics = make_virtual_metrics(family, instance, val);
i = virtual_metrics_.emplace(instance, std::move(metrics)).first;
}
set_current_family(family, "histogram");
auto& vm = i->second;
auto buckets = val->buckets();
size_t index = 0;
for (; index < buckets.size() - 1; ++index) {
append(buf_, vm[index], buckets[index].gauge.value(), ' ',
ms_timestamp{now_}, '\n');
}
auto count = buckets[index].gauge.value();
append(buf_, vm[index], count, ' ', ms_timestamp{now_}, '\n');
append(buf_, vm[++index], val->sum(), ' ', ms_timestamp{now_}, '\n');
append(buf_, vm[++index], count, ' ', ms_timestamp{now_}, '\n');
}
} // namespace caf::telemetry::collector } // namespace caf::telemetry::collector
...@@ -33,6 +33,11 @@ label::label(const label_view& view) : label(view.name(), view.value()) { ...@@ -33,6 +33,11 @@ label::label(const label_view& view) : label(view.name(), view.value()) {
// nop // nop
} }
void label::value(string_view new_value) {
str_.erase(name_length_ + 1);
str_.insert(str_.end(), new_value.begin(), new_value.end());
}
int label::compare(const label& x) const noexcept { int label::compare(const label& x) const noexcept {
return str_.compare(x.str()); return str_.compare(x.str());
} }
......
...@@ -19,13 +19,21 @@ ...@@ -19,13 +19,21 @@
#include "caf/telemetry/metric_registry.hpp" #include "caf/telemetry/metric_registry.hpp"
#include "caf/config.hpp" #include "caf/config.hpp"
#include "caf/raise_error.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"
#include "caf/telemetry/metric_family_impl.hpp" #include "caf/telemetry/metric_family_impl.hpp"
#include "caf/telemetry/metric_impl.hpp" #include "caf/telemetry/metric_impl.hpp"
#include "caf/telemetry/metric_type.hpp" #include "caf/telemetry/metric_type.hpp"
namespace {
template <class Range1, class Range2>
bool equals(Range1&& xs, Range2&& ys) {
return std::equal(xs.begin(), xs.end(), ys.begin(), ys.end());
}
} // namespace
namespace caf::telemetry { namespace caf::telemetry {
metric_registry::metric_registry() { metric_registry::metric_registry() {
...@@ -38,19 +46,26 @@ metric_registry::~metric_registry() { ...@@ -38,19 +46,26 @@ metric_registry::~metric_registry() {
metric_family* metric_registry::fetch(const string_view& prefix, metric_family* metric_registry::fetch(const string_view& prefix,
const string_view& name) { const string_view& name) {
auto matches = [&](const auto& ptr) { auto eq = [&](const auto& ptr) {
return ptr->prefix() == prefix && ptr->name() == name; return ptr->prefix() == prefix && ptr->name() == name;
}; };
if (auto i = std::find_if(dbl_gauges_.begin(), dbl_gauges_.end(), matches); if (auto i = std::find_if(dbl_gauges_.begin(), dbl_gauges_.end(), eq);
i != dbl_gauges_.end()) i != dbl_gauges_.end())
return i->get(); return i->get();
if (auto i = std::find_if(int_gauges_.begin(), int_gauges_.end(), matches); if (auto i = std::find_if(int_gauges_.begin(), int_gauges_.end(), eq);
i != int_gauges_.end()) i != int_gauges_.end())
return i->get(); return i->get();
if (auto i = std::find_if(dbl_histograms_.begin(), dbl_histograms_.end(), eq);
i != dbl_histograms_.end())
return i->get();
if (auto i = std::find_if(int_histograms_.begin(), int_histograms_.end(), eq);
i != int_histograms_.end())
return i->get();
return nullptr; return nullptr;
} }
void metric_registry::assert_properties(metric_family* ptr, metric_type type, void metric_registry::assert_properties(const metric_family* ptr,
metric_type type,
span<const string_view> label_names, span<const string_view> label_names,
string_view unit, bool is_sum) { string_view unit, bool is_sum) {
auto labels_match = [&] { auto labels_match = [&] {
......
...@@ -32,7 +32,6 @@ using namespace caf::telemetry; ...@@ -32,7 +32,6 @@ using namespace caf::telemetry;
namespace { namespace {
struct fixture { struct fixture {
using ig = int_gauge;
collector::prometheus exporter; collector::prometheus exporter;
metric_registry registry; metric_registry registry;
}; };
...@@ -42,16 +41,23 @@ struct fixture { ...@@ -42,16 +41,23 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(prometheus_tests, fixture) CAF_TEST_FIXTURE_SCOPE(prometheus_tests, fixture)
CAF_TEST(the Prometheus collector generates text output) { CAF_TEST(the Prometheus collector generates text output) {
auto fb = registry.family<ig>("foo", "bar", {}, "Some value without labels.", auto fb = registry.gauge_family("foo", "bar", {},
"seconds"); "Some value without labels.", "seconds");
auto sv = registry.family<ig>("some", "value", {"a", "b"}, auto sv = registry.gauge_family("some", "value", {"a", "b"},
"Some (total) value with two labels.", "1", "Some (total) value with two labels.", "1",
true); true);
auto ov = registry.family<ig>("other", "value", {"x"}, "", "seconds", true); 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, "Some help.", "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);
ov->get_or_add({{"x", "true"}})->value(31337); ov->get_or_add({{"x", "true"}})->value(31337);
auto h = sr->get_or_add({{"x", "get"}});
h->observe(3);
h->observe(4);
h->observe(7);
CAF_CHECK_EQUAL(exporter.collect_from(registry, 42), CAF_CHECK_EQUAL(exporter.collect_from(registry, 42),
R"(# HELP foo_bar_seconds Some value without labels. R"(# HELP foo_bar_seconds Some value without labels.
# TYPE foo_bar_seconds gauge # TYPE foo_bar_seconds gauge
...@@ -62,6 +68,14 @@ some_value_total{a="1",b="2"} 12 42000 ...@@ -62,6 +68,14 @@ some_value_total{a="1",b="2"} 12 42000
some_value_total{a="2",b="1"} 21 42000 some_value_total{a="2",b="1"} 21 42000
# TYPE other_value_seconds_total gauge # TYPE other_value_seconds_total gauge
other_value_seconds_total{x="true"} 31337 42000 other_value_seconds_total{x="true"} 31337 42000
# HELP some_request_duration_seconds Some help.
# TYPE some_request_duration_seconds histogram
some_request_duration_seconds_bucket{x="get",le="1"} 0 42000
some_request_duration_seconds_bucket{x="get",le="2"} 0 42000
some_request_duration_seconds_bucket{x="get",le="4"} 2 42000
some_request_duration_seconds_bucket{x="get",le="+Inf"} 1 42000
some_request_duration_seconds_sum{x="get"} 14 42000
some_request_duration_seconds_count{x="get"} 1 42000
)"_sv); )"_sv);
CAF_MESSAGE("multiple runs generate the same output"); CAF_MESSAGE("multiple runs generate the same output");
std::string res1; std::string res1;
......
...@@ -48,17 +48,24 @@ struct test_collector { ...@@ -48,17 +48,24 @@ struct test_collector {
result += std::to_string(wrapped->value()); result += std::to_string(wrapped->value());
} }
template <class T>
void operator()(const metric_family* family, const metric* instance,
const histogram<T>* wrapped) {
concat(family, instance);
result += std::to_string(wrapped->sum());
}
void concat(const metric_family* family, const metric* instance) { void concat(const metric_family* family, const metric* instance) {
result += '\n'; result += '\n';
result += family->prefix(); result += family->prefix();
result += '_'; result += '.';
result += family->name(); result += family->name();
if (family->unit() != "1") { if (family->unit() != "1") {
result += '_'; result += '.';
result += family->unit(); result += family->unit();
} }
if (family->is_sum()) if (family->is_sum())
result += "_total"; result += ".total";
if (!instance->labels().empty()) { if (!instance->labels().empty()) {
result += '{'; result += '{';
auto i = instance->labels().begin(); auto i = instance->labels().begin();
...@@ -81,7 +88,6 @@ struct test_collector { ...@@ -81,7 +88,6 @@ struct test_collector {
}; };
struct fixture { struct fixture {
using ig = int_gauge;
metric_registry registry; metric_registry registry;
test_collector collector; test_collector collector;
}; };
...@@ -91,8 +97,11 @@ struct fixture { ...@@ -91,8 +97,11 @@ struct fixture {
CAF_TEST_FIXTURE_SCOPE(metric_registry_tests, fixture) CAF_TEST_FIXTURE_SCOPE(metric_registry_tests, fixture)
CAF_TEST(registries lazily create metrics) { CAF_TEST(registries lazily create metrics) {
auto f = registry.family<ig>("caf", "running_actors", {"var1", "var2"}, std::vector<int64_t> upper_bounds{1, 2, 4, 8};
"How many actors are currently running?"); 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, "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"}};
...@@ -103,27 +112,33 @@ CAF_TEST(registries lazily create metrics) { ...@@ -103,27 +112,33 @@ CAF_TEST(registries lazily create metrics) {
CAF_CHECK_EQUAL(f->get_or_add(v1_reversed)->value(), 42); CAF_CHECK_EQUAL(f->get_or_add(v1_reversed)->value(), 42);
CAF_CHECK_EQUAL(f->get_or_add(v2)->value(), 23); CAF_CHECK_EQUAL(f->get_or_add(v2)->value(), 23);
CAF_CHECK_EQUAL(f->get_or_add(v2_reversed)->value(), 23); CAF_CHECK_EQUAL(f->get_or_add(v2_reversed)->value(), 23);
g->get_or_add(v1)->observe(3);
g->get_or_add(v2)->observe(7);
CAF_CHECK_EQUAL(g->get_or_add(v1)->sum(), 3);
CAF_CHECK_EQUAL(g->get_or_add(v1_reversed)->sum(), 3);
CAF_CHECK_EQUAL(g->get_or_add(v2)->sum(), 7);
CAF_CHECK_EQUAL(g->get_or_add(v2_reversed)->sum(), 7);
} }
CAF_TEST(registries allow users to collect all registered metrics) { CAF_TEST(registries allow users to collect all registered metrics) {
auto fb = registry.family<ig>("foo", "bar", {}, "Some value without labels.", auto fb = registry.gauge_family("foo", "bar", {},
"seconds"); "Some value without labels.", "seconds");
auto sv = registry.family<ig>("some", "value", {"a", "b"}, auto sv = registry.gauge_family("some", "value", {"a", "b"},
"Some (total) value with two labels.", "1", "Some (total) value with two labels.", "1",
true); true);
auto ov = registry.family<ig>("other", "value", {"x"}, auto ov = registry.gauge_family("other", "value", {"x"},
"Some (total) seconds with one label.", "Some (total) seconds with one label.",
"seconds", true); "seconds", true);
auto ra = registry.family<ig>("caf", "running_actors", {"node"}, auto ra = registry.gauge_family("caf", "running-actors", {"node"},
"How many actors are running?"); "How many actors are running?");
auto ms = registry.family<ig>("caf", "mailbox_size", {"name"}, auto ms = registry.gauge_family("caf", "mailbox-size", {"name"},
"How full is the mailbox?"); "How full is the mailbox?");
CAF_MESSAGE("the registry always returns the same family object"); CAF_MESSAGE("the registry always returns the same family object");
CAF_CHECK_EQUAL(fb, registry.family<ig>("foo", "bar", {}, "", "seconds")); CAF_CHECK_EQUAL(fb, registry.gauge_family("foo", "bar", {}, "", "seconds"));
CAF_CHECK_EQUAL(sv, registry.family<ig>("some", "value", {"a", "b"}, "", "1", CAF_CHECK_EQUAL(sv, registry.gauge_family("some", "value", {"a", "b"}, "",
true)); "1", true));
CAF_CHECK_EQUAL(sv, registry.family<ig>("some", "value", {"b", "a"}, "", "1", CAF_CHECK_EQUAL(sv, registry.gauge_family("some", "value", {"b", "a"}, "",
true)); "1", true));
CAF_MESSAGE("families always return the same metric object for given labels"); CAF_MESSAGE("families always return the same metric object for given labels");
CAF_CHECK_EQUAL(fb->get_or_add({}), fb->get_or_add({})); CAF_CHECK_EQUAL(fb->get_or_add({}), fb->get_or_add({}));
CAF_CHECK_EQUAL(sv->get_or_add({{"a", "1"}, {"b", "2"}}), CAF_CHECK_EQUAL(sv->get_or_add({{"a", "1"}, {"b", "2"}}),
...@@ -138,13 +153,13 @@ CAF_TEST(registries allow users to collect all registered metrics) { ...@@ -138,13 +153,13 @@ CAF_TEST(registries allow users to collect all registered metrics) {
ms->get_or_add({{"name", "parser"}})->value(12); ms->get_or_add({{"name", "parser"}})->value(12);
registry.collect(collector); registry.collect(collector);
CAF_CHECK_EQUAL(collector.result, R"( CAF_CHECK_EQUAL(collector.result, R"(
foo_bar_seconds 123 foo.bar.seconds 123
some_value_total{a="1",b="2"} 12 some.value.total{a="1",b="2"} 12
some_value_total{a="2",b="1"} 21 some.value.total{a="2",b="1"} 21
other_value_seconds_total{x="true"} 31337 other.value.seconds.total{x="true"} 31337
caf_running_actors{node="localhost"} 42 caf.running-actors{node="localhost"} 42
caf_mailbox_size{name="printer"} 3 caf.mailbox-size{name="printer"} 3
caf_mailbox_size{name="parser"} 12)"); caf.mailbox-size{name="parser"} 12)");
} }
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
...@@ -192,12 +192,12 @@ prometheus_broker::prometheus_broker(actor_config& cfg) : io::broker(cfg) { ...@@ -192,12 +192,12 @@ prometheus_broker::prometheus_broker(actor_config& cfg) : io::broker(cfg) {
using telemetry::dbl_gauge; using telemetry::dbl_gauge;
using telemetry::int_gauge; using telemetry::int_gauge;
auto& reg = system().telemetry(); auto& reg = system().telemetry();
cpu_time_ = reg.singleton<dbl_gauge>( cpu_time_ = reg.gauge_singleton<double>(
"process", "cpu", "Total user and system CPU time spent.", "seconds", true); "process", "cpu", "Total user and system CPU time spent.", "seconds", true);
mem_size_ = reg.singleton<int_gauge>("process", "resident_memory", mem_size_ = reg.gauge_singleton("process", "resident_memory",
"Resident memory size.", "bytes"); "Resident memory size.", "bytes");
virt_mem_size_ = reg.singleton<int_gauge>("process", "virtual_memory", virt_mem_size_ = reg.gauge_singleton("process", "virtual_memory",
"Virtual memory size.", "bytes"); "Virtual memory size.", "bytes");
#endif // HAS_PROCESS_METRICS #endif // HAS_PROCESS_METRICS
} }
......
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