Add prometheus metrics for Central controllers (#1969)
* add header-only prometheus lib to ext * rename folder * Undo rename directory * prometheus simpleapi included on mac & linux * wip * wire up some controller stats * Get windows building with prometheus * bsd build flags for prometheus * Fix multiple network join from environment entrypoint.sh.release (#1961) * _bond_m guards _bond, not _paths_m (#1965) * Fix: warning: mutex '_aqm_m' is not held on every path through here [-Wthread-safety-analysis] (#1964) * Serve prom metrics from /metrics endpoint * Add prom metrics for Central controller specific things * reorganize metric initialization * testing out a labled gauge on Networks * increment error counter on throw * Consolidate metrics definitions Put all metric definitions into node/Metrics.hpp. Accessed as needed from there. * Revert "testing out a labled gauge on Networks" This reverts commit 499ed6d95e11452019cdf48e32ed4cd878c2705b. * still blows up but adding to the record for completeness right now * Fix runtime issues with metrics * Add metrics files to visual studio project * Missed an "extern" * add copyright headers to new files * Add metrics for sent/received bytes (total) * put /metrics endpoint behind auth * sendto returns int on Win32 --------- Co-authored-by: Leonardo Amaral <leleobhz@users.noreply.github.com> Co-authored-by: Brenton Bostick <bostick@gmail.com>
This commit is contained in:
parent
0b03ad9a21
commit
8e6e4ede6d
62 changed files with 4023 additions and 25 deletions
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <atomic>
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
template <typename FloatingType>
|
||||
inline std::atomic<FloatingType>& atomic_add_for_floating_types(std::atomic<FloatingType>& value,
|
||||
const FloatingType& add) {
|
||||
FloatingType desired;
|
||||
FloatingType expected = value.load(std::memory_order_relaxed);
|
||||
do {
|
||||
desired = expected + add;
|
||||
} while (!value.compare_exchange_weak(expected, desired));
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
template <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
|
||||
inline std::atomic<FloatingType>& operator++(std::atomic<FloatingType>& value) {
|
||||
return atomic_add_for_floating_types(value, 1.0);
|
||||
}
|
||||
|
||||
template <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
|
||||
inline std::atomic<FloatingType>& operator+=(std::atomic<FloatingType>& value, const FloatingType& val) {
|
||||
return atomic_add_for_floating_types(value, val);
|
||||
}
|
||||
|
||||
template <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
|
||||
inline std::atomic<FloatingType>& operator--(std::atomic<FloatingType>& value) {
|
||||
return atomic_add_for_floating_types(value, -1.0);
|
||||
}
|
||||
|
||||
template <typename FloatingType, class = typename std::enable_if<std::is_floating_point<FloatingType>::value, int>::type>
|
||||
inline std::atomic<FloatingType>& operator-=(std::atomic<FloatingType>& value, const FloatingType& val) {
|
||||
return atomic_add_for_floating_types(value, -val);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include "prometheus/metric.h"
|
||||
#include "prometheus/family.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
class Benchmark : public Metric {
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool already_started = false;
|
||||
#endif
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> start_;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock>::duration elapsed = std::chrono::time_point<std::chrono::high_resolution_clock>::duration::zero(); // elapsed time
|
||||
|
||||
public:
|
||||
|
||||
using Value = double;
|
||||
using Family = CustomFamily<Benchmark>;
|
||||
|
||||
static const Metric::Type static_type = Metric::Type::Counter;
|
||||
|
||||
Benchmark() : Metric(Metric::Type::Counter) {}
|
||||
|
||||
void start() {
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (already_started)
|
||||
throw std::runtime_error("try to start already started counter");
|
||||
else
|
||||
already_started = true;
|
||||
#endif
|
||||
|
||||
start_ = std::chrono::high_resolution_clock::now();
|
||||
|
||||
}
|
||||
|
||||
void stop() {
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (already_started == false)
|
||||
throw std::runtime_error("try to stop already stoped counter");
|
||||
#endif
|
||||
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> stop;
|
||||
stop = std::chrono::high_resolution_clock::now();
|
||||
elapsed += stop - start_;
|
||||
|
||||
#ifndef NDEBUG
|
||||
already_started = false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
double Get() const {
|
||||
return std::chrono::duration_cast<std::chrono::duration<double>>(elapsed).count();
|
||||
}
|
||||
|
||||
virtual ClientMetric Collect() const {
|
||||
ClientMetric metric;
|
||||
metric.counter.value = Get();
|
||||
return metric;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "registry.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
template <typename CustomMetric>
|
||||
class Builder {
|
||||
|
||||
Family::Labels labels_;
|
||||
std::string name_;
|
||||
std::string help_;
|
||||
|
||||
public:
|
||||
Builder& Labels(const std::map<const std::string, const std::string>& labels) {
|
||||
labels_ = labels;
|
||||
return *this;
|
||||
}
|
||||
Builder& Name(const std::string& name) {
|
||||
name_ = name;
|
||||
return *this;
|
||||
}
|
||||
Builder& Help(const std::string& help) {
|
||||
help_ = help;
|
||||
return *this;
|
||||
}
|
||||
CustomFamily<CustomMetric>& Register(Registry& registry) {
|
||||
return registry.Add<CustomFamily<CustomMetric>>(name_, help_, labels_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class CKMSQuantiles {
|
||||
|
||||
public:
|
||||
|
||||
struct Quantile {
|
||||
|
||||
double quantile;
|
||||
double error;
|
||||
double u;
|
||||
double v;
|
||||
|
||||
Quantile(double quantile, double error)
|
||||
: quantile(quantile),
|
||||
error(error),
|
||||
u(2.0 * error / (1.0 - quantile)),
|
||||
v(2.0 * error / quantile) {}
|
||||
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
struct Item {
|
||||
|
||||
double value;
|
||||
int g;
|
||||
int delta;
|
||||
|
||||
Item(double value, int lower_delta, int delta)
|
||||
: value(value), g(lower_delta), delta(delta) {}
|
||||
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
explicit CKMSQuantiles(const std::vector<Quantile>& quantiles)
|
||||
: quantiles_(quantiles), count_(0), buffer_{}, buffer_count_(0) {}
|
||||
|
||||
void insert(double value) {
|
||||
buffer_[buffer_count_] = value;
|
||||
++buffer_count_;
|
||||
|
||||
if (buffer_count_ == buffer_.size()) {
|
||||
insertBatch();
|
||||
compress();
|
||||
}
|
||||
}
|
||||
|
||||
double get(double q) {
|
||||
insertBatch();
|
||||
compress();
|
||||
|
||||
if (sample_.empty()) {
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
int rankMin = 0;
|
||||
const auto desired = static_cast<int>(q * static_cast<double>(count_));
|
||||
const auto bound = desired + (allowableError(desired) / 2);
|
||||
|
||||
auto it = sample_.begin();
|
||||
decltype(it) prev;
|
||||
auto cur = it++;
|
||||
|
||||
while (it != sample_.end()) {
|
||||
prev = cur;
|
||||
cur = it++;
|
||||
|
||||
rankMin += prev->g;
|
||||
|
||||
if (rankMin + cur->g + cur->delta > bound) {
|
||||
return prev->value;
|
||||
}
|
||||
}
|
||||
|
||||
return sample_.back().value;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
count_ = 0;
|
||||
sample_.clear();
|
||||
buffer_count_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
double allowableError(int rank) {
|
||||
auto size = sample_.size();
|
||||
double minError = static_cast<double>(size + 1);
|
||||
|
||||
for (const auto& q : quantiles_.get()) {
|
||||
double error;
|
||||
if (static_cast<double>(rank) <= q.quantile * static_cast<double>(size)) {
|
||||
error = q.u * static_cast<double>(size - rank);
|
||||
}
|
||||
else {
|
||||
error = q.v * rank;
|
||||
}
|
||||
if (error < minError) {
|
||||
minError = error;
|
||||
}
|
||||
}
|
||||
|
||||
return minError;
|
||||
}
|
||||
|
||||
bool insertBatch() {
|
||||
if (buffer_count_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::sort(buffer_.begin(), buffer_.begin() + buffer_count_);
|
||||
|
||||
std::size_t start = 0;
|
||||
if (sample_.empty()) {
|
||||
sample_.emplace_back(buffer_[0], 1, 0);
|
||||
++start;
|
||||
++count_;
|
||||
}
|
||||
|
||||
std::size_t idx = 0;
|
||||
std::size_t item = idx++;
|
||||
|
||||
for (std::size_t i = start; i < buffer_count_; ++i) {
|
||||
double v = buffer_[i];
|
||||
while (idx < sample_.size() && sample_[item].value < v) {
|
||||
item = idx++;
|
||||
}
|
||||
|
||||
if (sample_[item].value > v) {
|
||||
--idx;
|
||||
}
|
||||
|
||||
int delta;
|
||||
if (idx - 1 == 0 || idx + 1 == sample_.size()) {
|
||||
delta = 0;
|
||||
}
|
||||
else {
|
||||
delta = static_cast<int>(std::floor(allowableError(static_cast<int>(idx + 1)))) + 1;
|
||||
}
|
||||
|
||||
sample_.emplace(sample_.begin() + idx, v, 1, delta);
|
||||
count_++;
|
||||
item = idx++;
|
||||
}
|
||||
|
||||
buffer_count_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void compress() {
|
||||
if (sample_.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t idx = 0;
|
||||
std::size_t prev;
|
||||
std::size_t next = idx++;
|
||||
|
||||
while (idx < sample_.size()) {
|
||||
prev = next;
|
||||
next = idx++;
|
||||
|
||||
if (sample_[prev].g + sample_[next].g + sample_[next].delta <=
|
||||
allowableError(static_cast<int>(idx - 1))) {
|
||||
sample_[next].g += sample_[prev].g;
|
||||
sample_.erase(sample_.begin() + prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const std::reference_wrapper<const std::vector<Quantile>> quantiles_;
|
||||
|
||||
std::size_t count_;
|
||||
std::vector<Item> sample_;
|
||||
std::array<double, 500> buffer_;
|
||||
std::size_t buffer_count_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
// ñòðóêòóðà, â êîòîðóþ êîïèðóþòñÿ çíà÷åíèÿ ìåòðèê ïåðåä èõ ñåðèàëèçàöèåé
|
||||
struct ClientMetric {
|
||||
|
||||
// Label
|
||||
|
||||
struct Label {
|
||||
|
||||
std::string name;
|
||||
std::string value;
|
||||
|
||||
Label(const std::string name_, const std::string value_) : name(name_), value(value_) {}
|
||||
|
||||
bool operator<(const Label& rhs) const {
|
||||
return std::tie(name, value) < std::tie(rhs.name, rhs.value);
|
||||
}
|
||||
|
||||
bool operator==(const Label& rhs) const {
|
||||
return std::tie(name, value) == std::tie(rhs.name, rhs.value);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
std::vector<Label> label;
|
||||
|
||||
// Counter
|
||||
|
||||
struct Counter {
|
||||
double value = 0.0;
|
||||
};
|
||||
|
||||
Counter counter;
|
||||
|
||||
// Gauge
|
||||
|
||||
struct Gauge {
|
||||
double value = 0.0;
|
||||
};
|
||||
|
||||
Gauge gauge;
|
||||
|
||||
// Summary
|
||||
|
||||
struct Quantile {
|
||||
double quantile = 0.0;
|
||||
double value = 0.0;
|
||||
};
|
||||
|
||||
struct Summary {
|
||||
std::uint64_t sample_count = 0;
|
||||
double sample_sum = 0.0;
|
||||
std::vector<Quantile> quantile;
|
||||
};
|
||||
|
||||
Summary summary;
|
||||
|
||||
// Histogram
|
||||
|
||||
struct Bucket {
|
||||
std::uint64_t cumulative_count = 0;
|
||||
double upper_bound = 0.0;
|
||||
};
|
||||
|
||||
struct Histogram {
|
||||
std::uint64_t sample_count = 0;
|
||||
double sample_sum = 0.0;
|
||||
std::vector<Bucket> bucket;
|
||||
};
|
||||
|
||||
Histogram histogram;
|
||||
|
||||
// Untyped
|
||||
|
||||
struct Untyped {
|
||||
double value = 0;
|
||||
};
|
||||
|
||||
Untyped untyped;
|
||||
|
||||
// Timestamp
|
||||
|
||||
std::int64_t timestamp_ms = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "prometheus/metric_family.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
/// @brief Interface implemented by anything that can be used by Prometheus to
|
||||
/// collect metrics.
|
||||
///
|
||||
/// A Collectable has to be registered for collection. See Registry.
|
||||
class Collectable {
|
||||
|
||||
public:
|
||||
|
||||
//Collectable() = default;
|
||||
|
||||
virtual ~Collectable() = default;
|
||||
|
||||
using MetricFamilies = std::vector<MetricFamily>;
|
||||
|
||||
/// \brief Returns a list of metrics and their samples.
|
||||
virtual MetricFamilies Collect() const = 0;
|
||||
};
|
||||
|
||||
} // namespace prometheus
|
112
ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h
Normal file
112
ext/prometheus-cpp-lite-1.0/core/include/prometheus/counter.h
Normal file
|
@ -0,0 +1,112 @@
|
|||
#pragma once
|
||||
|
||||
#include "prometheus/atomic_floating.h"
|
||||
#include "prometheus/metric.h"
|
||||
#include "prometheus/family.h"
|
||||
|
||||
#include "prometheus/builder.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
/// \brief A counter metric to represent a monotonically increasing value.
|
||||
///
|
||||
/// This class represents the metric type counter:
|
||||
/// https://prometheus.io/docs/concepts/metric_types/#counter
|
||||
///
|
||||
/// The value of the counter can only increase. Example of counters are:
|
||||
/// - the number of requests served
|
||||
/// - tasks completed
|
||||
/// - errors
|
||||
///
|
||||
/// Do not use a counter to expose a value that can decrease - instead use a
|
||||
/// Gauge.
|
||||
///
|
||||
/// The class is thread-safe. No concurrent call to any API of this type causes
|
||||
/// a data race.
|
||||
template <typename Value_ = uint64_t>
|
||||
class Counter : public Metric {
|
||||
|
||||
std::atomic<Value_> value{ 0 };
|
||||
|
||||
public:
|
||||
|
||||
using Value = Value_;
|
||||
using Family = CustomFamily<Counter<Value>>;
|
||||
|
||||
static const Metric::Type static_type = Metric::Type::Counter;
|
||||
|
||||
Counter() : Metric (Metric::Type::Counter) {} ///< \brief Create a counter that starts at 0.
|
||||
|
||||
// original API
|
||||
|
||||
void Increment() { ///< \brief Increment the counter by 1.
|
||||
++value;
|
||||
}
|
||||
|
||||
void Increment(const Value& val) { ///< \brief Increment the counter by a given amount. The counter will not change if the given amount is negative.
|
||||
if (val > 0)
|
||||
value += val;
|
||||
}
|
||||
|
||||
const Value Get() const { ///< \brief Get the current value of the counter.
|
||||
return value;
|
||||
}
|
||||
|
||||
virtual ClientMetric Collect() const { ///< /// \brief Get the current value of the counter. Collect is called by the Registry when collecting metrics.
|
||||
ClientMetric metric;
|
||||
metric.counter.value = static_cast<double>(value);
|
||||
return metric;
|
||||
}
|
||||
|
||||
// new API
|
||||
|
||||
Counter& operator ++() {
|
||||
++value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter& operator++ (int) {
|
||||
++value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Counter& operator += (const Value& val) {
|
||||
value += val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/// \brief Return a builder to configure and register a Counter metric.
|
||||
///
|
||||
/// @copydetails Family<>::Family()
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// \code
|
||||
/// auto registry = std::make_shared<Registry>();
|
||||
/// auto& counter_family = prometheus::BuildCounter()
|
||||
/// .Name("some_name")
|
||||
/// .Help("Additional description.")
|
||||
/// .Labels({{"key", "value"}})
|
||||
/// .Register(*registry);
|
||||
///
|
||||
/// ...
|
||||
/// \endcode
|
||||
///
|
||||
/// \return An object of unspecified type T, i.e., an implementation detail
|
||||
/// except that it has the following members:
|
||||
///
|
||||
/// - Name(const std::string&) to set the metric name,
|
||||
/// - Help(const std::string&) to set an additional description.
|
||||
/// - Label(const std::map<std::string, std::string>&) to assign a set of
|
||||
/// key-value pairs (= labels) to the metric.
|
||||
///
|
||||
/// To finish the configuration of the Counter metric, register it with
|
||||
/// Register(Registry&).
|
||||
using BuildCounter = Builder<Counter<double>>;
|
||||
|
||||
|
||||
} // namespace prometheus
|
355
ext/prometheus-cpp-lite-1.0/core/include/prometheus/family.h
Normal file
355
ext/prometheus-cpp-lite-1.0/core/include/prometheus/family.h
Normal file
|
@ -0,0 +1,355 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include "prometheus/collectable.h"
|
||||
#include "prometheus/metric.h"
|
||||
#include "prometheus/hash.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
/// \brief A metric of type T with a set of labeled dimensions.
|
||||
///
|
||||
/// One of Prometheus main feature is a multi-dimensional data model with time
|
||||
/// series data identified by metric name and key/value pairs, also known as
|
||||
/// labels. A time series is a series of data points indexed (or listed or
|
||||
/// graphed) in time order (https://en.wikipedia.org/wiki/Time_series).
|
||||
///
|
||||
/// An instance of this class is exposed as multiple time series during
|
||||
/// scrape, i.e., one time series for each set of labels provided to Add().
|
||||
///
|
||||
/// For example it is possible to collect data for a metric
|
||||
/// `http_requests_total`, with two time series:
|
||||
///
|
||||
/// - all HTTP requests that used the method POST
|
||||
/// - all HTTP requests that used the method GET
|
||||
///
|
||||
/// The metric name specifies the general feature of a system that is
|
||||
/// measured, e.g., `http_requests_total`. Labels enable Prometheus's
|
||||
/// dimensional data model: any given combination of labels for the same
|
||||
/// metric name identifies a particular dimensional instantiation of that
|
||||
/// metric. For example a label for 'all HTTP requests that used the method
|
||||
/// POST' can be assigned with `method= "POST"`.
|
||||
///
|
||||
/// Given a metric name and a set of labels, time series are frequently
|
||||
/// identified using this notation:
|
||||
///
|
||||
/// <metric name> { < label name >= <label value>, ... }
|
||||
///
|
||||
/// It is required to follow the syntax of metric names and labels given by:
|
||||
/// https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels
|
||||
///
|
||||
/// The following metric and label conventions are not required for using
|
||||
/// Prometheus, but can serve as both a style-guide and a collection of best
|
||||
/// practices: https://prometheus.io/docs/practices/naming/
|
||||
///
|
||||
/// tparam T One of the metric types Counter, Gauge, Histogram or Summary.
|
||||
class Family : public Collectable {
|
||||
|
||||
public:
|
||||
|
||||
using Hash = std::size_t;
|
||||
using Label = std::pair<const std::string, const std::string>;
|
||||
using Labels = std::map <const std::string, const std::string>;
|
||||
using MetricPtr = std::unique_ptr<Metric>;
|
||||
|
||||
const Metric::Type type;
|
||||
const std::string name;
|
||||
const std::string help;
|
||||
const Labels constant_labels;
|
||||
mutable std::mutex mutex;
|
||||
|
||||
std::unordered_map<Hash, MetricPtr> metrics;
|
||||
std::unordered_map<Hash, Labels> labels;
|
||||
std::unordered_map<Metric*, Hash> labels_reverse_lookup;
|
||||
|
||||
|
||||
/// \brief Compute the hash value of a map of labels.
|
||||
///
|
||||
/// \param labels The map that will be computed the hash value.
|
||||
///
|
||||
/// \returns The hash value of the given labels.
|
||||
static Hash hash_labels (const Labels& labels) {
|
||||
size_t seed = 0;
|
||||
for (const Label& label : labels)
|
||||
detail::hash_combine (&seed, label.first, label.second);
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
||||
static bool isLocaleIndependentDigit (char c) { return '0' <= c && c <= '9'; }
|
||||
static bool isLocaleIndependentAlphaNumeric (char c) { return isLocaleIndependentDigit(c) || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); }
|
||||
|
||||
bool nameStartsValid (const std::string& name) {
|
||||
if (name.empty()) return false; // must not be empty
|
||||
if (isLocaleIndependentDigit(name.front())) return false; // must not start with a digit
|
||||
if (name.compare(0, 2, "__") == 0) return false; // must not start with "__"
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \brief Check if the metric name is valid
|
||||
///
|
||||
/// The metric name regex is "[a-zA-Z_:][a-zA-Z0-9_:]*"
|
||||
///
|
||||
/// \see https://prometheus.io/docs/concepts/data_model/
|
||||
///
|
||||
/// \param name metric name
|
||||
/// \return true is valid, false otherwise
|
||||
bool CheckMetricName (const std::string& name) {
|
||||
|
||||
if (!nameStartsValid(name))
|
||||
return false;
|
||||
|
||||
for (const char& c : name)
|
||||
if ( !isLocaleIndependentAlphaNumeric(c) && c != '_' && c != ':' )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/// \brief Check if the label name is valid
|
||||
///
|
||||
/// The label name regex is "[a-zA-Z_][a-zA-Z0-9_]*"
|
||||
///
|
||||
/// \see https://prometheus.io/docs/concepts/data_model/
|
||||
///
|
||||
/// \param name label name
|
||||
/// \return true is valid, false otherwise
|
||||
bool CheckLabelName (const std::string& name) {
|
||||
|
||||
if (!nameStartsValid(name))
|
||||
return false;
|
||||
|
||||
for (const char& c : name)
|
||||
if (!isLocaleIndependentAlphaNumeric(c) && c != '_')
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/// \brief Create a new metric.
|
||||
///
|
||||
/// Every metric is uniquely identified by its name and a set of key-value
|
||||
/// pairs, also known as labels. Prometheus's query language allows filtering
|
||||
/// and aggregation based on metric name and these labels.
|
||||
///
|
||||
/// This example selects all time series that have the `http_requests_total`
|
||||
/// metric name:
|
||||
///
|
||||
/// http_requests_total
|
||||
///
|
||||
/// It is possible to assign labels to the metric name. These labels are
|
||||
/// propagated to each dimensional data added with Add(). For example if a
|
||||
/// label `job= "prometheus"` is provided to this constructor, it is possible
|
||||
/// to filter this time series with Prometheus's query language by appending
|
||||
/// a set of labels to match in curly braces ({})
|
||||
///
|
||||
/// http_requests_total{job= "prometheus"}
|
||||
///
|
||||
/// For further information see: [Quering Basics]
|
||||
/// (https://prometheus.io/docs/prometheus/latest/querying/basics/)
|
||||
///
|
||||
/// \param name Set the metric name.
|
||||
/// \param help Set an additional description.
|
||||
/// \param constant_labels Assign a set of key-value pairs (= labels) to the
|
||||
/// metric. All these labels are propagated to each time series within the
|
||||
/// metric.
|
||||
/// \throw std::runtime_exception on invalid metric or label names.
|
||||
Family (Metric::Type type_, const std::string& name_, const std::string& help_, const Labels& constant_labels_)
|
||||
: type(type_), name(name_), help(help_), constant_labels(constant_labels_) {
|
||||
|
||||
if (!CheckMetricName(name_))
|
||||
throw std::invalid_argument("Invalid metric name");
|
||||
|
||||
for (const Label& label_pair : constant_labels) {
|
||||
const std::string& label_name = label_pair.first;
|
||||
if (!CheckLabelName(label_name))
|
||||
throw std::invalid_argument("Invalid label name");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// \brief Remove the given dimensional data.
|
||||
///
|
||||
/// \param metric Dimensional data to be removed. The function does nothing,
|
||||
/// if the given metric was not returned by Add().
|
||||
void Remove (Metric* metric) {
|
||||
std::lock_guard<std::mutex> lock{ mutex };
|
||||
|
||||
if (labels_reverse_lookup.count(metric) == 0)
|
||||
return;
|
||||
|
||||
const Hash hash = labels_reverse_lookup.at(metric);
|
||||
metrics.erase(hash);
|
||||
labels.erase(hash);
|
||||
labels_reverse_lookup.erase(metric);
|
||||
|
||||
}
|
||||
|
||||
/// \brief Returns true if the dimensional data with the given labels exist
|
||||
///
|
||||
/// \param labels A set of key-value pairs (= labels) of the dimensional data.
|
||||
bool Has (const Labels& labels) const {
|
||||
const Hash hash = hash_labels (labels);
|
||||
std::lock_guard<std::mutex> lock{ mutex };
|
||||
return metrics.find(hash) != metrics.end();
|
||||
}
|
||||
|
||||
/// \brief Returns the name for this family.
|
||||
///
|
||||
/// \return The family name.
|
||||
const std::string& GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
/// \brief Returns the constant labels for this family.
|
||||
///
|
||||
/// \return All constant labels as key-value pairs.
|
||||
const Labels& GetConstantLabels() const {
|
||||
return constant_labels;
|
||||
}
|
||||
|
||||
/// \brief Returns the current value of each dimensional data.
|
||||
///
|
||||
/// Collect is called by the Registry when collecting metrics.
|
||||
///
|
||||
/// \return Zero or more samples for each dimensional data.
|
||||
MetricFamilies Collect() const override {
|
||||
std::lock_guard<std::mutex> lock{ mutex };
|
||||
|
||||
if (metrics.empty())
|
||||
return {};
|
||||
|
||||
MetricFamily family = MetricFamily{};
|
||||
family.type = type;
|
||||
family.name = name;
|
||||
family.help = help;
|
||||
family.metric.reserve(metrics.size());
|
||||
|
||||
for (const std::pair<const Hash, MetricPtr>& metric_pair : metrics) {
|
||||
|
||||
ClientMetric collected = metric_pair.second->Collect();
|
||||
for (const Label& constant_label : constant_labels)
|
||||
collected.label.emplace_back(ClientMetric::Label(constant_label.first, constant_label.second));
|
||||
|
||||
const Labels& metric_labels = labels.at(metric_pair.first);
|
||||
for (const Label& metric_label : metric_labels)
|
||||
collected.label.emplace_back(ClientMetric::Label(metric_label.first, metric_label.second));
|
||||
|
||||
family.metric.push_back(std::move(collected));
|
||||
|
||||
}
|
||||
|
||||
return { family };
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
template <typename CustomMetric>
|
||||
class CustomFamily : public Family {
|
||||
|
||||
public:
|
||||
|
||||
static const Metric::Type static_type = CustomMetric::static_type;
|
||||
|
||||
CustomFamily(const std::string& name, const std::string& help, const Family::Labels& constant_labels)
|
||||
: Family(static_type, name, help, constant_labels) {}
|
||||
|
||||
/// \brief Add a new dimensional data.
|
||||
///
|
||||
/// Each new set of labels adds a new dimensional data and is exposed in
|
||||
/// Prometheus as a time series. It is possible to filter the time series
|
||||
/// with Prometheus's query language by appending a set of labels to match in
|
||||
/// curly braces ({})
|
||||
///
|
||||
/// http_requests_total{job= "prometheus",method= "POST"}
|
||||
///
|
||||
/// \param labels Assign a set of key-value pairs (= labels) to the
|
||||
/// dimensional data. The function does nothing, if the same set of labels
|
||||
/// already exists.
|
||||
/// \param args Arguments are passed to the constructor of metric type T. See
|
||||
/// Counter, Gauge, Histogram or Summary for required constructor arguments.
|
||||
/// \return Return the newly created dimensional data or - if a same set of
|
||||
/// labels already exists - the already existing dimensional data.
|
||||
/// \throw std::runtime_exception on invalid label names.
|
||||
template <typename... Args>
|
||||
CustomMetric& Add (const Labels& new_labels, Args&&... args) {
|
||||
const Hash hash = hash_labels (new_labels);
|
||||
std::lock_guard<std::mutex> lock{ mutex };
|
||||
|
||||
// try to find existing one
|
||||
auto metrics_iter = metrics.find(hash);
|
||||
if (metrics_iter != metrics.end()) {
|
||||
#ifndef NDEBUG
|
||||
// check that we have stored labels for this existing metric
|
||||
auto labels_iter = labels.find(hash);
|
||||
assert(labels_iter != labels.end());
|
||||
const Labels& stored_labels = labels_iter->second;
|
||||
assert(new_labels == stored_labels);
|
||||
#endif
|
||||
return dynamic_cast<CustomMetric&>(*metrics_iter->second);
|
||||
}
|
||||
|
||||
// check labels before create the new one
|
||||
for (const Label& label_pair : new_labels) {
|
||||
const std::string& label_name = label_pair.first;
|
||||
if (!CheckLabelName(label_name))
|
||||
throw std::invalid_argument("Invalid label name");
|
||||
if (constant_labels.count(label_name))
|
||||
throw std::invalid_argument("Label name already present in constant labels");
|
||||
}
|
||||
|
||||
// create new one
|
||||
std::unique_ptr<CustomMetric> metric_ptr (new CustomMetric(std::forward<Args>(args)...));
|
||||
CustomMetric& metric = *metric_ptr;
|
||||
|
||||
const auto stored_metric = metrics.insert(std::make_pair(hash, std::move(metric_ptr)));
|
||||
assert(stored_metric.second);
|
||||
labels.insert({ hash, new_labels });
|
||||
labels_reverse_lookup.insert({ stored_metric.first->second.get(), hash });
|
||||
|
||||
return metric;
|
||||
}
|
||||
|
||||
/// \brief Return a builder to configure and register a Counter metric.
|
||||
///
|
||||
/// @copydetails family_base_t<>::family_base_t()
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// \code
|
||||
/// auto registry = std::make_shared<Registry>();
|
||||
/// auto& counter_family = prometheus::Counter_family::build("some_name", "Additional description.", {{"key", "value"}}, *registry);
|
||||
///
|
||||
/// ...
|
||||
/// \endcode
|
||||
///
|
||||
/// \return An object of unspecified type T, i.e., an implementation detail
|
||||
/// except that it has the following members:
|
||||
///
|
||||
/// - Name(const std::string&) to set the metric name,
|
||||
/// - Help(const std::string&) to set an additional description.
|
||||
/// - Label(const std::map<std::string, std::string>&) to assign a set of
|
||||
/// key-value pairs (= labels) to the metric.
|
||||
///
|
||||
/// To finish the configuration of the Counter metric, register it with
|
||||
/// Register(Registry&).
|
||||
template <typename Registry>
|
||||
static CustomFamily& Build(Registry& registry, const std::string& name, const std::string& help, const Family::Labels& labels = Family::Labels()) {
|
||||
return registry.template Add<CustomFamily>(name, help, labels);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace prometheus
|
205
ext/prometheus-cpp-lite-1.0/core/include/prometheus/gateway.h
Normal file
205
ext/prometheus-cpp-lite-1.0/core/include/prometheus/gateway.h
Normal file
|
@ -0,0 +1,205 @@
|
|||
#pragma once
|
||||
|
||||
#include "prometheus/collectable.h"
|
||||
#include "prometheus/text_serializer.h"
|
||||
#include "prometheus/metric_family.h"
|
||||
|
||||
#include <jdl/httpclientlite.h>
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <future>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
class Gateway {
|
||||
using CollectableEntry = std::pair<std::weak_ptr<Collectable>, std::string>;
|
||||
|
||||
std::string job_uri_;
|
||||
std::string labels_;
|
||||
|
||||
std::mutex mutex_;
|
||||
|
||||
std::vector<CollectableEntry> collectables_;
|
||||
|
||||
enum class HttpMethod : uint8_t{
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
};
|
||||
|
||||
public:
|
||||
using Labels = std::map<std::string, std::string>;
|
||||
|
||||
Gateway(const std::string host, const std::string port,
|
||||
const std::string jobname, const Labels& labels = {})
|
||||
: job_uri_(host + ':' + port + std::string("/metrics/job/") + jobname)
|
||||
, labels_{}
|
||||
{
|
||||
std::stringstream label_strm;
|
||||
for (const auto& label : labels) {
|
||||
label_strm << "/" << label.first << "/" << label.second;
|
||||
}
|
||||
labels_ = label_strm.str();
|
||||
}
|
||||
|
||||
void RegisterCollectable(const std::weak_ptr<Collectable>& collectable,
|
||||
const Labels* labels = nullptr) {
|
||||
std::stringstream label_strm;
|
||||
|
||||
if (labels != nullptr) {
|
||||
for (const auto& label : *labels) {
|
||||
label_strm << "/" << label.first << "/" << label.second;
|
||||
}
|
||||
}
|
||||
|
||||
CleanupStalePointers(collectables_);
|
||||
collectables_.emplace_back(std::make_pair(collectable, label_strm.str()));
|
||||
}
|
||||
|
||||
|
||||
static const Labels GetInstanceLabel(const std::string& hostname) {
|
||||
if (hostname.empty()) {
|
||||
return Gateway::Labels{};
|
||||
}
|
||||
|
||||
return Gateway::Labels{{"instance", hostname}};
|
||||
}
|
||||
|
||||
|
||||
// Push metrics to the given pushgateway.
|
||||
int Push() {
|
||||
return push(HttpMethod::Post);
|
||||
}
|
||||
|
||||
|
||||
std::future<int> AsyncPush() {
|
||||
return async_push(HttpMethod::Post);
|
||||
}
|
||||
|
||||
|
||||
// PushAdd metrics to the given pushgateway.
|
||||
int PushAdd() {
|
||||
return push(HttpMethod::Put);
|
||||
}
|
||||
|
||||
|
||||
std::future<int> AsyncPushAdd() {
|
||||
return async_push(HttpMethod::Put);
|
||||
}
|
||||
|
||||
|
||||
// Delete metrics from the given pushgateway.
|
||||
int Delete() {
|
||||
return performHttpRequest(HttpMethod::Delete, job_uri_, {});
|
||||
}
|
||||
|
||||
// Delete metrics from the given pushgateway.
|
||||
std::future<int> AsyncDelete() {
|
||||
return std::async(std::launch::async, [&] { return Delete(); });
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
std::string getUri(const CollectableEntry& collectable) const {
|
||||
return (job_uri_ + labels_ + collectable.second);
|
||||
}
|
||||
|
||||
|
||||
int performHttpRequest(HttpMethod /*method*/, const std::string& uri_str, const std::string& body) {
|
||||
std::lock_guard<std::mutex> l(mutex_);
|
||||
|
||||
/* Stub function. The implementation will be later, after connecting the
|
||||
* additional library of HTTP requests. */
|
||||
|
||||
jdl::URI uri(uri_str);
|
||||
jdl::HTTPResponse response = jdl::HTTPClient::request(jdl::HTTPClient::m_post, uri, body);
|
||||
|
||||
return std::stoi(response.response);
|
||||
}
|
||||
|
||||
|
||||
int push(HttpMethod method) {
|
||||
const auto serializer = TextSerializer{};
|
||||
|
||||
for (const auto& wcollectable : collectables_) {
|
||||
auto collectable = wcollectable.first.lock();
|
||||
if (!collectable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto metrics = collectable->Collect();
|
||||
auto uri = getUri(wcollectable);
|
||||
|
||||
std::stringstream body;
|
||||
serializer.Serialize(body, metrics);
|
||||
std::string body_str = body.str();
|
||||
|
||||
auto status_code = performHttpRequest(method, uri, body_str);
|
||||
|
||||
if (status_code < 100 || status_code >= 400) {
|
||||
return status_code;
|
||||
}
|
||||
}
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
|
||||
std::future<int> async_push(HttpMethod method) {
|
||||
const auto serializer = TextSerializer{};
|
||||
std::vector<std::future<int>> futures;
|
||||
|
||||
for (const auto& wcollectable : collectables_) {
|
||||
auto collectable = wcollectable.first.lock();
|
||||
if (!collectable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto metrics = collectable->Collect();
|
||||
auto uri = getUri(wcollectable);
|
||||
|
||||
std::stringstream body;
|
||||
serializer.Serialize(body, metrics);
|
||||
auto body_ptr = std::make_shared<std::string>(body.str());
|
||||
|
||||
futures.emplace_back(std::async(std::launch::async, [method, &uri, &body_ptr, this] {
|
||||
return performHttpRequest(method, uri, *body_ptr);
|
||||
}));
|
||||
}
|
||||
|
||||
const auto reduceFutures = [](std::vector<std::future<int>> lfutures) {
|
||||
auto final_status_code = 200;
|
||||
|
||||
for (auto& future : lfutures) {
|
||||
auto status_code = future.get();
|
||||
|
||||
if (status_code < 100 || status_code >= 400) {
|
||||
final_status_code = status_code;
|
||||
}
|
||||
}
|
||||
|
||||
return final_status_code;
|
||||
};
|
||||
|
||||
return std::async(std::launch::async, reduceFutures, std::move(futures));
|
||||
}
|
||||
|
||||
|
||||
static void CleanupStalePointers(std::vector<CollectableEntry>& collectables) {
|
||||
collectables.erase(std::remove_if(std::begin(collectables), std::end(collectables),
|
||||
[](const CollectableEntry& candidate) {
|
||||
return candidate.first.expired();
|
||||
}),
|
||||
std::end(collectables));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace prometheus
|
128
ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h
Normal file
128
ext/prometheus-cpp-lite-1.0/core/include/prometheus/gauge.h
Normal file
|
@ -0,0 +1,128 @@
|
|||
#pragma once
|
||||
|
||||
#include "prometheus/atomic_floating.h"
|
||||
#include "prometheus/metric.h"
|
||||
#include "prometheus/family.h"
|
||||
|
||||
#include "prometheus/builder.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <ctime>
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
/// \brief A gauge metric to represent a value that can arbitrarily go up and
|
||||
/// down.
|
||||
///
|
||||
/// The class represents the metric type gauge:
|
||||
/// https://prometheus.io/docs/concepts/metric_types/#gauge
|
||||
///
|
||||
/// Gauges are typically used for measured values like temperatures or current
|
||||
/// memory usage, but also "counts" that can go up and down, like the number of
|
||||
/// running processes.
|
||||
///
|
||||
/// The class is thread-safe. No concurrent call to any API of this type causes
|
||||
/// a data race.
|
||||
template <typename Value_ = uint64_t>
|
||||
class Gauge : public Metric {
|
||||
|
||||
std::atomic<Value_> value { 0 };
|
||||
|
||||
public:
|
||||
|
||||
using Value = Value_;
|
||||
using Family = CustomFamily<Gauge<Value>>;
|
||||
|
||||
static const Metric::Type static_type = Metric::Type::Gauge;
|
||||
|
||||
|
||||
Gauge() : Metric (static_type) {} ///< \brief Create a gauge that starts at 0.
|
||||
Gauge(const Value value_) : Metric(static_type), value{ value_ } {} ///< \brief Create a gauge that starts at the given amount.
|
||||
|
||||
// original API
|
||||
|
||||
void Increment() { ++value; } ///< \brief Increment the gauge by 1.
|
||||
void Increment(const Value& val) { value += val; } ///< \brief Increment the gauge by the given amount.
|
||||
|
||||
void Decrement() { --value; } ///< \brief Decrement the gauge by 1.
|
||||
void Decrement(const Value& val) { value -= val; } ///< \brief Decrement the gauge by the given amount.
|
||||
|
||||
void SetToCurrentTime() { ///< \brief Set the gauge to the current unixtime in seconds.
|
||||
const time_t time = std::time(nullptr);
|
||||
value = static_cast<Value>(time);
|
||||
}
|
||||
void Set(const Value& val) { value = val; } ///< \brief Set the gauge to the given value.
|
||||
const Value Get() const { return value; } ///< \brief Get the current value of the gauge.
|
||||
|
||||
virtual ClientMetric Collect() const { ///< \brief Get the current value of the gauge. Collect is called by the Registry when collecting metrics.
|
||||
ClientMetric metric;
|
||||
metric.gauge.value = static_cast<double>(value);
|
||||
return metric;
|
||||
}
|
||||
|
||||
// new API
|
||||
|
||||
Gauge& operator ++() {
|
||||
++value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Gauge& operator++ (int) {
|
||||
++value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Gauge& operator --() {
|
||||
--value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Gauge& operator-- (int) {
|
||||
--value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Gauge& operator+=(const Value& val) {
|
||||
value += val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Gauge& operator-=(const Value& val) {
|
||||
value -= val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/// \brief Return a builder to configure and register a Gauge metric.
|
||||
///
|
||||
/// @copydetails Family<>::Family()
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// \code
|
||||
/// auto registry = std::make_shared<Registry>();
|
||||
/// auto& gauge_family = prometheus::BuildGauge()
|
||||
/// .Name("some_name")
|
||||
/// .Help("Additional description.")
|
||||
/// .Labels({{"key", "value"}})
|
||||
/// .Register(*registry);
|
||||
///
|
||||
/// ...
|
||||
/// \endcode
|
||||
///
|
||||
/// \return An object of unspecified type T, i.e., an implementation detail
|
||||
/// except that it has the following members:
|
||||
///
|
||||
/// - Name(const std::string&) to set the metric name,
|
||||
/// - Help(const std::string&) to set an additional description.
|
||||
/// - Label(const std::map<std::string, std::string>&) to assign a set of
|
||||
/// key-value pairs (= labels) to the metric.
|
||||
///
|
||||
/// To finish the configuration of the Gauge metric register it with
|
||||
/// Register(Registry&).
|
||||
using BuildGauge = Builder<Gauge<double>>;
|
||||
|
||||
|
||||
} // namespace prometheus
|
50
ext/prometheus-cpp-lite-1.0/core/include/prometheus/hash.h
Normal file
50
ext/prometheus-cpp-lite-1.0/core/include/prometheus/hash.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/// \brief Combine a hash value with nothing.
|
||||
/// It's the boundary condition of this serial functions.
|
||||
///
|
||||
/// \param seed Not effect.
|
||||
inline void hash_combine(std::size_t *) {}
|
||||
|
||||
/// \brief Combine the given hash value with another obeject.
|
||||
///
|
||||
/// \param seed The given hash value. It's a input/output parameter.
|
||||
/// \param value The object that will be combined with the given hash value.
|
||||
template <typename T>
|
||||
inline void hash_combine(std::size_t *seed, const T &value) {
|
||||
*seed ^= std::hash<T>{}(value) + 0x9e3779b9 + (*seed << 6) + (*seed >> 2);
|
||||
}
|
||||
|
||||
/// \brief Combine the given hash value with another objects. It's a recursion。
|
||||
///
|
||||
/// \param seed The give hash value. It's a input/output parameter.
|
||||
/// \param value The object that will be combined with the given hash value.
|
||||
/// \param args The objects that will be combined with the given hash value.
|
||||
template <typename T, typename... Types>
|
||||
inline void hash_combine(std::size_t *seed, const T &value,
|
||||
const Types &...args) {
|
||||
hash_combine(seed, value);
|
||||
hash_combine(seed, args...);
|
||||
}
|
||||
|
||||
/// \brief Compute a hash value of the given args.
|
||||
///
|
||||
/// \param args The arguments that will be computed hash value.
|
||||
/// \return The hash value of the given args.
|
||||
template <typename... Types>
|
||||
inline std::size_t hash_value(const Types &...args) {
|
||||
std::size_t seed = 0;
|
||||
hash_combine(&seed, args...);
|
||||
return seed;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace prometheus
|
154
ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h
Normal file
154
ext/prometheus-cpp-lite-1.0/core/include/prometheus/histogram.h
Normal file
|
@ -0,0 +1,154 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
#include "prometheus/metric.h"
|
||||
#include "prometheus/family.h"
|
||||
#include "prometheus/counter.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
/// \brief A histogram metric to represent aggregatable distributions of events.
|
||||
///
|
||||
/// This class represents the metric type histogram:
|
||||
/// https://prometheus.io/docs/concepts/metric_types/#histogram
|
||||
///
|
||||
/// A histogram tracks the number of observations and the sum of the observed
|
||||
/// values, allowing to calculate the average of the observed values.
|
||||
///
|
||||
/// At its core a histogram has a counter per bucket. The sum of observations
|
||||
/// also behaves like a counter as long as there are no negative observations.
|
||||
///
|
||||
/// See https://prometheus.io/docs/practices/histograms/ for detailed
|
||||
/// explanations of histogram usage and differences to summaries.
|
||||
///
|
||||
/// The class is thread-safe. No concurrent call to any API of this type causes
|
||||
/// a data race.
|
||||
template <typename Value_ = uint64_t>
|
||||
class Histogram : Metric {
|
||||
|
||||
using BucketBoundaries = std::vector<Value_>;
|
||||
|
||||
const BucketBoundaries bucket_boundaries_;
|
||||
std::vector<Counter<Value_>> bucket_counts_;
|
||||
Gauge<Value_> sum_;
|
||||
|
||||
public:
|
||||
using Value = Value_;
|
||||
using Family = CustomFamily<Histogram<Value>>;
|
||||
|
||||
static const Metric::Type static_type = Metric::Type::Histogram;
|
||||
|
||||
/// \brief Create a histogram with manually chosen buckets.
|
||||
///
|
||||
/// The BucketBoundaries are a list of monotonically increasing values
|
||||
/// representing the bucket boundaries. Each consecutive pair of values is
|
||||
/// interpreted as a half-open interval [b_n, b_n+1) which defines one bucket.
|
||||
///
|
||||
/// There is no limitation on how the buckets are divided, i.e, equal size,
|
||||
/// exponential etc..
|
||||
///
|
||||
/// The bucket boundaries cannot be changed once the histogram is created.
|
||||
Histogram (const BucketBoundaries& buckets)
|
||||
: Metric(static_type), bucket_boundaries_{ buckets }, bucket_counts_{ buckets.size() + 1 }, sum_{} {
|
||||
assert(std::is_sorted(std::begin(bucket_boundaries_),
|
||||
std::end(bucket_boundaries_)));
|
||||
}
|
||||
|
||||
/// \brief Observe the given amount.
|
||||
///
|
||||
/// The given amount selects the 'observed' bucket. The observed bucket is
|
||||
/// chosen for which the given amount falls into the half-open interval [b_n,
|
||||
/// b_n+1). The counter of the observed bucket is incremented. Also the total
|
||||
/// sum of all observations is incremented.
|
||||
void Observe(const Value value) {
|
||||
// TODO: determine bucket list size at which binary search would be faster
|
||||
const auto bucket_index = static_cast<std::size_t>(std::distance(
|
||||
bucket_boundaries_.begin(),
|
||||
std::find_if(
|
||||
std::begin(bucket_boundaries_), std::end(bucket_boundaries_),
|
||||
[value](const double boundary) { return boundary >= value; })));
|
||||
sum_.Increment(value);
|
||||
bucket_counts_[bucket_index].Increment();
|
||||
}
|
||||
|
||||
/// \brief Observe multiple data points.
|
||||
///
|
||||
/// Increments counters given a count for each bucket. (i.e. the caller of
|
||||
/// this function must have already sorted the values into buckets).
|
||||
/// Also increments the total sum of all observations by the given value.
|
||||
void ObserveMultiple(const std::vector<Value>& bucket_increments,
|
||||
const Value sum_of_values) {
|
||||
|
||||
if (bucket_increments.size() != bucket_counts_.size()) {
|
||||
throw std::length_error(
|
||||
"The size of bucket_increments was not equal to"
|
||||
"the number of buckets in the histogram.");
|
||||
}
|
||||
|
||||
sum_.Increment(sum_of_values);
|
||||
|
||||
for (std::size_t i{ 0 }; i < bucket_counts_.size(); ++i) {
|
||||
bucket_counts_[i].Increment(bucket_increments[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// \brief Get the current value of the counter.
|
||||
///
|
||||
/// Collect is called by the Registry when collecting metrics.
|
||||
virtual ClientMetric Collect() const {
|
||||
auto metric = ClientMetric{};
|
||||
|
||||
auto cumulative_count = 0ULL;
|
||||
metric.histogram.bucket.reserve(bucket_counts_.size());
|
||||
for (std::size_t i{0}; i < bucket_counts_.size(); ++i) {
|
||||
cumulative_count += static_cast<std::size_t>(bucket_counts_[i].Value());
|
||||
auto bucket = ClientMetric::Bucket{};
|
||||
bucket.cumulative_count = cumulative_count;
|
||||
bucket.upper_bound = (i == bucket_boundaries_.size()
|
||||
? std::numeric_limits<double>::infinity()
|
||||
: bucket_boundaries_[i]);
|
||||
metric.histogram.bucket.push_back(std::move(bucket));
|
||||
}
|
||||
metric.histogram.sample_count = cumulative_count;
|
||||
metric.histogram.sample_sum = sum_.Get();
|
||||
|
||||
return metric;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/// \brief Return a builder to configure and register a Histogram metric.
|
||||
///
|
||||
/// @copydetails Family<>::Family()
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// \code
|
||||
/// auto registry = std::make_shared<Registry>();
|
||||
/// auto& histogram_family = prometheus::BuildHistogram()
|
||||
/// .Name("some_name")
|
||||
/// .Help("Additional description.")
|
||||
/// .Labels({{"key", "value"}})
|
||||
/// .Register(*registry);
|
||||
///
|
||||
/// ...
|
||||
/// \endcode
|
||||
///
|
||||
/// \return An object of unspecified type T, i.e., an implementation detail
|
||||
/// except that it has the following members:
|
||||
///
|
||||
/// - Name(const std::string&) to set the metric name,
|
||||
/// - Help(const std::string&) to set an additional description.
|
||||
/// - Label(const std::map<std::string, std::string>&) to assign a set of
|
||||
/// key-value pairs (= labels) to the metric.
|
||||
///
|
||||
/// To finish the configuration of the Histogram metric register it with
|
||||
/// Register(Registry&).
|
||||
using BuildHistogram = Builder<Histogram<double>>;
|
||||
|
||||
|
||||
} // namespace prometheus
|
29
ext/prometheus-cpp-lite-1.0/core/include/prometheus/metric.h
Normal file
29
ext/prometheus-cpp-lite-1.0/core/include/prometheus/metric.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "client_metric.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
class Metric {
|
||||
|
||||
public:
|
||||
enum class Type {
|
||||
Counter,
|
||||
Gauge,
|
||||
Summary,
|
||||
Histogram,
|
||||
Untyped,
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
||||
Metric (Type type_) : type(type_) {}
|
||||
virtual ~Metric() = default;
|
||||
|
||||
virtual ClientMetric Collect() const = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "metric.h"
|
||||
#include "prometheus/client_metric.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
struct MetricFamily {
|
||||
Metric::Type type;
|
||||
std::string name;
|
||||
std::string help;
|
||||
std::vector<ClientMetric> metric;
|
||||
};
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include "registry.h"
|
||||
#include "text_serializer.h"
|
||||
|
||||
#include <jdl/httpclientlite.h>
|
||||
|
||||
|
||||
namespace prometheus {
|
||||
class PushToServer {
|
||||
std::chrono::seconds period { 1 };
|
||||
std::string uri { "" };
|
||||
std::thread worker_thread { &PushToServer::worker_function, this };
|
||||
std::shared_ptr<Registry> registry_ptr { nullptr };
|
||||
bool must_die { false };
|
||||
|
||||
void push_data() {
|
||||
if (registry_ptr) {
|
||||
if (!uri.empty()) {
|
||||
std::stringstream body_strm;
|
||||
TextSerializer::Serialize(body_strm, registry_ptr->Collect());
|
||||
|
||||
std::string body = body_strm.str();
|
||||
jdl::HTTPResponse response = jdl::HTTPClient::request(jdl::HTTPClient::m_post, jdl::URI(uri), body);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void worker_function() {
|
||||
// it need for fast shutdown this thread when SaveToFile destructor is called
|
||||
const uint64_t divider = 100;
|
||||
uint64_t fraction = divider;
|
||||
|
||||
for (;;) {
|
||||
std::chrono::milliseconds period_ms
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(period);
|
||||
std::this_thread::sleep_for( period_ms / divider );
|
||||
|
||||
if (must_die) {
|
||||
push_data();
|
||||
return;
|
||||
}
|
||||
|
||||
if (--fraction == 0) {
|
||||
fraction = divider;
|
||||
push_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
PushToServer() {
|
||||
jdl::init_socket();
|
||||
}
|
||||
|
||||
~PushToServer() {
|
||||
must_die = true;
|
||||
worker_thread.join();
|
||||
jdl::deinit_socket();
|
||||
}
|
||||
|
||||
PushToServer(std::shared_ptr<Registry>& registry_, const std::chrono::seconds& period_, const std::string& uri_) {
|
||||
set_registry(registry_);
|
||||
set_delay(period_);
|
||||
set_uri(uri_);
|
||||
}
|
||||
|
||||
void set_delay (const std::chrono::seconds& new_period) {
|
||||
period = new_period;
|
||||
}
|
||||
|
||||
|
||||
void set_uri (const std::string& uri_) {
|
||||
uri = std::move(uri_);
|
||||
}
|
||||
|
||||
void set_registry (std::shared_ptr<Registry>& new_registry_ptr) {
|
||||
registry_ptr = new_registry_ptr;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
123
ext/prometheus-cpp-lite-1.0/core/include/prometheus/registry.h
Normal file
123
ext/prometheus-cpp-lite-1.0/core/include/prometheus/registry.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "prometheus/collectable.h"
|
||||
#include "prometheus/family.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
/// \brief Manages the collection of a number of metrics.
|
||||
///
|
||||
/// The Registry is responsible to expose data to a class/method/function
|
||||
/// "bridge", which returns the metrics in a format Prometheus supports.
|
||||
///
|
||||
/// The key class is the Collectable. This has a method - called Collect() -
|
||||
/// that returns zero or more metrics and their samples. The metrics are
|
||||
/// represented by the class Family<>, which implements the Collectable
|
||||
/// interface. A new metric is registered with BuildCounter(), BuildGauge(),
|
||||
/// BuildHistogram() or BuildSummary().
|
||||
///
|
||||
/// The class is thread-safe. No concurrent call to any API of this type causes
|
||||
/// a data race.
|
||||
class Registry : public Collectable {
|
||||
|
||||
public:
|
||||
|
||||
/// \brief How to deal with repeatedly added family names for a type.
|
||||
///
|
||||
/// Adding a family with the same name but different types is always an error
|
||||
/// and will lead to an exception.
|
||||
enum class InsertBehavior {
|
||||
/// \brief If a family with the same name and labels already exists return
|
||||
/// the existing one. If no family with that name exists create it.
|
||||
/// Otherwise throw.
|
||||
Merge,
|
||||
/// \brief Throws if a family with the same name already exists.
|
||||
Throw,
|
||||
/// \brief Never merge and always create a new family. This violates the
|
||||
/// prometheus specification but was the default behavior in earlier
|
||||
/// versions
|
||||
NonStandardAppend,
|
||||
};
|
||||
|
||||
using FamilyPtr = std::unique_ptr<Family>;
|
||||
using Families = std::vector<FamilyPtr>;
|
||||
|
||||
const InsertBehavior insert_behavior;
|
||||
mutable std::mutex mutex;
|
||||
Families families;
|
||||
|
||||
/// \brief name Create a new registry.
|
||||
///
|
||||
/// \param insert_behavior How to handle families with the same name.
|
||||
Registry (InsertBehavior insert_behavior_ = InsertBehavior::Merge)
|
||||
: insert_behavior(insert_behavior_) {}
|
||||
|
||||
/// \brief Returns a list of metrics and their samples.
|
||||
///
|
||||
/// Every time the Registry is scraped it calls each of the metrics Collect
|
||||
/// function.
|
||||
///
|
||||
/// \return Zero or more metrics and their samples.
|
||||
virtual MetricFamilies Collect() const {
|
||||
|
||||
std::lock_guard<std::mutex> lock{ mutex };
|
||||
|
||||
MetricFamilies results;
|
||||
|
||||
for (const FamilyPtr& family_ptr : families) {
|
||||
MetricFamilies metrics = family_ptr->Collect();
|
||||
results.insert(results.end(), std::make_move_iterator(metrics.begin()), std::make_move_iterator(metrics.end()));
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
}
|
||||
|
||||
template <typename CustomFamily>
|
||||
CustomFamily& Add (const std::string& name, const std::string& help, const Family::Labels& labels) {
|
||||
|
||||
std::lock_guard<std::mutex> lock{ mutex };
|
||||
|
||||
bool found_one_but_not_merge = false;
|
||||
for (const FamilyPtr& family_ptr : families) {
|
||||
if (family_ptr->GetName() == name) {
|
||||
|
||||
if (family_ptr->type != CustomFamily::static_type) // found family with this name and with different type
|
||||
throw std::invalid_argument("Family name already exists with different type");
|
||||
|
||||
else { // found family with this name and the same type
|
||||
switch (insert_behavior) {
|
||||
case InsertBehavior::Throw:
|
||||
throw std::invalid_argument("Family name already exists");
|
||||
case InsertBehavior::Merge:
|
||||
if (family_ptr->GetConstantLabels() == labels)
|
||||
return dynamic_cast<CustomFamily&>(*family_ptr);
|
||||
else // this strange rule was in previos version prometheus cpp
|
||||
found_one_but_not_merge = true;
|
||||
case InsertBehavior::NonStandardAppend:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (found_one_but_not_merge) // this strange rule was in previos version prometheus cpp
|
||||
throw std::invalid_argument("Family name already exists with different labels");
|
||||
|
||||
std::unique_ptr<CustomFamily> new_family_ptr (new CustomFamily(name, help, labels));
|
||||
CustomFamily& new_family = *new_family_ptr;
|
||||
families.push_back(std::move(new_family_ptr));
|
||||
return new_family;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
#include "registry.h"
|
||||
#include "text_serializer.h"
|
||||
|
||||
namespace prometheus {
|
||||
class SaveToFile {
|
||||
std::chrono::seconds period { 1 };
|
||||
std::string filename;
|
||||
std::thread worker_thread { &SaveToFile::worker_function, this };
|
||||
std::shared_ptr<Registry> registry_ptr { nullptr };
|
||||
bool must_die { false };
|
||||
|
||||
void save_data() {
|
||||
if (registry_ptr) {
|
||||
std::fstream out_file_stream;
|
||||
out_file_stream.open(filename, std::fstream::out | std::fstream::binary);
|
||||
if (out_file_stream.is_open()) {
|
||||
TextSerializer::Serialize(out_file_stream, registry_ptr->Collect());
|
||||
out_file_stream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void worker_function() {
|
||||
// it need for fast shutdown this thread when SaveToFile destructor is called
|
||||
const uint64_t divider = 100;
|
||||
uint64_t fraction = divider;
|
||||
for (;;) {
|
||||
std::chrono::milliseconds period_ms
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(period);
|
||||
std::this_thread::sleep_for( period_ms / divider );
|
||||
if (must_die) {
|
||||
save_data();
|
||||
return;
|
||||
}
|
||||
if (--fraction == 0) {
|
||||
fraction = divider;
|
||||
save_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
SaveToFile() = default;
|
||||
|
||||
~SaveToFile() {
|
||||
must_die = true;
|
||||
worker_thread.join();
|
||||
}
|
||||
|
||||
SaveToFile(std::shared_ptr<Registry>& registry_, const std::chrono::seconds& period_, const std::string& filename_) {
|
||||
set_registry(registry_);
|
||||
set_delay(period_);
|
||||
set_out_file(filename_);
|
||||
}
|
||||
|
||||
void set_delay (const std::chrono::seconds& new_period) {
|
||||
period = new_period;
|
||||
}
|
||||
|
||||
|
||||
bool set_out_file (const std::string& filename_) {
|
||||
filename = filename_;
|
||||
std::fstream out_file_stream;
|
||||
out_file_stream.open(filename, std::fstream::out | std::fstream::binary);
|
||||
bool open_success = out_file_stream.is_open();
|
||||
out_file_stream.close();
|
||||
return open_success;
|
||||
}
|
||||
|
||||
void set_registry (std::shared_ptr<Registry>& new_registry_ptr) {
|
||||
registry_ptr = new_registry_ptr;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
154
ext/prometheus-cpp-lite-1.0/core/include/prometheus/summary.h
Normal file
154
ext/prometheus-cpp-lite-1.0/core/include/prometheus/summary.h
Normal file
|
@ -0,0 +1,154 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "prometheus/metric.h"
|
||||
#include "prometheus/family.h"
|
||||
|
||||
#include "prometheus/detail/ckms_quantiles.h"
|
||||
#include "prometheus/detail/time_window_quantiles.h"
|
||||
|
||||
#include "prometheus/builder.h"
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
/// \brief A summary metric samples observations over a sliding window of time.
|
||||
///
|
||||
/// This class represents the metric type summary:
|
||||
/// https://prometheus.io/docs/instrumenting/writing_clientlibs/#summary
|
||||
///
|
||||
/// A summary provides a total count of observations and a sum of all observed
|
||||
/// values. In contrast to a histogram metric it also calculates configurable
|
||||
/// Phi-quantiles over a sliding window of time.
|
||||
///
|
||||
/// The essential difference between summaries and histograms is that summaries
|
||||
/// calculate streaming Phi-quantiles on the client side and expose them
|
||||
/// directly, while histograms expose bucketed observation counts and the
|
||||
/// calculation of quantiles from the buckets of a histogram happens on the
|
||||
/// server side:
|
||||
/// https://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile.
|
||||
///
|
||||
/// Note that Phi designates the probability density function of the standard
|
||||
/// Gaussian distribution.
|
||||
///
|
||||
/// See https://prometheus.io/docs/practices/histograms/ for detailed
|
||||
/// explanations of Phi-quantiles, summary usage, and differences to histograms.
|
||||
///
|
||||
/// The class is thread-safe. No concurrent call to any API of this type causes
|
||||
/// a data race.
|
||||
class Summary : Metric {
|
||||
|
||||
public:
|
||||
|
||||
using Value = double;
|
||||
using Family = CustomFamily<Summary>;
|
||||
|
||||
static const Metric::Type static_type = Metric::Type::Summary;
|
||||
|
||||
using Quantiles = std::vector<detail::CKMSQuantiles::Quantile>;
|
||||
|
||||
const Quantiles quantiles_;
|
||||
mutable std::mutex mutex_;
|
||||
std::uint64_t count_;
|
||||
double sum_;
|
||||
detail::TimeWindowQuantiles quantile_values_;
|
||||
|
||||
public:
|
||||
|
||||
/// \brief Create a summary metric.
|
||||
///
|
||||
/// \param quantiles A list of 'targeted' Phi-quantiles. A targeted
|
||||
/// Phi-quantile is specified in the form of a Phi-quantile and tolerated
|
||||
/// error. For example a Quantile{0.5, 0.1} means that the median (= 50th
|
||||
/// percentile) should be returned with 10 percent error or a Quantile{0.2,
|
||||
/// 0.05} means the 20th percentile with 5 percent tolerated error. Note that
|
||||
/// percentiles and quantiles are the same concept, except percentiles are
|
||||
/// expressed as percentages. The Phi-quantile must be in the interval [0, 1].
|
||||
/// Note that a lower tolerated error for a Phi-quantile results in higher
|
||||
/// usage of resources (memory and cpu) to calculate the summary.
|
||||
///
|
||||
/// The Phi-quantiles are calculated over a sliding window of time. The
|
||||
/// sliding window of time is configured by max_age and age_buckets.
|
||||
///
|
||||
/// \param max_age Set the duration of the time window, i.e., how long
|
||||
/// observations are kept before they are discarded. The default value is 60
|
||||
/// seconds.
|
||||
///
|
||||
/// \param age_buckets Set the number of buckets of the time window. It
|
||||
/// determines the number of buckets used to exclude observations that are
|
||||
/// older than max_age from the summary, e.g., if max_age is 60 seconds and
|
||||
/// age_buckets is 5, buckets will be switched every 12 seconds. The value is
|
||||
/// a trade-off between resources (memory and cpu for maintaining the bucket)
|
||||
/// and how smooth the time window is moved. With only one age bucket it
|
||||
/// effectively results in a complete reset of the summary each time max_age
|
||||
/// has passed. The default value is 5.
|
||||
Summary(const Quantiles& quantiles, std::chrono::milliseconds max_age = std::chrono::seconds{ 60 }, int age_buckets = 5)
|
||||
: Metric(static_type), quantiles_{ quantiles }, count_{ 0 }, sum_{ 0 }, quantile_values_(quantiles_, max_age, age_buckets) {}
|
||||
|
||||
/// \brief Observe the given amount.
|
||||
void Observe(const double value) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
count_ += 1;
|
||||
sum_ += value;
|
||||
quantile_values_.insert(value);
|
||||
|
||||
}
|
||||
|
||||
/// \brief Get the current value of the summary.
|
||||
///
|
||||
/// Collect is called by the Registry when collecting metrics.
|
||||
virtual ClientMetric Collect() const {
|
||||
auto metric = ClientMetric{};
|
||||
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
metric.summary.quantile.reserve(quantiles_.size());
|
||||
for (const auto& quantile : quantiles_) {
|
||||
auto metricQuantile = ClientMetric::Quantile{};
|
||||
metricQuantile.quantile = quantile.quantile;
|
||||
metricQuantile.value = quantile_values_.get(quantile.quantile);
|
||||
metric.summary.quantile.push_back(std::move(metricQuantile));
|
||||
}
|
||||
metric.summary.sample_count = count_;
|
||||
metric.summary.sample_sum = sum_;
|
||||
|
||||
return metric;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/// \brief Return a builder to configure and register a Summary metric.
|
||||
///
|
||||
/// @copydetails Family<>::Family()
|
||||
///
|
||||
/// Example usage:
|
||||
///
|
||||
/// \code
|
||||
/// auto registry = std::make_shared<Registry>();
|
||||
/// auto& summary_family = prometheus::BuildSummary()
|
||||
/// .Name("some_name")
|
||||
/// .Help("Additional description.")
|
||||
/// .Labels({{"key", "value"}})
|
||||
/// .Register(*registry);
|
||||
///
|
||||
/// ...
|
||||
/// \endcode
|
||||
///
|
||||
/// \return An object of unspecified type T, i.e., an implementation detail
|
||||
/// except that it has the following members:
|
||||
///
|
||||
/// - Name(const std::string&) to set the metric name,
|
||||
/// - Help(const std::string&) to set an additional description.
|
||||
/// - Label(const std::map<std::string, std::string>&) to assign a set of
|
||||
/// key-value pairs (= labels) to the metric.
|
||||
///
|
||||
/// To finish the configuration of the Summary metric register it with
|
||||
/// Register(Registry&).
|
||||
using BuildSummary = Builder<Summary>;
|
||||
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,211 @@
|
|||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <math.h>
|
||||
#include <ostream>
|
||||
|
||||
#include "prometheus/metric_family.h"
|
||||
|
||||
#if __cpp_lib_to_chars >= 201611L
|
||||
#include <charconv>
|
||||
#endif
|
||||
|
||||
namespace prometheus {
|
||||
|
||||
class TextSerializer {
|
||||
|
||||
// Write a double as a string, with proper formatting for infinity and NaN
|
||||
static void WriteValue (std::ostream& out, double value) {
|
||||
if (std::isnan(value))
|
||||
out << "Nan";
|
||||
else if (std::isinf(value))
|
||||
out << (value < 0 ? "-Inf" : "+Inf");
|
||||
|
||||
else {
|
||||
|
||||
std::array<char, 128> buffer;
|
||||
|
||||
#if __cpp_lib_to_chars >= 201611L
|
||||
auto [ptr, ec] = std::to_chars(buffer.data(), buffer.data() + buffer.size(), value);
|
||||
if (ec != std::errc()) {
|
||||
throw std::runtime_error("Could not convert double to string: " +
|
||||
std::make_error_code(ec).message());
|
||||
}
|
||||
out.write(buffer.data(), ptr - buffer.data());
|
||||
#else
|
||||
int wouldHaveWritten = std::snprintf(buffer.data(), buffer.size(), "%.*g", std::numeric_limits<double>::max_digits10 - 1, value);
|
||||
if (wouldHaveWritten <= 0 || static_cast<std::size_t>(wouldHaveWritten) >= buffer.size()) {
|
||||
throw std::runtime_error("Could not convert double to string");
|
||||
}
|
||||
out.write(buffer.data(), wouldHaveWritten);
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteValue(std::ostream& out, const std::string& value) {
|
||||
for (auto c : value) {
|
||||
switch (c) {
|
||||
case '\n': out << '\\' << 'n'; break;
|
||||
case '\\': out << '\\' << c; break;
|
||||
case '"': out << '\\' << c; break;
|
||||
default: out << c; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write a line header: metric name and labels
|
||||
template <typename T = std::string>
|
||||
static void WriteHead(
|
||||
std::ostream& out,
|
||||
const MetricFamily& family,
|
||||
const ClientMetric& metric,
|
||||
const std::string& suffix = "",
|
||||
const std::string& extraLabelName = "",
|
||||
const T& extraLabelValue = T()) {
|
||||
|
||||
out << family.name << suffix;
|
||||
|
||||
if (!metric.label.empty() || !extraLabelName.empty()) {
|
||||
|
||||
out << "{";
|
||||
const char* prefix = "";
|
||||
|
||||
for (auto& lp : metric.label) {
|
||||
out << prefix << lp.name << "=\"";
|
||||
WriteValue(out, lp.value);
|
||||
out << "\"";
|
||||
prefix = ",";
|
||||
}
|
||||
if (!extraLabelName.empty()) {
|
||||
out << prefix << extraLabelName << "=\"";
|
||||
WriteValue(out, extraLabelValue);
|
||||
out << "\"";
|
||||
}
|
||||
out << "}";
|
||||
}
|
||||
out << " ";
|
||||
}
|
||||
|
||||
// Write a line trailer: timestamp
|
||||
static void WriteTail(std::ostream& out, const ClientMetric& metric) {
|
||||
if (metric.timestamp_ms != 0) {
|
||||
out << " " << metric.timestamp_ms;
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
|
||||
static void SerializeCounter(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
|
||||
WriteHead(out, family, metric);
|
||||
WriteValue(out, metric.counter.value);
|
||||
WriteTail(out, metric);
|
||||
}
|
||||
|
||||
static void SerializeGauge(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
|
||||
WriteHead(out, family, metric);
|
||||
WriteValue(out, metric.gauge.value);
|
||||
WriteTail(out, metric);
|
||||
}
|
||||
|
||||
static void SerializeSummary(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
|
||||
auto& sum = metric.summary;
|
||||
WriteHead(out, family, metric, "_count");
|
||||
out << sum.sample_count;
|
||||
WriteTail(out, metric);
|
||||
|
||||
WriteHead(out, family, metric, "_sum");
|
||||
WriteValue(out, sum.sample_sum);
|
||||
WriteTail(out, metric);
|
||||
|
||||
for (auto& q : sum.quantile) {
|
||||
WriteHead(out, family, metric, "", "quantile", q.quantile);
|
||||
WriteValue(out, q.value);
|
||||
WriteTail(out, metric);
|
||||
}
|
||||
}
|
||||
|
||||
static void SerializeUntyped(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
|
||||
WriteHead(out, family, metric);
|
||||
WriteValue(out, metric.untyped.value);
|
||||
WriteTail(out, metric);
|
||||
}
|
||||
|
||||
static void SerializeHistogram(std::ostream& out, const MetricFamily& family, const ClientMetric& metric) {
|
||||
auto& hist = metric.histogram;
|
||||
WriteHead(out, family, metric, "_count");
|
||||
out << hist.sample_count;
|
||||
WriteTail(out, metric);
|
||||
|
||||
WriteHead(out, family, metric, "_sum");
|
||||
WriteValue(out, hist.sample_sum);
|
||||
WriteTail(out, metric);
|
||||
|
||||
double last = -std::numeric_limits<double>::infinity();
|
||||
for (auto& b : hist.bucket) {
|
||||
WriteHead(out, family, metric, "_bucket", "le", b.upper_bound);
|
||||
last = b.upper_bound;
|
||||
out << b.cumulative_count;
|
||||
WriteTail(out, metric);
|
||||
}
|
||||
|
||||
if (last != std::numeric_limits<double>::infinity()) {
|
||||
WriteHead(out, family, metric, "_bucket", "le", "+Inf");
|
||||
out << hist.sample_count;
|
||||
WriteTail(out, metric);
|
||||
}
|
||||
}
|
||||
|
||||
static void SerializeFamily(std::ostream& out, const MetricFamily& family) {
|
||||
if (!family.help.empty()) {
|
||||
out << "# HELP " << family.name << " " << family.help << "\n";
|
||||
}
|
||||
switch (family.type) {
|
||||
case Metric::Type::Counter:
|
||||
out << "# TYPE " << family.name << " counter\n";
|
||||
for (auto& metric : family.metric) {
|
||||
SerializeCounter(out, family, metric);
|
||||
}
|
||||
break;
|
||||
case Metric::Type::Gauge:
|
||||
out << "# TYPE " << family.name << " gauge\n";
|
||||
for (auto& metric : family.metric) {
|
||||
SerializeGauge(out, family, metric);
|
||||
}
|
||||
break;
|
||||
case Metric::Type::Summary:
|
||||
out << "# TYPE " << family.name << " summary\n";
|
||||
for (auto& metric : family.metric) {
|
||||
SerializeSummary(out, family, metric);
|
||||
}
|
||||
break;
|
||||
case Metric::Type::Untyped:
|
||||
out << "# TYPE " << family.name << " untyped\n";
|
||||
for (auto& metric : family.metric) {
|
||||
SerializeUntyped(out, family, metric);
|
||||
}
|
||||
break;
|
||||
case Metric::Type::Histogram:
|
||||
out << "# TYPE " << family.name << " histogram\n";
|
||||
for (auto& metric : family.metric) {
|
||||
SerializeHistogram(out, family, metric);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
static void Serialize (std::ostream& out, const std::vector<MetricFamily>& metrics) {
|
||||
std::locale saved_locale = out.getloc();
|
||||
out.imbue(std::locale::classic());
|
||||
for (auto& family : metrics) {
|
||||
SerializeFamily(out, family);
|
||||
}
|
||||
out.imbue(saved_locale);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace prometheus
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "prometheus/detail/ckms_quantiles.h"
|
||||
|
||||
namespace prometheus {
|
||||
namespace detail {
|
||||
|
||||
class TimeWindowQuantiles {
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
public:
|
||||
TimeWindowQuantiles(const std::vector<CKMSQuantiles::Quantile>& quantiles,
|
||||
const Clock::duration max_age, const int age_buckets)
|
||||
: quantiles_(quantiles),
|
||||
ckms_quantiles_(age_buckets, CKMSQuantiles(quantiles_)),
|
||||
current_bucket_(0),
|
||||
last_rotation_(Clock::now()),
|
||||
rotation_interval_(max_age / age_buckets) {}
|
||||
|
||||
double get(double q) const {
|
||||
CKMSQuantiles& current_bucket = rotate();
|
||||
return current_bucket.get(q);
|
||||
}
|
||||
|
||||
void insert(double value) {
|
||||
rotate();
|
||||
for (auto& bucket : ckms_quantiles_) {
|
||||
bucket.insert(value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CKMSQuantiles& rotate() const {
|
||||
auto delta = Clock::now() - last_rotation_;
|
||||
while (delta > rotation_interval_) {
|
||||
ckms_quantiles_[current_bucket_].reset();
|
||||
|
||||
if (++current_bucket_ >= ckms_quantiles_.size()) {
|
||||
current_bucket_ = 0;
|
||||
}
|
||||
|
||||
delta -= rotation_interval_;
|
||||
last_rotation_ += rotation_interval_;
|
||||
}
|
||||
return ckms_quantiles_[current_bucket_];
|
||||
}
|
||||
|
||||
const std::vector<CKMSQuantiles::Quantile>& quantiles_;
|
||||
mutable std::vector<CKMSQuantiles> ckms_quantiles_;
|
||||
mutable std::size_t current_bucket_;
|
||||
|
||||
mutable Clock::time_point last_rotation_;
|
||||
const Clock::duration rotation_interval_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace prometheus
|
Loading…
Add table
Add a link
Reference in a new issue