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/collector.h>
9 
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/counter.h>
14 #include <zephyr/net/prometheus/gauge.h>
15 #include <zephyr/net/prometheus/formatter.h>
16 
17 #include <stdlib.h>
18 #include <string.h>
19 
20 #include <zephyr/kernel.h>
21 #include <zephyr/sys/iterable_sections.h>
22 
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(pm_collector, CONFIG_PROMETHEUS_LOG_LEVEL);
25 
prometheus_collector_register_metric(struct prometheus_collector * collector,struct prometheus_metric * metric)26 int prometheus_collector_register_metric(struct prometheus_collector *collector,
27 					 struct prometheus_metric *metric)
28 {
29 	if (!collector || !metric) {
30 		LOG_ERR("Invalid arguments");
31 		return -EINVAL;
32 	}
33 
34 	LOG_DBG("Registering metric type=%d name=\"%s\"", metric->type, metric->name);
35 
36 	k_mutex_lock(&collector->lock, K_FOREVER);
37 
38 	/* Node cannot be added to list twice */
39 	(void)sys_slist_find_and_remove(&collector->metrics, &metric->node);
40 
41 	sys_slist_prepend(&collector->metrics, &metric->node);
42 
43 	k_mutex_unlock(&collector->lock);
44 
45 	return 0;
46 }
47 
prometheus_get_counter_metric(const char * name)48 const struct prometheus_counter *prometheus_get_counter_metric(const char *name)
49 {
50 	STRUCT_SECTION_FOREACH(prometheus_counter, entry) {
51 		LOG_DBG("entry->name: %s", entry->base.name);
52 
53 		if (strncmp(entry->base.name, name, strlen(entry->base.name)) == 0) {
54 			LOG_DBG("Counter found %s", entry->base.name);
55 			return entry;
56 		}
57 	}
58 
59 	LOG_DBG("%s %s not found", "Counter", name);
60 
61 	return NULL;
62 }
63 
prometheus_get_gauge_metric(const char * name)64 const struct prometheus_gauge *prometheus_get_gauge_metric(const char *name)
65 {
66 	STRUCT_SECTION_FOREACH(prometheus_gauge, entry) {
67 		LOG_DBG("entry->name: %s", entry->base.name);
68 
69 		if (strncmp(entry->base.name, name, strlen(entry->base.name)) == 0) {
70 			LOG_DBG("Counter found %s", entry->base.name);
71 			return entry;
72 		}
73 	}
74 
75 	LOG_DBG("%s %s not found", "Gauge", name);
76 
77 	return NULL;
78 }
79 
prometheus_get_histogram_metric(const char * name)80 const struct prometheus_histogram *prometheus_get_histogram_metric(const char *name)
81 {
82 	STRUCT_SECTION_FOREACH(prometheus_histogram, entry) {
83 		LOG_DBG("entry->name: %s", entry->base.name);
84 
85 		if (strncmp(entry->base.name, name, strlen(entry->base.name)) == 0) {
86 			LOG_DBG("Counter found %s", entry->base.name);
87 			return entry;
88 		}
89 	}
90 
91 	LOG_DBG("%s %s not found", "Histogram", name);
92 
93 	return NULL;
94 }
95 
prometheus_get_summary_metric(const char * name)96 const struct prometheus_summary *prometheus_get_summary_metric(const char *name)
97 {
98 	STRUCT_SECTION_FOREACH(prometheus_summary, entry) {
99 		LOG_DBG("entry->name: %s", entry->base.name);
100 
101 		if (strncmp(entry->base.name, name, strlen(entry->base.name)) == 0) {
102 			LOG_DBG("Counter found %s", entry->base.name);
103 			return entry;
104 		}
105 	}
106 
107 	LOG_DBG("%s %s not found", "Summary", name);
108 
109 	return NULL;
110 }
111 
prometheus_collector_get_metric(struct prometheus_collector * collector,const char * name)112 const void *prometheus_collector_get_metric(struct prometheus_collector *collector,
113 					    const char *name)
114 {
115 	bool is_found = false;
116 	enum prometheus_metric_type type = 0;
117 	struct prometheus_metric *metric;
118 	struct prometheus_metric *tmp;
119 
120 	if (collector == NULL || name == NULL) {
121 		return NULL;
122 	}
123 
124 	k_mutex_lock(&collector->lock, K_FOREVER);
125 
126 	SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&collector->metrics, metric, tmp, node) {
127 
128 		if (strncmp(metric->name, name, strlen(metric->name)) == 0) {
129 			type = metric->type;
130 			is_found = true;
131 
132 			LOG_DBG("metric found: %s", metric->name);
133 			break;
134 		}
135 	}
136 
137 	k_mutex_unlock(&collector->lock);
138 
139 	if (!is_found) {
140 		LOG_ERR("Metric %s not found", name);
141 		goto out;
142 	}
143 
144 	switch (type) {
145 	case PROMETHEUS_COUNTER:
146 		return CONTAINER_OF(metric, struct prometheus_counter, base);
147 	case PROMETHEUS_GAUGE:
148 		return CONTAINER_OF(metric, struct prometheus_gauge, base);
149 	case PROMETHEUS_HISTOGRAM:
150 		return CONTAINER_OF(metric, struct prometheus_histogram, base);
151 	case PROMETHEUS_SUMMARY:
152 		return CONTAINER_OF(metric, struct prometheus_summary, base);
153 	default:
154 		LOG_ERR("Invalid metric type");
155 		break;
156 	}
157 
158 out:
159 	return NULL;
160 }
161 
prometheus_collector_walk_metrics(struct prometheus_collector_walk_context * ctx,uint8_t * buffer,size_t buffer_size)162 int prometheus_collector_walk_metrics(struct prometheus_collector_walk_context *ctx,
163 				      uint8_t *buffer, size_t buffer_size)
164 {
165 	int ret = 0;
166 
167 	if (ctx->collector == NULL) {
168 		LOG_ERR("Invalid arguments");
169 		return -EINVAL;
170 	}
171 
172 	if (ctx->state == PROMETHEUS_WALK_START) {
173 		k_mutex_lock(&ctx->collector->lock, K_FOREVER);
174 		ctx->state = PROMETHEUS_WALK_CONTINUE;
175 
176 		/* Start of the loop is taken from
177 		 * SYS_SLIST_FOR_EACH_CONTAINER_SAFE macro to simulate
178 		 * a loop.
179 		 */
180 
181 		ctx->metric = Z_GENLIST_PEEK_HEAD_CONTAINER(slist,
182 							    &ctx->collector->metrics,
183 							    ctx->metric,
184 							    node);
185 		ctx->tmp = Z_GENLIST_PEEK_NEXT_CONTAINER(slist,
186 							 ctx->metric,
187 							 node);
188 	}
189 
190 	if (ctx->state == PROMETHEUS_WALK_CONTINUE) {
191 		int len = 0;
192 
193 		ctx->metric = ctx->tmp;
194 		ctx->tmp = Z_GENLIST_PEEK_NEXT_CONTAINER(slist,
195 							 ctx->metric,
196 							 node);
197 
198 		if (ctx->metric == NULL) {
199 			ctx->state = PROMETHEUS_WALK_STOP;
200 			goto out;
201 		}
202 
203 		/* If there is a user callback, use it to update the metric data. */
204 		if (ctx->collector->user_cb) {
205 			ret = ctx->collector->user_cb(ctx->collector, ctx->metric,
206 						      ctx->collector->user_data);
207 			if (ret < 0) {
208 				if (ret != -EAGAIN) {
209 					ctx->state = PROMETHEUS_WALK_STOP;
210 					goto out;
211 				}
212 
213 				/* Skip this metric for now */
214 				goto out;
215 			}
216 		}
217 
218 		ret = prometheus_format_one_metric(ctx->metric, buffer, buffer_size, &len);
219 		if (ret < 0) {
220 			ctx->state = PROMETHEUS_WALK_STOP;
221 			goto out;
222 		}
223 
224 		ret = -EAGAIN;
225 	}
226 
227 out:
228 	if (ctx->state == PROMETHEUS_WALK_STOP) {
229 		k_mutex_unlock(&ctx->collector->lock);
230 		ret = 0;
231 	}
232 
233 	return ret;
234 }
235