xref: /openbsd-src/usr.sbin/bgpctl/ometric.c (revision 81b1a5f567e3c330c826411b75b8cf197bb8da4b)
1 /*	$OpenBSD: ometric.c,v 1.10 2023/01/06 13:26:57 tb Exp $ */
2 
3 /*
4  * Copyright (c) 2022 Claudio Jeker <claudio@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/queue.h>
20 #include <sys/time.h>
21 
22 #include <err.h>
23 #include <stdarg.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "ometric.h"
30 
31 struct olabel {
32 	STAILQ_ENTRY(olabel)	 entry;
33 	const char		*key;
34 	char			*value;
35 };
36 
37 struct olabels {
38 	STAILQ_HEAD(, olabel)	 labels;
39 	struct olabels		*next;
40 	int			 refcnt;
41 };
42 
43 enum ovalue_type {
44 	OVT_INTEGER,
45 	OVT_DOUBLE,
46 	OVT_TIMESPEC,
47 };
48 
49 struct ovalue {
50 	STAILQ_ENTRY(ovalue)	 entry;
51 	struct olabels		*labels;
52 	union {
53 		unsigned long long	i;
54 		double			f;
55 		struct timespec		ts;
56 	}			 value;
57 	enum ovalue_type	 valtype;
58 };
59 
60 STAILQ_HEAD(ovalues, ovalue);
61 
62 struct ometric {
63 	STAILQ_ENTRY(ometric)	 entry;
64 	struct ovalues		 vals;
65 	const char		*name;
66 	const char		*help;
67 	const char *const	*stateset;
68 	size_t			 setsize;
69 	enum ometric_type	 type;
70 };
71 
72 STAILQ_HEAD(, ometric)	ometrics = STAILQ_HEAD_INITIALIZER(ometrics);
73 
74 static const char *suffixes[] = { "_total", "_created", "_count",
75 	"_sum", "_bucket", "_gcount", "_gsum", "_info",
76 };
77 
78 /*
79  * Return true if name has one of the above suffixes.
80  */
81 static int
strsuffix(const char * name)82 strsuffix(const char *name)
83 {
84 	const char *suffix;
85 	size_t	i;
86 
87 	suffix = strrchr(name, '_');
88 	if (suffix == NULL)
89 		return 0;
90 	for (i = 0; i < sizeof(suffixes) / sizeof(suffixes[0]); i++) {
91 		if (strcmp(suffix, suffixes[i]) == 0)
92 			return 1;
93 	}
94 	return 0;
95 }
96 
97 static void
ometric_check(const char * name)98 ometric_check(const char *name)
99 {
100 	struct ometric *om;
101 
102 	if (strsuffix(name))
103 		errx(1, "reserved name suffix used: %s", name);
104 	STAILQ_FOREACH(om, &ometrics, entry)
105 		if (strcmp(name, om->name) == 0)
106 			errx(1, "duplicate name: %s", name);
107 }
108 
109 /*
110  * Allocate and return new ometric. The name and help string need to remain
111  * valid until the ometric is freed. Normally constant strings should be used.
112  */
113 struct ometric *
ometric_new(enum ometric_type type,const char * name,const char * help)114 ometric_new(enum ometric_type type, const char *name, const char *help)
115 {
116 	struct ometric *om;
117 
118 	ometric_check(name);
119 
120 	if ((om = calloc(1, sizeof(*om))) == NULL)
121 		err(1, NULL);
122 
123 	om->name = name;
124 	om->help = help;
125 	om->type = type;
126 	STAILQ_INIT(&om->vals);
127 
128 	STAILQ_INSERT_TAIL(&ometrics, om, entry);
129 
130 	return om;
131 }
132 
133 /*
134  * Same as above but for a stateset. The states is an array of constant strings
135  * with statecnt elements. The states, name and help pointers need to remain
136  * valid until the ometric is freed.
137  */
138 struct ometric *
ometric_new_state(const char * const * states,size_t statecnt,const char * name,const char * help)139 ometric_new_state(const char * const *states, size_t statecnt, const char *name,
140     const char *help)
141 {
142 	struct ometric *om;
143 
144 	ometric_check(name);
145 
146 	if ((om = calloc(1, sizeof(*om))) == NULL)
147 		err(1, NULL);
148 
149 	om->name = name;
150 	om->help = help;
151 	om->type = OMT_STATESET;
152 	om->stateset = states;
153 	om->setsize = statecnt;
154 	STAILQ_INIT(&om->vals);
155 
156 	STAILQ_INSERT_TAIL(&ometrics, om, entry);
157 
158 	return om;
159 }
160 
161 void
ometric_free_all(void)162 ometric_free_all(void)
163 {
164 	struct ometric *om;
165 	struct ovalue *ov;
166 
167 	while ((om = STAILQ_FIRST(&ometrics)) != NULL) {
168 		STAILQ_REMOVE_HEAD(&ometrics, entry);
169 		while ((ov = STAILQ_FIRST(&om->vals)) != NULL) {
170 			STAILQ_REMOVE_HEAD(&om->vals, entry);
171 			olabels_free(ov->labels);
172 			free(ov);
173 		}
174 		free(om);
175 	}
176 }
177 
178 static struct olabels *
olabels_ref(struct olabels * ol)179 olabels_ref(struct olabels *ol)
180 {
181 	struct olabels *x = ol;
182 
183 	while (x != NULL) {
184 		x->refcnt++;
185 		x = x->next;
186 	}
187 
188 	return ol;
189 }
190 
191 /*
192  * Create a new set of labels based on keys and values arrays.
193  * keys must end in a NULL element. values needs to hold as many elements
194  * but the elements can be NULL. values are copied for the olabel but
195  * keys needs to point to constant memory.
196  */
197 struct olabels *
olabels_new(const char * const * keys,const char ** values)198 olabels_new(const char * const *keys, const char **values)
199 {
200 	struct olabels *ol;
201 	struct olabel  *l;
202 
203 	if ((ol = malloc(sizeof(*ol))) == NULL)
204 		err(1, NULL);
205 	STAILQ_INIT(&ol->labels);
206 	ol->refcnt = 1;
207 	ol->next = NULL;
208 
209 	while (*keys != NULL) {
210 		if (*values && **values != '\0') {
211 			if ((l = malloc(sizeof(*l))) == NULL)
212 				err(1, NULL);
213 			l->key = *keys;
214 			if ((l->value = strdup(*values)) == NULL)
215 				err(1, NULL);
216 			STAILQ_INSERT_TAIL(&ol->labels, l, entry);
217 		}
218 
219 		keys++;
220 		values++;
221 	}
222 
223 	return ol;
224 }
225 
226 /*
227  * Free olables once nothing uses them anymore.
228  */
229 void
olabels_free(struct olabels * ol)230 olabels_free(struct olabels *ol)
231 {
232 	struct olabels *next;
233 	struct olabel  *l;
234 
235 	for ( ; ol != NULL; ol = next) {
236 		next = ol->next;
237 
238 		if (--ol->refcnt == 0) {
239 			while ((l = STAILQ_FIRST(&ol->labels)) != NULL) {
240 				STAILQ_REMOVE_HEAD(&ol->labels, entry);
241 				free(l->value);
242 				free(l);
243 			}
244 			free(ol);
245 		}
246 	}
247 }
248 
249 /*
250  * Add one extra label onto the label stack. Once no longer used the
251  * value needs to be freed with olabels_free().
252  */
253 static struct olabels *
olabels_add_extras(struct olabels * ol,const char ** keys,const char ** values)254 olabels_add_extras(struct olabels *ol, const char **keys, const char **values)
255 {
256 	struct olabels *new;
257 
258 	new = olabels_new(keys, values);
259 	new->next = olabels_ref(ol);
260 
261 	return new;
262 }
263 
264 /*
265  * Output function called last.
266  */
267 static const char *
ometric_type(enum ometric_type type)268 ometric_type(enum ometric_type type)
269 {
270 	switch (type) {
271 	case OMT_GAUGE:
272 		return "gauge";
273 	case OMT_COUNTER:
274 		return "counter";
275 	case OMT_STATESET:
276 		return "stateset";
277 	case OMT_HISTOGRAM:
278 		return "histogram";
279 	case OMT_SUMMARY:
280 		return "summary";
281 	case OMT_INFO:
282 		return "info";
283 	default:
284 		return "unknown";
285 	}
286 }
287 
288 static int
ometric_output_labels(FILE * out,const struct olabels * ol)289 ometric_output_labels(FILE *out, const struct olabels *ol)
290 {
291 	struct olabel *l;
292 	const char *comma = "";
293 
294 	if (ol == NULL)
295 		return fprintf(out, " ");
296 
297 	if (fprintf(out, "{") < 0)
298 		return -1;
299 
300 	while (ol != NULL) {
301 		STAILQ_FOREACH(l, &ol->labels, entry) {
302 			if (fprintf(out, "%s%s=\"%s\"", comma, l->key,
303 			    l->value) < 0)
304 				return -1;
305 			comma = ",";
306 		}
307 		ol = ol->next;
308 	}
309 
310 	return fprintf(out, "} ");
311 }
312 
313 static int
ometric_output_value(FILE * out,const struct ovalue * ov)314 ometric_output_value(FILE *out, const struct ovalue *ov)
315 {
316 	switch (ov->valtype) {
317 	case OVT_INTEGER:
318 		return fprintf(out, "%llu", ov->value.i);
319 	case OVT_DOUBLE:
320 		return fprintf(out, "%g", ov->value.f);
321 	case OVT_TIMESPEC:
322 		return fprintf(out, "%lld.%09ld",
323 		    (long long)ov->value.ts.tv_sec, ov->value.ts.tv_nsec);
324 	}
325 	return -1;
326 }
327 
328 static int
ometric_output_name(FILE * out,const struct ometric * om)329 ometric_output_name(FILE *out, const struct ometric *om)
330 {
331 	const char *suffix;
332 
333 	switch (om->type) {
334 	case OMT_COUNTER:
335 		suffix = "_total";
336 		break;
337 	case OMT_INFO:
338 		suffix = "_info";
339 		break;
340 	default:
341 		suffix = "";
342 		break;
343 	}
344 	return fprintf(out, "%s%s", om->name, suffix);
345 }
346 
347 /*
348  * Output all metric values with TYPE and optional HELP strings.
349  */
350 int
ometric_output_all(FILE * out)351 ometric_output_all(FILE *out)
352 {
353 	struct ometric *om;
354 	struct ovalue *ov;
355 
356 	STAILQ_FOREACH(om, &ometrics, entry) {
357 		if (om->help)
358 			if (fprintf(out, "# HELP %s %s\n", om->name,
359 			    om->help) < 0)
360 				return -1;
361 
362 		if (fprintf(out, "# TYPE %s %s\n", om->name,
363 		    ometric_type(om->type)) < 0)
364 			return -1;
365 
366 		STAILQ_FOREACH(ov, &om->vals, entry) {
367 			if (ometric_output_name(out, om) < 0)
368 				return -1;
369 			if (ometric_output_labels(out, ov->labels) < 0)
370 				return -1;
371 			if (ometric_output_value(out, ov) < 0)
372 				return -1;
373 			if (fprintf(out, "\n") < 0)
374 				return -1;
375 		}
376 	}
377 
378 	if (fprintf(out, "# EOF\n") < 0)
379 		return -1;
380 	return 0;
381 }
382 
383 /*
384  * Value setters
385  */
386 static void
ometric_set_int_value(struct ometric * om,uint64_t val,struct olabels * ol)387 ometric_set_int_value(struct ometric *om, uint64_t val, struct olabels *ol)
388 {
389 	struct ovalue *ov;
390 
391 	if ((ov = malloc(sizeof(*ov))) == NULL)
392 		err(1, NULL);
393 
394 	ov->value.i = val;
395 	ov->valtype = OVT_INTEGER;
396 	ov->labels = olabels_ref(ol);
397 
398 	STAILQ_INSERT_TAIL(&om->vals, ov, entry);
399 }
400 
401 /*
402  * Set an integer value with label ol. ol can be NULL.
403  */
404 void
ometric_set_int(struct ometric * om,uint64_t val,struct olabels * ol)405 ometric_set_int(struct ometric *om, uint64_t val, struct olabels *ol)
406 {
407 	if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
408 		errx(1, "%s incorrect ometric type", __func__);
409 
410 	ometric_set_int_value(om, val, ol);
411 }
412 
413 /*
414  * Set a floating point value with label ol. ol can be NULL.
415  */
416 void
ometric_set_float(struct ometric * om,double val,struct olabels * ol)417 ometric_set_float(struct ometric *om, double val, struct olabels *ol)
418 {
419 	struct ovalue *ov;
420 
421 	if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
422 		errx(1, "%s incorrect ometric type", __func__);
423 
424 	if ((ov = malloc(sizeof(*ov))) == NULL)
425 		err(1, NULL);
426 
427 	ov->value.f = val;
428 	ov->valtype = OVT_DOUBLE;
429 	ov->labels = olabels_ref(ol);
430 
431 	STAILQ_INSERT_TAIL(&om->vals, ov, entry);
432 }
433 
434 /*
435  * Set an timespec value with label ol. ol can be NULL.
436  */
437 void
ometric_set_timespec(struct ometric * om,const struct timespec * ts,struct olabels * ol)438 ometric_set_timespec(struct ometric *om, const struct timespec *ts,
439     struct olabels *ol)
440 {
441 	struct ovalue *ov;
442 
443 	if (om->type != OMT_GAUGE)
444 		errx(1, "%s incorrect ometric type", __func__);
445 
446 	if ((ov = malloc(sizeof(*ov))) == NULL)
447 		err(1, NULL);
448 
449 	ov->value.ts = *ts;
450 	ov->valtype = OVT_TIMESPEC;
451 	ov->labels = olabels_ref(ol);
452 
453 	STAILQ_INSERT_TAIL(&om->vals, ov, entry);
454 }
455 
456 /*
457  * Add an info value (which is the value 1 but with extra key-value pairs).
458  */
459 void
ometric_set_info(struct ometric * om,const char ** keys,const char ** values,struct olabels * ol)460 ometric_set_info(struct ometric *om, const char **keys, const char **values,
461     struct olabels *ol)
462 {
463 	struct olabels *extra = NULL;
464 
465 	if (om->type != OMT_INFO)
466 		errx(1, "%s incorrect ometric type", __func__);
467 
468 	if (keys != NULL)
469 		extra = olabels_add_extras(ol, keys, values);
470 
471 	ometric_set_int_value(om, 1, extra != NULL ? extra : ol);
472 	olabels_free(extra);
473 }
474 
475 /*
476  * Set a stateset to one of its states.
477  */
478 void
ometric_set_state(struct ometric * om,const char * state,struct olabels * ol)479 ometric_set_state(struct ometric *om, const char *state, struct olabels *ol)
480 {
481 	struct olabels *extra;
482 	size_t i;
483 	int val;
484 
485 	if (om->type != OMT_STATESET)
486 		errx(1, "%s incorrect ometric type", __func__);
487 
488 	for (i = 0; i < om->setsize; i++) {
489 		if (strcasecmp(state, om->stateset[i]) == 0)
490 			val = 1;
491 		else
492 			val = 0;
493 
494 		extra = olabels_add_extras(ol, OKV(om->name),
495 		    OKV(om->stateset[i]));
496 		ometric_set_int_value(om, val, extra);
497 		olabels_free(extra);
498 	}
499 }
500 
501 /*
502  * Set a value with an extra label, the key should be a constant string while
503  * the value is copied into the extra label.
504  */
505 void
ometric_set_int_with_labels(struct ometric * om,uint64_t val,const char ** keys,const char ** values,struct olabels * ol)506 ometric_set_int_with_labels(struct ometric *om, uint64_t val,
507     const char **keys, const char **values, struct olabels *ol)
508 {
509 	struct olabels *extra;
510 
511 	extra = olabels_add_extras(ol, keys, values);
512 	ometric_set_int(om, val, extra);
513 	olabels_free(extra);
514 }
515 
516 void
ometric_set_timespec_with_labels(struct ometric * om,struct timespec * ts,const char ** keys,const char ** values,struct olabels * ol)517 ometric_set_timespec_with_labels(struct ometric *om, struct timespec *ts,
518     const char **keys, const char **values, struct olabels *ol)
519 {
520 	struct olabels *extra;
521 
522 	extra = olabels_add_extras(ol, keys, values);
523 	ometric_set_timespec(om, ts, extra);
524 	olabels_free(extra);
525 }
526