1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
#ifndef _METRICS_H
#define _METRICS_H 1
// A simple global class to keep track of metrics export in Prometheus format.
// It would be better to use a more full-featured Prometheus client library for this,
// but it would introduce a dependency that is not commonly packaged in distributions,
// which makes it quite unwieldy. Thus, we'll package our own for the time being.
#include <atomic>
#include <chrono>
#include <deque>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
class Histogram;
class Summary;
// Prometheus recommends the use of timestamps instead of “time since event”,
// so you can use this to get the number of seconds since the epoch.
// Note that this will be wrong if your clock changes, so for non-metric use,
// you should use std::chrono::steady_clock instead.
double get_timestamp_for_metrics();
class Metrics {
public:
enum Type {
TYPE_COUNTER,
TYPE_GAUGE,
TYPE_HISTOGRAM, // Internal use only.
TYPE_SUMMARY, // Internal use only.
};
enum Laziness {
PRINT_ALWAYS,
PRINT_WHEN_NONEMPTY,
};
void set_prefix(const std::string &prefix) // Not thread-safe; must be set before HTTPD starts up.
{
this->prefix = prefix;
}
void add(const std::string &name, std::atomic<int64_t> *location, Type type = TYPE_COUNTER)
{
add(name, {}, location, type);
}
void add(const std::string &name, std::atomic<double> *location, Type type = TYPE_COUNTER)
{
add(name, {}, location, type);
}
void add(const std::string &name, Histogram *location)
{
add(name, {}, location);
}
void add(const std::string &name, Summary *location)
{
add(name, {}, location);
}
void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, std::atomic<int64_t> *location, Type type = TYPE_COUNTER);
void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, std::atomic<double> *location, Type type = TYPE_COUNTER);
void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, Histogram *location, Laziness laziness = PRINT_ALWAYS);
void add(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels, Summary *location, Laziness laziness = PRINT_ALWAYS);
void remove(const std::string &name)
{
remove(name, {});
}
void remove(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
void remove_if_exists(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
std::string serialize() const;
private:
static std::string serialize_name(const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
static std::string serialize_labels(const std::vector<std::pair<std::string, std::string>> &labels);
enum DataType {
DATA_TYPE_INT64,
DATA_TYPE_DOUBLE,
DATA_TYPE_HISTOGRAM,
DATA_TYPE_SUMMARY,
};
struct MetricKey {
MetricKey(const std::string &name, const std::vector<std::pair<std::string, std::string>> labels)
: name(name), labels(labels), serialized_labels(serialize_labels(labels))
{
}
bool operator< (const MetricKey &other) const
{
if (name != other.name)
return name < other.name;
return serialized_labels < other.serialized_labels;
}
const std::string name;
const std::vector<std::pair<std::string, std::string>> labels;
const std::string serialized_labels;
};
struct Metric {
DataType data_type;
Laziness laziness; // Only for TYPE_HISTOGRAM.
union {
std::atomic<int64_t> *location_int64;
std::atomic<double> *location_double;
Histogram *location_histogram;
Summary *location_summary;
};
};
mutable std::mutex mu;
std::map<std::string, Type> types; // Ordered the same as metrics.
std::map<MetricKey, Metric> metrics;
static std::string prefix;
friend class Histogram;
friend class Summary;
};
class Histogram {
public:
void init(const std::vector<double> &bucket_vals);
void init_uniform(size_t num_buckets); // Sets up buckets 0..(N-1).
void init_geometric(double min, double max, size_t num_buckets);
void count_event(double val);
std::string serialize(Metrics::Laziness laziness, const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels) const;
private:
// Bucket <i> counts number of events where val[i - 1] < x <= val[i].
// The end histogram ends up being made into a cumulative one,
// but that's not how we store it here.
struct Bucket {
double val;
std::atomic<int64_t> count{0};
};
std::unique_ptr<Bucket[]> buckets;
size_t num_buckets;
std::atomic<double> sum{0.0};
std::atomic<int64_t> count_after_last_bucket{0};
};
// This is a pretty dumb streaming quantile class, but it's exact, and we don't have
// too many values (typically one per frame, and one-minute interval), so we don't
// need anything fancy.
class Summary {
public:
void init(const std::vector<double> &quantiles, double window_seconds);
void count_event(double val);
std::string serialize(Metrics::Laziness laziness, const std::string &name, const std::vector<std::pair<std::string, std::string>> &labels);
private:
std::vector<double> quantiles;
std::chrono::duration<double> window;
mutable std::mutex mu;
std::deque<std::pair<std::chrono::steady_clock::time_point, double>> values;
std::atomic<double> sum{0.0};
std::atomic<int64_t> count{0};
};
extern Metrics global_metrics;
#endif // !defined(_METRICS_H)
|