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 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 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 * 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 * 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 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 * 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 * 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 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 * 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 * 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 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 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 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 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 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 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 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 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 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 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 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 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