X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=shared%2Fmetrics.h;fp=shared%2Fmetrics.h;h=e2e1e74f5ac9bb818f5bbdb8e6798ec27cadf869;hb=131a051c4cd3719a9be415386fdf0f4e15da7c66;hp=0000000000000000000000000000000000000000;hpb=7188e3e948c60f78f5e2cd8756337f716de06d99;p=nageru diff --git a/shared/metrics.h b/shared/metrics.h new file mode 100644 index 0000000..e2e1e74 --- /dev/null +++ b/shared/metrics.h @@ -0,0 +1,164 @@ +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 add(const std::string &name, std::atomic *location, Type type = TYPE_COUNTER) + { + add(name, {}, location, type); + } + + void add(const std::string &name, std::atomic *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> &labels, std::atomic *location, Type type = TYPE_COUNTER); + void add(const std::string &name, const std::vector> &labels, std::atomic *location, Type type = TYPE_COUNTER); + void add(const std::string &name, const std::vector> &labels, Histogram *location, Laziness laziness = PRINT_ALWAYS); + void add(const std::string &name, const std::vector> &labels, Summary *location, Laziness laziness = PRINT_ALWAYS); + + void remove(const std::string &name) + { + remove(name, {}); + } + + void remove(const std::string &name, const std::vector> &labels); + + std::string serialize() const; + +private: + static std::string serialize_name(const std::string &name, const std::vector> &labels); + static std::string serialize_labels(const std::vector> &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> 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> labels; + const std::string serialized_labels; + }; + struct Metric { + DataType data_type; + Laziness laziness; // Only for TYPE_HISTOGRAM. + union { + std::atomic *location_int64; + std::atomic *location_double; + Histogram *location_histogram; + Summary *location_summary; + }; + }; + + mutable std::mutex mu; + std::map types; // Ordered the same as metrics. + std::map metrics; + + friend class Histogram; + friend class Summary; +}; + +class Histogram { +public: + void init(const std::vector &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> &labels) const; + +private: + // Bucket 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 count{0}; + }; + std::unique_ptr buckets; + size_t num_buckets; + std::atomic sum{0.0}; + std::atomic 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 &quantiles, double window_seconds); + void count_event(double val); + std::string serialize(Metrics::Laziness laziness, const std::string &name, const std::vector> &labels); + +private: + std::vector quantiles; + std::chrono::duration window; + + mutable std::mutex mu; + std::deque> values; + std::atomic sum{0.0}; + std::atomic count{0}; +}; + +extern Metrics global_metrics; + +#endif // !defined(_METRICS_H)