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