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
31struct olabel {
32	STAILQ_ENTRY(olabel)	 entry;
33	const char		*key;
34	char			*value;
35};
36
37struct olabels {
38	STAILQ_HEAD(, olabel)	 labels;
39	struct olabels		*next;
40	int			 refcnt;
41};
42
43enum ovalue_type {
44	OVT_INTEGER,
45	OVT_DOUBLE,
46	OVT_TIMESPEC,
47};
48
49struct 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
60STAILQ_HEAD(ovalues, ovalue);
61
62struct 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
72STAILQ_HEAD(, ometric)	ometrics = STAILQ_HEAD_INITIALIZER(ometrics);
73
74static 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 */
81static int
82strsuffix(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
97static void
98ometric_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 */
113struct ometric *
114ometric_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 */
138struct ometric *
139ometric_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
161void
162ometric_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
178static struct olabels *
179olabels_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 */
197struct olabels *
198olabels_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 */
229void
230olabels_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 */
253static struct olabels *
254olabels_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 */
267static const char *
268ometric_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
288static int
289ometric_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
313static int
314ometric_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
328static int
329ometric_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 */
350int
351ometric_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 */
386static void
387ometric_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 */
404void
405ometric_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 */
416void
417ometric_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 */
437void
438ometric_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 */
459void
460ometric_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 */
478void
479ometric_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 */
505void
506ometric_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
516void
517ometric_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