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