1 /*- 2 * Copyright (c) 2016 Nuxi, https://nuxi.nl/ 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26 #include <sys/cdefs.h> 27 __FBSDID("$FreeBSD$"); 28 29 #include <sys/param.h> 30 #include <sys/resource.h> 31 #include <sys/socket.h> 32 #include <sys/sysctl.h> 33 34 #include <assert.h> 35 #include <err.h> 36 #include <errno.h> 37 #include <math.h> 38 #include <stdbool.h> 39 #include <stdint.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 #include <zlib.h> 45 46 /* 47 * Cursor for iterating over all of the system's sysctl OIDs. 48 */ 49 struct oid { 50 int id[CTL_MAXNAME]; 51 size_t len; 52 }; 53 54 /* Initializes the cursor to point to start of the tree. */ 55 static void 56 oid_get_root(struct oid *o) 57 { 58 59 o->id[0] = 1; 60 o->len = 1; 61 } 62 63 /* Obtains the OID for a sysctl by name. */ 64 static void 65 oid_get_by_name(struct oid *o, const char *name) 66 { 67 68 o->len = nitems(o->id); 69 if (sysctlnametomib(name, o->id, &o->len) != 0) 70 err(1, "sysctl(%s)", name); 71 } 72 73 /* Returns whether an OID is placed below another OID. */ 74 static bool 75 oid_is_beneath(struct oid *oa, struct oid *ob) 76 { 77 78 return (oa->len >= ob->len && 79 memcmp(oa->id, ob->id, ob->len * sizeof(oa->id[0])) == 0); 80 } 81 82 /* Advances the cursor to the next OID. */ 83 static bool 84 oid_get_next(const struct oid *cur, struct oid *next) 85 { 86 int lookup[CTL_MAXNAME + 2]; 87 size_t nextsize; 88 89 lookup[0] = 0; 90 lookup[1] = 2; 91 memcpy(lookup + 2, cur->id, cur->len * sizeof(lookup[0])); 92 nextsize = sizeof(next->id); 93 if (sysctl(lookup, 2 + cur->len, &next->id, &nextsize, 0, 0) != 0) { 94 if (errno == ENOENT) 95 return (false); 96 err(1, "sysctl(next)"); 97 } 98 next->len = nextsize / sizeof(next->id[0]); 99 return (true); 100 } 101 102 /* 103 * OID formatting metadata. 104 */ 105 struct oidformat { 106 unsigned int kind; 107 char format[BUFSIZ]; 108 }; 109 110 /* Returns whether the OID represents a temperature value. */ 111 static bool 112 oidformat_is_temperature(const struct oidformat *of) 113 { 114 115 return (of->format[0] == 'I' && of->format[1] == 'K'); 116 } 117 118 /* Returns whether the OID represents a timeval structure. */ 119 static bool 120 oidformat_is_timeval(const struct oidformat *of) 121 { 122 123 return (strcmp(of->format, "S,timeval") == 0); 124 } 125 126 /* Fetches the formatting metadata for an OID. */ 127 static bool 128 oid_get_format(const struct oid *o, struct oidformat *of) 129 { 130 int lookup[CTL_MAXNAME + 2]; 131 size_t oflen; 132 133 lookup[0] = 0; 134 lookup[1] = 4; 135 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0])); 136 oflen = sizeof(*of); 137 if (sysctl(lookup, 2 + o->len, of, &oflen, 0, 0) != 0) { 138 if (errno == ENOENT) 139 return (false); 140 err(1, "sysctl(oidfmt)"); 141 } 142 return (true); 143 } 144 145 /* 146 * Container for holding the value of an OID. 147 */ 148 struct oidvalue { 149 enum { SIGNED, UNSIGNED, FLOAT } type; 150 union { 151 intmax_t s; 152 uintmax_t u; 153 double f; 154 } value; 155 }; 156 157 /* Extracts the value of an OID, converting it to a floating-point number. */ 158 static double 159 oidvalue_get_float(const struct oidvalue *ov) 160 { 161 162 switch (ov->type) { 163 case SIGNED: 164 return (ov->value.s); 165 case UNSIGNED: 166 return (ov->value.u); 167 case FLOAT: 168 return (ov->value.f); 169 default: 170 assert(0 && "Unknown value type"); 171 } 172 } 173 174 /* Sets the value of an OID as a signed integer. */ 175 static void 176 oidvalue_set_signed(struct oidvalue *ov, intmax_t s) 177 { 178 179 ov->type = SIGNED; 180 ov->value.s = s; 181 } 182 183 /* Sets the value of an OID as an unsigned integer. */ 184 static void 185 oidvalue_set_unsigned(struct oidvalue *ov, uintmax_t u) 186 { 187 188 ov->type = UNSIGNED; 189 ov->value.u = u; 190 } 191 192 /* Sets the value of an OID as a floating-point number. */ 193 static void 194 oidvalue_set_float(struct oidvalue *ov, double f) 195 { 196 197 ov->type = FLOAT; 198 ov->value.f = f; 199 } 200 201 /* Prints the value of an OID to a file stream. */ 202 static void 203 oidvalue_print(const struct oidvalue *ov, FILE *fp) 204 { 205 206 switch (ov->type) { 207 case SIGNED: 208 fprintf(fp, "%jd", ov->value.s); 209 break; 210 case UNSIGNED: 211 fprintf(fp, "%ju", ov->value.u); 212 break; 213 case FLOAT: 214 switch (fpclassify(ov->value.f)) { 215 case FP_INFINITE: 216 if (signbit(ov->value.f)) 217 fprintf(fp, "-Inf"); 218 else 219 fprintf(fp, "+Inf"); 220 break; 221 case FP_NAN: 222 fprintf(fp, "Nan"); 223 break; 224 default: 225 fprintf(fp, "%.6f", ov->value.f); 226 break; 227 } 228 break; 229 } 230 } 231 232 /* Fetches the value of an OID. */ 233 static bool 234 oid_get_value(const struct oid *o, const struct oidformat *of, 235 struct oidvalue *ov) 236 { 237 238 switch (of->kind & CTLTYPE) { 239 #define GET_VALUE(ctltype, type) \ 240 case (ctltype): { \ 241 type value; \ 242 size_t valuesize; \ 243 \ 244 valuesize = sizeof(value); \ 245 if (sysctl(o->id, o->len, &value, &valuesize, 0, 0) != 0) \ 246 return (false); \ 247 if ((type)-1 > 0) \ 248 oidvalue_set_unsigned(ov, value); \ 249 else \ 250 oidvalue_set_signed(ov, value); \ 251 break; \ 252 } 253 GET_VALUE(CTLTYPE_INT, int); 254 GET_VALUE(CTLTYPE_UINT, unsigned int); 255 GET_VALUE(CTLTYPE_LONG, long); 256 GET_VALUE(CTLTYPE_ULONG, unsigned long); 257 GET_VALUE(CTLTYPE_S8, int8_t); 258 GET_VALUE(CTLTYPE_U8, uint8_t); 259 GET_VALUE(CTLTYPE_S16, int16_t); 260 GET_VALUE(CTLTYPE_U16, uint16_t); 261 GET_VALUE(CTLTYPE_S32, int32_t); 262 GET_VALUE(CTLTYPE_U32, uint32_t); 263 GET_VALUE(CTLTYPE_S64, int64_t); 264 GET_VALUE(CTLTYPE_U64, uint64_t); 265 #undef GET_VALUE 266 case CTLTYPE_OPAQUE: 267 if (oidformat_is_timeval(of)) { 268 struct timeval tv; 269 size_t tvsize; 270 271 tvsize = sizeof(tv); 272 if (sysctl(o->id, o->len, &tv, &tvsize, 0, 0) != 0) 273 return (false); 274 oidvalue_set_float(ov, 275 (double)tv.tv_sec + (double)tv.tv_usec / 1000000); 276 return (true); 277 } else if (strcmp(of->format, "S,loadavg") == 0) { 278 struct loadavg la; 279 size_t lasize; 280 281 /* 282 * Only return the one minute load average, as 283 * the others can be inferred using avg_over_time(). 284 */ 285 lasize = sizeof(la); 286 if (sysctl(o->id, o->len, &la, &lasize, 0, 0) != 0) 287 return (false); 288 oidvalue_set_float(ov, 289 (double)la.ldavg[0] / (double)la.fscale); 290 return (true); 291 } 292 return (false); 293 default: 294 return (false); 295 } 296 297 /* Convert temperatures from decikelvin to degrees Celcius. */ 298 if (oidformat_is_temperature(of)) { 299 double v; 300 int e; 301 302 v = oidvalue_get_float(ov); 303 if (v < 0) { 304 oidvalue_set_float(ov, NAN); 305 } else { 306 e = of->format[2] >= '0' && of->format[2] <= '9' ? 307 of->format[2] - '0' : 1; 308 oidvalue_set_float(ov, v / pow(10, e) - 273.15); 309 } 310 } 311 return (true); 312 } 313 314 /* 315 * The full name of an OID, stored as a series of components. 316 */ 317 struct oidname { 318 struct oid oid; 319 char names[BUFSIZ]; 320 char labels[BUFSIZ]; 321 }; 322 323 /* 324 * Initializes the OID name object with an empty value. 325 */ 326 static void 327 oidname_init(struct oidname *on) 328 { 329 330 on->oid.len = 0; 331 } 332 333 /* Fetches the name and labels of an OID, reusing the previous results. */ 334 static void 335 oid_get_name(const struct oid *o, struct oidname *on) 336 { 337 int lookup[CTL_MAXNAME + 2]; 338 char *c, *label; 339 size_t i, len; 340 341 /* Fetch the name and split it up in separate components. */ 342 lookup[0] = 0; 343 lookup[1] = 1; 344 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0])); 345 len = sizeof(on->names); 346 if (sysctl(lookup, 2 + o->len, on->names, &len, 0, 0) != 0) 347 err(1, "sysctl(name)"); 348 for (c = strchr(on->names, '.'); c != NULL; c = strchr(c + 1, '.')) 349 *c = '\0'; 350 351 /* No need to fetch labels for components that we already have. */ 352 label = on->labels; 353 for (i = 0; i < o->len && i < on->oid.len && o->id[i] == on->oid.id[i]; 354 ++i) 355 label += strlen(label) + 1; 356 357 /* Fetch the remaining labels. */ 358 lookup[1] = 6; 359 for (; i < o->len; ++i) { 360 len = on->labels + sizeof(on->labels) - label; 361 if (sysctl(lookup, 2 + i + 1, label, &len, 0, 0) == 0) { 362 label += len; 363 } else if (errno == ENOENT) { 364 *label++ = '\0'; 365 } else { 366 err(1, "sysctl(oidlabel)"); 367 } 368 } 369 on->oid = *o; 370 } 371 372 /* Prints the name and labels of an OID to a file stream. */ 373 static void 374 oidname_print(const struct oidname *on, const struct oidformat *of, 375 FILE *fp) 376 { 377 const char *name, *label; 378 size_t i; 379 char separator; 380 381 /* Print the name of the metric. */ 382 fprintf(fp, "sysctl"); 383 name = on->names; 384 label = on->labels; 385 for (i = 0; i < on->oid.len; ++i) { 386 if (*label == '\0') { 387 assert(name[strspn(name, 388 "abcdefghijklmnopqrstuvwxyz" 389 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 390 "0123456789_")] == '\0'); 391 fprintf(fp, "_%s", name); 392 } 393 name += strlen(name) + 1; 394 label += strlen(label) + 1; 395 } 396 if (oidformat_is_temperature(of)) 397 fprintf(fp, "_celcius"); 398 else if (oidformat_is_timeval(of)) 399 fprintf(fp, "_seconds"); 400 401 /* Print the labels of the metric. */ 402 name = on->names; 403 label = on->labels; 404 separator = '{'; 405 for (i = 0; i < on->oid.len; ++i) { 406 if (*label != '\0') { 407 assert(name[strspn(name, 408 "abcdefghijklmnopqrstuvwxyz" 409 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 410 "0123456789_-")] == '\0'); 411 assert(label[strspn(label, 412 "abcdefghijklmnopqrstuvwxyz" 413 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 414 "0123456789_")] == '\0'); 415 fprintf(fp, "%c%s=\"%s\"", separator, label, name); 416 separator = ','; 417 } 418 name += strlen(name) + 1; 419 label += strlen(label) + 1; 420 } 421 if (separator != '{') 422 fputc('}', fp); 423 } 424 425 /* Returns whether the OID name has any labels associated to it. */ 426 static bool 427 oidname_has_labels(const struct oidname *on) 428 { 429 size_t i; 430 431 for (i = 0; i < on->oid.len; ++i) 432 if (on->labels[i] != 0) 433 return (true); 434 return (false); 435 } 436 437 /* 438 * The description of an OID. 439 */ 440 struct oiddescription { 441 char description[BUFSIZ]; 442 }; 443 444 /* 445 * Fetches the description of an OID. 446 */ 447 static bool 448 oid_get_description(const struct oid *o, struct oiddescription *od) 449 { 450 int lookup[CTL_MAXNAME + 2]; 451 char *newline; 452 size_t odlen; 453 454 lookup[0] = 0; 455 lookup[1] = 5; 456 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0])); 457 odlen = sizeof(od->description); 458 if (sysctl(lookup, 2 + o->len, &od->description, &odlen, 0, 0) != 0) { 459 if (errno == ENOENT) 460 return (false); 461 err(1, "sysctl(oiddescr)"); 462 } 463 464 newline = strchr(od->description, '\n'); 465 if (newline != NULL) 466 *newline = '\0'; 467 468 return (*od->description != '\0'); 469 } 470 471 /* Prints the description of an OID to a file stream. */ 472 static void 473 oiddescription_print(const struct oiddescription *od, FILE *fp) 474 { 475 476 fprintf(fp, "%s", od->description); 477 } 478 479 static void 480 oid_print(const struct oid *o, struct oidname *on, bool print_description, 481 FILE *fp) 482 { 483 struct oidformat of; 484 struct oidvalue ov; 485 struct oiddescription od; 486 487 if (!oid_get_format(o, &of) || !oid_get_value(o, &of, &ov)) 488 return; 489 oid_get_name(o, on); 490 491 /* 492 * Print the line with the description. Prometheus expects a 493 * single unique description for every metric, which cannot be 494 * guaranteed by sysctl if labels are present. Omit the 495 * description if labels are present. 496 */ 497 if (print_description && !oidname_has_labels(on) && 498 oid_get_description(o, &od)) { 499 fprintf(fp, "# HELP "); 500 oidname_print(on, &of, fp); 501 fputc(' ', fp); 502 oiddescription_print(&od, fp); 503 fputc('\n', fp); 504 } 505 506 /* Print the line with the value. */ 507 oidname_print(on, &of, fp); 508 fputc(' ', fp); 509 oidvalue_print(&ov, fp); 510 fputc('\n', fp); 511 } 512 513 /* Gzip compresses a buffer of memory. */ 514 static bool 515 buf_gzip(const char *in, size_t inlen, char *out, size_t *outlen) 516 { 517 z_stream stream = { 518 .next_in = __DECONST(unsigned char *, in), 519 .avail_in = inlen, 520 .next_out = (unsigned char *)out, 521 .avail_out = *outlen, 522 }; 523 524 if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 525 MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK || 526 deflate(&stream, Z_FINISH) != Z_STREAM_END) { 527 return (false); 528 } 529 *outlen = stream.total_out; 530 return (deflateEnd(&stream) == Z_OK); 531 } 532 533 static void 534 usage(void) 535 { 536 537 fprintf(stderr, 538 "usage: prometheus_sysctl_exporter [-dgh] [prefix ...]\n"); 539 exit(1); 540 } 541 542 int 543 main(int argc, char *argv[]) 544 { 545 struct oidname on; 546 char *http_buf; 547 FILE *fp; 548 size_t http_buflen; 549 int ch; 550 bool gzip_mode, http_mode, print_descriptions; 551 552 /* Parse command line flags. */ 553 gzip_mode = http_mode = print_descriptions = false; 554 while ((ch = getopt(argc, argv, "dgh")) != -1) { 555 switch (ch) { 556 case 'd': 557 print_descriptions = true; 558 break; 559 case 'g': 560 gzip_mode = true; 561 break; 562 case 'h': 563 http_mode = true; 564 break; 565 default: 566 usage(); 567 } 568 } 569 argc -= optind; 570 argv += optind; 571 572 /* HTTP output: cache metrics in buffer. */ 573 if (http_mode) { 574 fp = open_memstream(&http_buf, &http_buflen); 575 if (fp == NULL) 576 err(1, "open_memstream"); 577 } else { 578 fp = stdout; 579 } 580 581 oidname_init(&on); 582 if (argc == 0) { 583 struct oid o; 584 585 /* Print all OIDs. */ 586 oid_get_root(&o); 587 do { 588 oid_print(&o, &on, print_descriptions, fp); 589 } while (oid_get_next(&o, &o)); 590 } else { 591 int i; 592 593 /* Print only trees provided as arguments. */ 594 for (i = 0; i < argc; ++i) { 595 struct oid o, root; 596 597 oid_get_by_name(&root, argv[i]); 598 o = root; 599 do { 600 oid_print(&o, &on, print_descriptions, fp); 601 } while (oid_get_next(&o, &o) && 602 oid_is_beneath(&o, &root)); 603 } 604 } 605 606 if (http_mode) { 607 const char *content_encoding = ""; 608 609 if (ferror(fp) || fclose(fp) != 0) 610 err(1, "Cannot generate output"); 611 612 /* Gzip compress the output. */ 613 if (gzip_mode) { 614 char *buf; 615 size_t buflen; 616 617 buflen = http_buflen; 618 buf = malloc(buflen); 619 if (buf == NULL) 620 err(1, "Cannot allocate compression buffer"); 621 if (buf_gzip(http_buf, http_buflen, buf, &buflen)) { 622 content_encoding = "Content-Encoding: gzip\r\n"; 623 free(http_buf); 624 http_buf = buf; 625 http_buflen = buflen; 626 } else { 627 free(buf); 628 } 629 } 630 631 /* Print HTTP header and metrics. */ 632 dprintf(STDOUT_FILENO, 633 "HTTP/1.1 200 OK\r\n" 634 "Connection: close\r\n" 635 "%s" 636 "Content-Length: %zu\r\n" 637 "Content-Type: text/plain; version=0.0.4\r\n" 638 "\r\n", 639 content_encoding, http_buflen); 640 write(STDOUT_FILENO, http_buf, http_buflen); 641 free(http_buf); 642 643 /* Drain output. */ 644 if (shutdown(STDIN_FILENO, SHUT_WR) == 0) { 645 char buf[1024]; 646 647 while (read(STDIN_FILENO, buf, sizeof(buf)) > 0) { 648 } 649 } 650 } 651 return (0); 652 } 653