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