File: metrics.h

package info (click to toggle)
nageru 2.3.2-1
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 3,120 kB
  • sloc: cpp: 39,131; perl: 94; sh: 18; makefile: 4
file content (172 lines) | stat: -rw-r--r-- 5,496 bytes parent folder | download | duplicates (4)
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)