1/*	$OpenBSD: ometric.c,v 1.2 2023/01/06 13:22:00 deraadt 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"; node_exporter does not like this type */
277		return "gauge";
278	case OMT_HISTOGRAM:
279		return "histogram";
280	case OMT_SUMMARY:
281		return "summary";
282	case OMT_INFO:
283		/* return "info"; node_exporter does not like this type */
284		return "gauge";
285	default:
286		return "unknown";
287	}
288}
289
290static int
291ometric_output_labels(FILE *out, const struct olabels *ol)
292{
293	struct olabel *l;
294	const char *comma = "";
295
296	if (ol == NULL)
297		return fprintf(out, " ");
298
299	if (fprintf(out, "{") < 0)
300		return -1;
301
302	while (ol != NULL) {
303		STAILQ_FOREACH(l, &ol->labels, entry) {
304			if (fprintf(out, "%s%s=\"%s\"", comma, l->key,
305			    l->value) < 0)
306				return -1;
307			comma = ",";
308		}
309		ol = ol->next;
310	}
311
312	return fprintf(out, "} ");
313}
314
315static int
316ometric_output_value(FILE *out, const struct ovalue *ov)
317{
318	switch (ov->valtype) {
319	case OVT_INTEGER:
320		return fprintf(out, "%llu", ov->value.i);
321	case OVT_DOUBLE:
322		return fprintf(out, "%g", ov->value.f);
323	case OVT_TIMESPEC:
324		return fprintf(out, "%lld.%09ld",
325		    (long long)ov->value.ts.tv_sec, ov->value.ts.tv_nsec);
326	}
327	return -1;
328}
329
330static int
331ometric_output_name(FILE *out, const struct ometric *om)
332{
333	const char *suffix;
334
335	switch (om->type) {
336	case OMT_COUNTER:
337		suffix = "_total";
338		break;
339	case OMT_INFO:
340		suffix = "_info";
341		break;
342	default:
343		suffix = "";
344		break;
345	}
346	return fprintf(out, "%s%s", om->name, suffix);
347}
348
349/*
350 * Output all metric values with TYPE and optional HELP strings.
351 */
352int
353ometric_output_all(FILE *out)
354{
355	struct ometric *om;
356	struct ovalue *ov;
357
358	STAILQ_FOREACH(om, &ometrics, entry) {
359		if (om->help)
360			if (fprintf(out, "# HELP %s %s\n", om->name,
361			    om->help) < 0)
362				return -1;
363
364		if (fprintf(out, "# TYPE %s %s\n", om->name,
365		    ometric_type(om->type)) < 0)
366			return -1;
367
368		STAILQ_FOREACH(ov, &om->vals, entry) {
369			if (ometric_output_name(out, om) < 0)
370				return -1;
371			if (ometric_output_labels(out, ov->labels) < 0)
372				return -1;
373			if (ometric_output_value(out, ov) < 0)
374				return -1;
375			if (fprintf(out, "\n") < 0)
376				return -1;
377		}
378	}
379
380	if (fprintf(out, "# EOF\n") < 0)
381		return -1;
382	return 0;
383}
384
385/*
386 * Value setters
387 */
388static void
389ometric_set_int_value(struct ometric *om, uint64_t val, struct olabels *ol)
390{
391	struct ovalue *ov;
392
393	if ((ov = malloc(sizeof(*ov))) == NULL)
394		err(1, NULL);
395
396	ov->value.i = val;
397	ov->valtype = OVT_INTEGER;
398	ov->labels = olabels_ref(ol);
399
400	STAILQ_INSERT_TAIL(&om->vals, ov, entry);
401}
402
403/*
404 * Set an integer value with label ol. ol can be NULL.
405 */
406void
407ometric_set_int(struct ometric *om, uint64_t val, struct olabels *ol)
408{
409	if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
410		errx(1, "%s incorrect ometric type", __func__);
411
412	ometric_set_int_value(om, val, ol);
413}
414
415/*
416 * Set a floating point value with label ol. ol can be NULL.
417 */
418void
419ometric_set_float(struct ometric *om, double val, struct olabels *ol)
420{
421	struct ovalue *ov;
422
423	if (om->type != OMT_COUNTER && om->type != OMT_GAUGE)
424		errx(1, "%s incorrect ometric type", __func__);
425
426	if ((ov = malloc(sizeof(*ov))) == NULL)
427		err(1, NULL);
428
429	ov->value.f = val;
430	ov->valtype = OVT_DOUBLE;
431	ov->labels = olabels_ref(ol);
432
433	STAILQ_INSERT_TAIL(&om->vals, ov, entry);
434}
435
436/*
437 * Set an timespec value with label ol. ol can be NULL.
438 */
439void
440ometric_set_timespec(struct ometric *om, const struct timespec *ts,
441    struct olabels *ol)
442{
443	struct ovalue *ov;
444
445	if (om->type != OMT_GAUGE)
446		errx(1, "%s incorrect ometric type", __func__);
447
448	if ((ov = malloc(sizeof(*ov))) == NULL)
449		err(1, NULL);
450
451	ov->value.ts = *ts;
452	ov->valtype = OVT_TIMESPEC;
453	ov->labels = olabels_ref(ol);
454
455	STAILQ_INSERT_TAIL(&om->vals, ov, entry);
456}
457
458/*
459 * Add an info value (which is the value 1 but with extra key-value pairs).
460 */
461void
462ometric_set_info(struct ometric *om, const char **keys, const char **values,
463    struct olabels *ol)
464{
465	struct olabels *extra = NULL;
466
467	if (om->type != OMT_INFO)
468		errx(1, "%s incorrect ometric type", __func__);
469
470	if (keys != NULL)
471		extra = olabels_add_extras(ol, keys, values);
472
473	ometric_set_int_value(om, 1, extra != NULL ? extra : ol);
474	olabels_free(extra);
475}
476
477/*
478 * Set a stateset to one of its states.
479 */
480void
481ometric_set_state(struct ometric *om, const char *state, struct olabels *ol)
482{
483	struct olabels *extra;
484	size_t i;
485	int val;
486
487	if (om->type != OMT_STATESET)
488		errx(1, "%s incorrect ometric type", __func__);
489
490	for (i = 0; i < om->setsize; i++) {
491		if (strcasecmp(state, om->stateset[i]) == 0)
492			val = 1;
493		else
494			val = 0;
495
496		extra = olabels_add_extras(ol, OKV(om->name),
497		    OKV(om->stateset[i]));
498		ometric_set_int_value(om, val, extra);
499		olabels_free(extra);
500	}
501}
502
503/*
504 * Set a value with an extra label, the key should be a constant string while
505 * the value is copied into the extra label.
506 */
507void
508ometric_set_int_with_labels(struct ometric *om, uint64_t val,
509    const char **keys, const char **values, struct olabels *ol)
510{
511	struct olabels *extra;
512
513	extra = olabels_add_extras(ol, keys, values);
514	ometric_set_int(om, val, extra);
515	olabels_free(extra);
516}
517
518void
519ometric_set_timespec_with_labels(struct ometric *om, struct timespec *ts,
520    const char **keys, const char **values, struct olabels *ol)
521{
522	struct olabels *extra;
523
524	extra = olabels_add_extras(ol, keys, values);
525	ometric_set_timespec(om, ts, extra);
526	olabels_free(extra);
527}
528