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 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"; 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 288static int 289ometric_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 313static int 314ometric_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 328static int 329ometric_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 */ 350int 351ometric_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 */ 386static void 387ometric_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 */ 404void 405ometric_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 */ 416void 417ometric_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 */ 437void 438ometric_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 */ 459void 460ometric_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 */ 478void 479ometric_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 */ 505void 506ometric_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 516void 517ometric_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