Commit f1c30206 authored by Dominik Charousset's avatar Dominik Charousset

Add metric units and collector API

parent e892fcf5
......@@ -34,14 +34,21 @@ public:
// -- constructors, destructors, and assignment operators --------------------
metric_family(std::string prefix, std::string name,
std::vector<std::string> label_names, std::string helptext)
std::vector<std::string> label_names, std::string helptext,
std::string unit, bool is_sum)
: prefix_(std::move(prefix)),
name_(std::move(name)),
label_names_(std::move(label_names)),
helptext_(std::move(helptext)) {
helptext_(std::move(helptext)),
unit_(std::move(unit)),
is_sum_(is_sum) {
// nop
}
metric_family(const metric_family&) = delete;
metric_family& operator=(const metric_family&) = delete;
virtual ~metric_family();
// -- properties -------------------------------------------------------------
......@@ -62,13 +69,21 @@ public:
return helptext_;
}
virtual void scrape() = 0;
const auto& unit() const noexcept {
return unit_;
}
auto is_sum() const noexcept {
return is_sum_;
}
private:
std::string prefix_;
std::string name_;
std::vector<std::string> label_names_;
std::string helptext_;
std::string unit_;
bool is_sum_;
};
} // namespace caf::telemetry
......@@ -54,12 +54,15 @@ public:
return std::addressof(m->get()->impl());
}
void scrape() override {
// nop
template <class Collector>
void collect(Collector& collector) const {
std::unique_lock<std::mutex> guard{mx_};
for (auto& ptr : metrics_)
collector(this, ptr.get(), std::addressof(ptr->impl()));
}
private:
std::mutex mx_;
mutable std::mutex mx_;
std::vector<std::unique_ptr<metric_type>> metrics_;
};
......
......@@ -25,6 +25,7 @@
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
#include "caf/string_view.hpp"
#include "caf/telemetry/metric_family_impl.hpp"
namespace caf::telemetry {
......@@ -35,12 +36,34 @@ public:
~metric_registry();
/// Adds a new metric family to the registry.
/// @param type The kind of the metric, e.g. gauge or count.
/// @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 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.
void add_family(metric_type type, std::string prefix, std::string name,
std::vector<std::string> label_names, std::string helptext);
std::vector<std::string> label_names, std::string helptext,
std::string unit = "1", bool is_sum = false);
telemetry::int_gauge* int_gauge(string_view prefix, string_view name,
std::vector<label_view> labels);
template <class Collector>
void collect(Collector& collector) const {
for (auto& ptr : int_gauges)
ptr->collect(collector);
}
private:
void add_int_gauge_family(std::string prefix, std::string name,
std::vector<std::string> label_names,
......
......@@ -59,25 +59,30 @@ metric_registry::int_gauge(string_view prefix, string_view name,
return (*i)->get_or_add(labels);
}
void metric_registry::add_int_gauge_family(std::string prefix, std::string name,
std::vector<std::string> label_names,
std::string helptext) {
using family_type = metric_family_impl<telemetry::int_gauge>;
std::sort(label_names.begin(), label_names.end());
auto ptr = std::make_unique<family_type>(std::move(prefix), std::move(name),
std::move(label_names),
std::move(helptext));
int_gauges.emplace_back(std::move(ptr));
namespace {
template <class Type>
using metric_family_ptr = std::unique_ptr<metric_family_impl<Type>>;
template <class T, class... Ts>
void emplace_family(std::vector<metric_family_ptr<T>>& families, Ts&&... xs) {
using family_type = metric_family_impl<T>;
families.emplace_back(std::make_unique<family_type>(std::forward<Ts>(xs)...));
}
} // namespace
void metric_registry::add_family(metric_type type, std::string prefix,
std::string name,
std::vector<std::string> label_names,
std::string helptext) {
std::string helptext, std::string unit,
bool is_sum) {
std::sort(label_names.begin(), label_names.end());
switch (type) {
case metric_type::int_gauge:
return add_int_gauge_family(std::move(prefix), std::move(name),
std::move(label_names), std::move(helptext));
return emplace_family(int_gauges, std::move(prefix), std::move(name),
std::move(label_names), std::move(helptext),
std::move(unit), is_sum);
default:
break;
}
......
......@@ -32,6 +32,43 @@ using namespace caf::telemetry;
namespace {
struct collector {
std::string result;
void operator()(const metric_family* family, const metric* instance,
const int_gauge* wrapped) {
result += '\n';
result += family->prefix();
result += '_';
result += family->name();
if (family->unit() != "1") {
result += '_';
result += family->unit();
}
if (family->is_sum())
result += "_total";
if (!instance->labels().empty()) {
result += '{';
auto i = instance->labels().begin();
concat(*i++);
while (i != instance->labels().end()) {
result += ',';
concat(*i++);
}
result += '}';
}
result += ' ';
result += std::to_string(wrapped->value());
}
void concat(const label& lbl) {
result.insert(result.end(), lbl.name().begin(), lbl.name().end());
result += "=\"";
result.insert(result.end(), lbl.value().begin(), lbl.value().end());
result += '"';
}
};
struct fixture {
metric_registry registry;
......@@ -69,4 +106,37 @@ CAF_TEST(registries lazily create metrics) {
CAF_CHECK_EQUAL(var.int_gauge(v2_reversed)->value(), 23);
}
CAF_TEST(registries allow users to collect all registered metrics) {
registry.add_family(metric_type::int_gauge, "foo", "bar", {},
"Just some value without labels.", "seconds");
registry.add_family(metric_type::int_gauge, "some", "value", {"a", "b"},
"Just some (total) value with two labels.", "1", true);
registry.add_family(metric_type::int_gauge, "other", "value", {"x"},
"Just some (total) seconds value with a labels.",
"seconds", true);
registry.add_family(metric_type::int_gauge, "caf", "running_actors", {"node"},
"How many actors are currently running?");
registry.add_family(metric_type::int_gauge, "caf", "mailbox_size", {"name"},
"How many message are currently in mailboxes?");
registry.int_gauge("foo", "bar", {})->value(123);
registry.int_gauge("some", "value", {{"a", "1"}, {"b", "2"}})->value(12);
registry.int_gauge("some", "value", {{"b", "1"}, {"a", "2"}})->value(21);
registry.int_gauge("other", "value", {{"x", "true"}})->value(31337);
auto running_actors = bind("caf", "running_actors");
running_actors.int_gauge({{"node", "localhost"}})->value(42);
auto mailbox_size = bind("caf", "mailbox_size");
mailbox_size.int_gauge({{"name", "printer"}})->value(3);
mailbox_size.int_gauge({{"name", "parser"}})->value(12);
collector c;
registry.collect(c);
CAF_CHECK_EQUAL(c.result, R"(
foo_bar_seconds 123
some_value_total{a="1",b="2"} 12
some_value_total{a="2",b="1"} 21
other_value_seconds_total{x="true"} 31337
caf_running_actors{node="localhost"} 42
caf_mailbox_size{name="printer"} 3
caf_mailbox_size{name="parser"} 12)");
}
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