Commit 6b1bd132 authored by Dominik Charousset's avatar Dominik Charousset

Add timestamps to Prometheus output

parent 00ee8640
......@@ -18,6 +18,7 @@
#pragma once
#include <ctime>
#include <unordered_map>
#include <vector>
......@@ -30,19 +31,39 @@ namespace caf::telemetry::collector {
/// format. For a documentation of the format, see: https://git.io/fjgDD.
class prometheus {
public:
// -- member types -----------------------------------------------------------
/// A buffer for storing UTF-8 characters. Using a vector instead of a
/// `std::string` has slight performance benefits, since the vector does not
/// have to maintain a null-terminator.
using char_buffer = std::vector<char>;
/// Initializes state before passing this collector to a metric registry.
void begin_collecting();
// -- properties -------------------------------------------------------------
/// Returns the minimum scrape interval, i.e., the minimum time that needs to
/// pass before `collect_from` iterates the registry to re-fill the buffer.
time_t min_scrape_interval() const noexcept {
return min_scrape_interval_;
}
/// Finalizes state after collecting metrics from a registry completed.
void end_collecting();
/// Sets the minimum scrape interval to `value`.
void min_scrape_interval(time_t value) noexcept {
min_scrape_interval_ = value;
}
// -- collect API ------------------------------------------------------------
/// Applies this collector to the registry, filling the character buffer while
/// collecting metrics.
/// @param registry Source for the metrics.
/// @param now Timestamp as time since UNIX epoch in seconds.
/// @returns a view into the filled buffer.
string_view collect_from(const metric_registry& registry, time_t now);
/// Applies this collector to the registry, filling the character buffer while
/// collecting metrics. Uses the current system time as timestamp.
/// @param registry Source for the metrics.
/// @returns a view into the filled buffer.
string_view collect_from(const metric_registry& registry);
// -- call operators for the metric registry ---------------------------------
......@@ -59,11 +80,17 @@ private:
/// Stores the generated text output.
char_buffer buf_;
/// Current timestamp.
time_t now_ = 0;
/// Caches type information and help text for a metric.
std::unordered_map<const metric_family*, char_buffer> meta_info_;
/// Caches which metric family is currently collected.
const metric_family* current_family_ = nullptr;
/// Minimum time between re-iterating the registry.
time_t min_scrape_interval_ = 0;
};
} // namespace caf::telemetry::collector
......@@ -19,6 +19,8 @@
#include "caf/telemetry/collector/prometheus.hpp"
#include <cmath>
#include <ctime>
#include <type_traits>
#include "caf/telemetry/int_gauge.hpp"
#include "caf/telemetry/metric.hpp"
......@@ -44,8 +46,9 @@ void append(prometheus::char_buffer&, char, Ts&&...);
template <class... Ts>
void append(prometheus::char_buffer&, double, Ts&&...);
template <class... Ts>
void append(prometheus::char_buffer&, int64_t, Ts&&...);
template <class T, class... Ts>
std::enable_if_t<std::is_integral<T>::value>
append(prometheus::char_buffer& buf, T val, Ts&&... xs);
template <class... Ts>
void append(prometheus::char_buffer&, const metric_family*, Ts&&...);
......@@ -80,8 +83,9 @@ void append(prometheus::char_buffer& buf, double val, Ts&&... xs) {
append(buf, std::forward<Ts>(xs)...);
}
template <class... Ts>
void append(prometheus::char_buffer& buf, int64_t val, Ts&&... xs) {
template <class T, class... Ts>
std::enable_if_t<std::is_integral<T>::value>
append(prometheus::char_buffer& buf, T val, Ts&&... xs) {
append(buf, std::to_string(val));
append(buf, std::forward<Ts>(xs)...);
}
......@@ -113,17 +117,25 @@ void append(prometheus::char_buffer& buf, const metric* instance, Ts&&... xs) {
} // namespace
string_view prometheus::collect_from(const metric_registry& registry) {
string_view prometheus::collect_from(const metric_registry& registry,
time_t now) {
if (!buf_.empty() && now - now_ < min_scrape_interval_)
return {buf_.data(), buf_.size()};
buf_.clear();
now_ = now;
registry.collect(*this);
current_family_ = nullptr;
return {buf_.data(), buf_.size()};
}
string_view prometheus::collect_from(const metric_registry& registry) {
return collect_from(registry, time(NULL));
}
void prometheus::operator()(const metric_family* family, const metric* instance,
const int_gauge* gauge) {
set_current_family(family, "gauge");
append(buf_, family, instance, ' ', gauge->value(), '\n');
append(buf_, family, instance, ' ', gauge->value(), ' ', now_, '\n');
}
void prometheus::set_current_family(const metric_family* family,
......
......@@ -51,16 +51,16 @@ CAF_TEST(the Prometheus collector generates text output) {
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);
CAF_CHECK_EQUAL(exporter.collect_from(registry),
CAF_CHECK_EQUAL(exporter.collect_from(registry, 42),
R"(# HELP foo_bar_seconds Just some value without labels.
# TYPE foo_bar_seconds gauge
foo_bar_seconds 123
foo_bar_seconds 123 42
# HELP some_value_total Just some (total) value with two labels.
# TYPE some_value_total gauge
some_value_total{a="1",b="2"} 12
some_value_total{a="2",b="1"} 21
some_value_total{a="1",b="2"} 12 42
some_value_total{a="2",b="1"} 21 42
# TYPE other_value_seconds_total gauge
other_value_seconds_total{x="true"} 31337
other_value_seconds_total{x="true"} 31337 42
)"_sv);
CAF_MESSAGE("multiple runs generate the same output");
std::string res1;
......
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