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 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"; 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 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 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 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 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 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 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 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 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 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 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 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 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