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