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
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"; 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
290 static int
ometric_output_labels(FILE * out,const struct olabels * ol)291 ometric_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
315 static int
ometric_output_value(FILE * out,const struct ovalue * ov)316 ometric_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
330 static int
ometric_output_name(FILE * out,const struct ometric * om)331 ometric_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 */
352 int
ometric_output_all(FILE * out)353 ometric_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 */
388 static void
ometric_set_int_value(struct ometric * om,uint64_t val,struct olabels * ol)389 ometric_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 */
406 void
ometric_set_int(struct ometric * om,uint64_t val,struct olabels * ol)407 ometric_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 */
418 void
ometric_set_float(struct ometric * om,double val,struct olabels * ol)419 ometric_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 */
439 void
ometric_set_timespec(struct ometric * om,const struct timespec * ts,struct olabels * ol)440 ometric_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 */
461 void
ometric_set_info(struct ometric * om,const char ** keys,const char ** values,struct olabels * ol)462 ometric_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 */
480 void
ometric_set_state(struct ometric * om,const char * state,struct olabels * ol)481 ometric_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 */
507 void
ometric_set_int_with_labels(struct ometric * om,uint64_t val,const char ** keys,const char ** values,struct olabels * ol)508 ometric_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
518 void
ometric_set_timespec_with_labels(struct ometric * om,struct timespec * ts,const char ** keys,const char ** values,struct olabels * ol)519 ometric_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