1 /*
2 * Copyright (c) 2024 Mustafa Abdullah Kus, Sparse Technology
3 * Copyright (c) 2024 Nordic Semiconductor
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/net/prometheus/formatter.h>
9 #include <zephyr/net/prometheus/collector.h>
10 #include <zephyr/net/prometheus/metric.h>
11 #include <zephyr/net/prometheus/histogram.h>
12 #include <zephyr/net/prometheus/summary.h>
13 #include <zephyr/net/prometheus/gauge.h>
14 #include <zephyr/net/prometheus/counter.h>
15
16 #include <stdarg.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <stddef.h>
20 #include <errno.h>
21
22 #include <zephyr/kernel.h>
23
24 #include <zephyr/logging/log.h>
25 LOG_MODULE_REGISTER(pm_formatter, CONFIG_PROMETHEUS_LOG_LEVEL);
26
write_metric_to_buffer(char * buffer,size_t buffer_size,const char * format,...)27 static int write_metric_to_buffer(char *buffer, size_t buffer_size, const char *format, ...)
28 {
29 /* helper function to write formatted metric to buffer */
30 va_list args;
31 size_t len = strlen(buffer);
32
33 if (len >= buffer_size) {
34 return -ENOMEM;
35 }
36 buffer += len;
37 buffer_size -= len;
38
39 va_start(args, format);
40 len = vsnprintf(buffer, buffer_size, format, args);
41 va_end(args);
42 if (len >= buffer_size) {
43 return -ENOMEM;
44 }
45
46 return 0;
47 }
48
prometheus_format_one_metric(struct prometheus_metric * metric,char * buffer,size_t buffer_size,int * written)49 int prometheus_format_one_metric(struct prometheus_metric *metric, char *buffer,
50 size_t buffer_size, int *written)
51 {
52 int ret = 0;
53
54 /* write HELP line if available */
55 if (metric->description[0] != '\0') {
56 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
57 "# HELP %s %s\n", metric->name,
58 metric->description);
59 if (ret < 0) {
60 LOG_ERR("Error writing to buffer");
61 goto out;
62 }
63 }
64
65 /* write TYPE line */
66 switch (metric->type) {
67 case PROMETHEUS_COUNTER:
68 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
69 "# TYPE %s counter\n", metric->name);
70 if (ret < 0) {
71 LOG_ERR("Error writing counter");
72 goto out;
73 }
74
75 break;
76
77 case PROMETHEUS_GAUGE:
78 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
79 "# TYPE %s gauge\n", metric->name);
80 if (ret < 0) {
81 LOG_ERR("Error writing gauge");
82 goto out;
83 }
84
85 break;
86
87 case PROMETHEUS_HISTOGRAM:
88 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
89 "# TYPE %s histogram\n", metric->name);
90 if (ret < 0) {
91 LOG_ERR("Error writing histogram");
92 goto out;
93 }
94
95 break;
96
97 case PROMETHEUS_SUMMARY:
98 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
99 "# TYPE %s summary\n", metric->name);
100 if (ret < 0) {
101 LOG_ERR("Error writing summary");
102 goto out;
103 }
104
105 break;
106
107 default:
108 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
109 "# TYPE %s untyped\n", metric->name);
110 if (ret < 0) {
111 LOG_ERR("Error writing untyped");
112 goto out;
113 }
114
115 break;
116 }
117
118 /* write metric-specific fields */
119 switch (metric->type) {
120 case PROMETHEUS_COUNTER: {
121 const struct prometheus_counter *counter =
122 CONTAINER_OF(metric, struct prometheus_counter, base);
123
124 LOG_DBG("counter->value: %llu", counter->value);
125
126 for (int i = 0; i < metric->num_labels; ++i) {
127 ret = write_metric_to_buffer(
128 buffer + *written, buffer_size - *written,
129 "%s{%s=\"%s\"} %llu\n", metric->name, metric->labels[i].key,
130 metric->labels[i].value, counter->value);
131 if (ret < 0) {
132 LOG_ERR("Error writing counter");
133 goto out;
134 }
135 }
136
137 break;
138 }
139
140 case PROMETHEUS_GAUGE: {
141 const struct prometheus_gauge *gauge =
142 CONTAINER_OF(metric, struct prometheus_gauge, base);
143
144 LOG_DBG("gauge->value: %f", gauge->value);
145
146 for (int i = 0; i < metric->num_labels; ++i) {
147 ret = write_metric_to_buffer(
148 buffer + *written, buffer_size - *written,
149 "%s{%s=\"%s\"} %f\n", metric->name, metric->labels[i].key,
150 metric->labels[i].value, gauge->value);
151 if (ret < 0) {
152 LOG_ERR("Error writing gauge");
153 goto out;
154 }
155 }
156
157 break;
158 }
159
160 case PROMETHEUS_HISTOGRAM: {
161 const struct prometheus_histogram *histogram =
162 CONTAINER_OF(metric, struct prometheus_histogram, base);
163
164 LOG_DBG("histogram->count: %lu", histogram->count);
165
166 for (int i = 0; i < histogram->num_buckets; ++i) {
167 ret = write_metric_to_buffer(
168 buffer + *written, buffer_size - *written,
169 "%s_bucket{le=\"%f\"} %lu\n", metric->name,
170 histogram->buckets[i].upper_bound,
171 histogram->buckets[i].count);
172 if (ret < 0) {
173 LOG_ERR("Error writing histogram");
174 goto out;
175 }
176 }
177
178 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
179 "%s_sum %f\n", metric->name, histogram->sum);
180 if (ret < 0) {
181 LOG_ERR("Error writing histogram");
182 goto out;
183 }
184
185 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
186 "%s_count %lu\n", metric->name,
187 histogram->count);
188 if (ret < 0) {
189 LOG_ERR("Error writing histogram");
190 goto out;
191 }
192
193 break;
194 }
195
196 case PROMETHEUS_SUMMARY: {
197 const struct prometheus_summary *summary =
198 CONTAINER_OF(metric, struct prometheus_summary, base);
199
200 LOG_DBG("summary->count: %lu", summary->count);
201
202 for (int i = 0; i < summary->num_quantiles; ++i) {
203 ret = write_metric_to_buffer(
204 buffer + *written, buffer_size - *written,
205 "%s{%s=\"%f\"} %f\n", metric->name, "quantile",
206 summary->quantiles[i].quantile,
207 summary->quantiles[i].value);
208 if (ret < 0) {
209 LOG_ERR("Error writing summary");
210 goto out;
211 }
212 }
213
214 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
215 "%s_sum %f\n", metric->name, summary->sum);
216 if (ret < 0) {
217 LOG_ERR("Error writing summary");
218 goto out;
219 }
220
221 ret = write_metric_to_buffer(buffer + *written, buffer_size - *written,
222 "%s_count %lu\n", metric->name,
223 summary->count);
224 if (ret < 0) {
225 LOG_ERR("Error writing summary");
226 goto out;
227 }
228
229 break;
230 }
231
232 default:
233 /* should not happen */
234 LOG_ERR("Unsupported metric type %d", metric->type);
235 ret = -EINVAL;
236 goto out;
237 }
238
239 out:
240 return ret;
241 }
242
prometheus_format_exposition(struct prometheus_collector * collector,char * buffer,size_t buffer_size)243 int prometheus_format_exposition(struct prometheus_collector *collector, char *buffer,
244 size_t buffer_size)
245 {
246 struct prometheus_metric *metric;
247 struct prometheus_metric *tmp;
248 int written = 0;
249 int ret = 0;
250
251 if (collector == NULL || buffer == NULL || buffer_size == 0) {
252 LOG_ERR("Invalid arguments");
253 return -EINVAL;
254 }
255
256 k_mutex_lock(&collector->lock, K_FOREVER);
257
258 SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&collector->metrics, metric, tmp, node) {
259
260 /* If there is a user callback, use it to update the metric data. */
261 if (collector->user_cb) {
262 ret = collector->user_cb(collector, metric, collector->user_data);
263 if (ret < 0) {
264 if (ret == -EAGAIN) {
265 /* Skip this metric for now */
266 continue;
267 }
268
269 LOG_ERR("Error in user callback (%d)", ret);
270 goto out;
271 }
272 }
273
274 ret = prometheus_format_one_metric(metric, buffer, buffer_size, &written);
275 if (ret < 0) {
276 goto out;
277 }
278 }
279
280 out:
281 k_mutex_unlock(&collector->lock);
282
283 return ret;
284 }
285