]> git.sesse.net Git - nageru/blob - metrics.cpp
Fix a crash on getting metrics after switching file.
[nageru] / metrics.cpp
1 #include "metrics.h"
2
3 #include <assert.h>
4 #include <math.h>
5
6 #include <algorithm>
7 #include <locale>
8 #include <sstream>
9
10 using namespace std;
11
12 Metrics global_metrics;
13
14 namespace {
15
16 string serialize_name(const string &name, const vector<pair<string, string>> &labels)
17 {
18         if (labels.empty()) {
19                 return "nageru_" + name;
20         }
21
22         string label_str;
23         for (const pair<string, string> &label : labels) {
24                 if (!label_str.empty()) {
25                         label_str += ",";
26                 }
27                 label_str += label.first + "=\"" + label.second + "\"";
28         }
29         return "nageru_" + name + "{" + label_str + "}";
30 }
31
32 }  // namespace
33
34 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<int64_t> *location, Metrics::Type type)
35 {
36         Metric metric;
37         metric.data_type = DATA_TYPE_INT64;
38         metric.name = name;
39         metric.labels = labels;
40         metric.location_int64 = location;
41
42         lock_guard<mutex> lock(mu);
43         metrics.push_back(metric);
44         assert(types.count(name) == 0 || types[name] == type);
45         types[name] = type;
46 }
47
48 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, atomic<double> *location, Metrics::Type type)
49 {
50         Metric metric;
51         metric.data_type = DATA_TYPE_DOUBLE;
52         metric.name = name;
53         metric.labels = labels;
54         metric.location_double = location;
55
56         lock_guard<mutex> lock(mu);
57         metrics.push_back(metric);
58         assert(types.count(name) == 0 || types[name] == type);
59         types[name] = type;
60 }
61
62 void Metrics::add(const string &name, const vector<pair<string, string>> &labels, Histogram *location)
63 {
64         Metric metric;
65         metric.data_type = DATA_TYPE_HISTOGRAM;
66         metric.name = name;
67         metric.labels = labels;
68         metric.location_histogram = location;
69
70         lock_guard<mutex> lock(mu);
71         metrics.push_back(metric);
72         assert(types.count(name) == 0 || types[name] == TYPE_HISTOGRAM);
73         types[name] = TYPE_HISTOGRAM;
74 }
75
76 string Metrics::serialize() const
77 {
78         stringstream ss;
79         ss.imbue(locale("C"));
80         ss.precision(20);
81
82         lock_guard<mutex> lock(mu);
83         for (const auto &name_and_type : types) {
84                 if (name_and_type.second == TYPE_GAUGE) {
85                         ss << "# TYPE nageru_" << name_and_type.first << " gauge\n";
86                 } else if (name_and_type.second == TYPE_HISTOGRAM) {
87                         ss << "# TYPE nageru_" << name_and_type.first << " histogram\n";
88                 }
89         }
90         for (const Metric &metric : metrics) {
91                 string name = serialize_name(metric.name, metric.labels);
92
93                 if (metric.data_type == DATA_TYPE_INT64) {
94                         ss << name << " " << metric.location_int64->load() << "\n";
95                 } else if (metric.data_type == DATA_TYPE_DOUBLE) {
96                         ss << name << " " << metric.location_double->load() << "\n";
97                 } else {
98                         ss << metric.location_histogram->serialize(metric.name, metric.labels);
99                 }
100         }
101
102         return ss.str();
103 }
104
105 void Histogram::init(const vector<double> &bucket_vals)
106 {
107         this->num_buckets = bucket_vals.size();
108         buckets.reset(new Bucket[num_buckets]);
109         for (size_t i = 0; i < num_buckets; ++i) {
110                 buckets[i].val = bucket_vals[i];
111         }
112 }
113
114 void Histogram::init_uniform(size_t num_buckets)
115 {
116         this->num_buckets = num_buckets;
117         buckets.reset(new Bucket[num_buckets]);
118         for (size_t i = 0; i < num_buckets; ++i) {
119                 buckets[i].val = i;
120         }
121 }
122
123 void Histogram::init_geometric(double min, double max, size_t num_buckets)
124 {
125         this->num_buckets = num_buckets;
126         buckets.reset(new Bucket[num_buckets]);
127         for (size_t i = 0; i < num_buckets; ++i) {
128                 buckets[i].val = min * pow(max / min, double(i) / (num_buckets - 1));
129         }
130 }
131
132 void Histogram::count_event(double val)
133 {
134         Bucket ref_bucket;
135         ref_bucket.val = val;
136         auto it = lower_bound(buckets.get(), buckets.get() + num_buckets, ref_bucket,
137                 [](const Bucket &a, const Bucket &b) { return a.val < b.val; });
138         if (it == buckets.get() + num_buckets) {
139                 ++count_after_last_bucket;
140         } else {
141                 ++it->count;
142         }
143         // Non-atomic add, but that's fine, since there are no concurrent writers.
144         sum = sum + val;
145 }
146
147 string Histogram::serialize(const string &name, const vector<pair<string, string>> &labels) const
148 {
149         stringstream ss;
150         ss.imbue(locale("C"));
151         ss.precision(20);
152
153         int64_t count = 0;
154         for (size_t bucket_idx = 0; bucket_idx < num_buckets; ++bucket_idx) {
155                 stringstream le_ss;
156                 le_ss.imbue(locale("C"));
157                 le_ss.precision(20);
158                 le_ss << buckets[bucket_idx].val;
159                 vector<pair<string, string>> bucket_labels = labels;
160                 bucket_labels.emplace_back("le", le_ss.str());
161
162                 count += buckets[bucket_idx].count.load();
163                 ss << serialize_name(name + "_bucket", bucket_labels) << " " << count << "\n";
164         }
165
166         count += count_after_last_bucket.load();
167
168         ss << serialize_name(name + "_sum", labels) << " " << sum.load() << "\n";
169         ss << serialize_name(name + "_count", labels) << " " << count << "\n";
170
171         return ss.str();
172 }