2 * Copyright (c) 2016-2017 Nuxi, https://nuxi.nl/
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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
26 #include <sys/cdefs.h>
27 __FBSDID("$FreeBSD$");
29 #include <sys/param.h>
30 #include <sys/resource.h>
31 #include <sys/socket.h>
32 #include <sys/sysctl.h>
48 /* Regular expressions for filtering output. */
49 static regex_t inc_regex;
50 static regex_t exc_regex;
53 * Cursor for iterating over all of the system's sysctl OIDs.
60 /* Initializes the cursor to point to start of the tree. */
62 oid_get_root(struct oid *o)
69 /* Obtains the OID for a sysctl by name. */
71 oid_get_by_name(struct oid *o, const char *name)
74 o->len = nitems(o->id);
75 return (sysctlnametomib(name, o->id, &o->len) == 0);
78 /* Returns whether an OID is placed below another OID. */
80 oid_is_beneath(struct oid *oa, struct oid *ob)
83 return (oa->len >= ob->len &&
84 memcmp(oa->id, ob->id, ob->len * sizeof(oa->id[0])) == 0);
87 /* Advances the cursor to the next OID. */
89 oid_get_next(const struct oid *cur, struct oid *next)
91 int lookup[CTL_MAXNAME + 2];
94 lookup[0] = CTL_SYSCTL;
95 lookup[1] = CTL_SYSCTL_NEXT;
96 memcpy(lookup + 2, cur->id, cur->len * sizeof(lookup[0]));
97 nextsize = sizeof(next->id);
98 if (sysctl(lookup, 2 + cur->len, &next->id, &nextsize, 0, 0) != 0) {
101 err(1, "sysctl(next)");
103 next->len = nextsize / sizeof(next->id[0]);
108 * OID formatting metadata.
115 /* Returns whether the OID represents a temperature value. */
117 oidformat_is_temperature(const struct oidformat *of)
120 return (of->format[0] == 'I' && of->format[1] == 'K');
123 /* Returns whether the OID represents a timeval structure. */
125 oidformat_is_timeval(const struct oidformat *of)
128 return (strcmp(of->format, "S,timeval") == 0);
131 /* Fetches the formatting metadata for an OID. */
133 oid_get_format(const struct oid *o, struct oidformat *of)
135 int lookup[CTL_MAXNAME + 2];
138 lookup[0] = CTL_SYSCTL;
139 lookup[1] = CTL_SYSCTL_OIDFMT;
140 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0]));
142 if (sysctl(lookup, 2 + o->len, of, &oflen, 0, 0) != 0) {
145 err(1, "sysctl(oidfmt)");
151 * Container for holding the value of an OID.
154 enum { SIGNED, UNSIGNED, FLOAT } type;
162 /* Extracts the value of an OID, converting it to a floating-point number. */
164 oidvalue_get_float(const struct oidvalue *ov)
169 return (ov->value.s);
171 return (ov->value.u);
173 return (ov->value.f);
175 assert(0 && "Unknown value type");
179 /* Sets the value of an OID as a signed integer. */
181 oidvalue_set_signed(struct oidvalue *ov, intmax_t s)
188 /* Sets the value of an OID as an unsigned integer. */
190 oidvalue_set_unsigned(struct oidvalue *ov, uintmax_t u)
197 /* Sets the value of an OID as a floating-point number. */
199 oidvalue_set_float(struct oidvalue *ov, double f)
206 /* Prints the value of an OID to a file stream. */
208 oidvalue_print(const struct oidvalue *ov, FILE *fp)
213 fprintf(fp, "%jd", ov->value.s);
216 fprintf(fp, "%ju", ov->value.u);
219 switch (fpclassify(ov->value.f)) {
221 if (signbit(ov->value.f))
230 fprintf(fp, "%.6f", ov->value.f);
237 /* Fetches the value of an OID. */
239 oid_get_value(const struct oid *o, const struct oidformat *of,
243 switch (of->kind & CTLTYPE) {
244 #define GET_VALUE(ctltype, type) \
249 valuesize = sizeof(value); \
250 if (sysctl(o->id, o->len, &value, &valuesize, 0, 0) != 0) \
253 oidvalue_set_unsigned(ov, value); \
255 oidvalue_set_signed(ov, value); \
258 GET_VALUE(CTLTYPE_INT, int);
259 GET_VALUE(CTLTYPE_UINT, unsigned int);
260 GET_VALUE(CTLTYPE_LONG, long);
261 GET_VALUE(CTLTYPE_ULONG, unsigned long);
262 GET_VALUE(CTLTYPE_S8, int8_t);
263 GET_VALUE(CTLTYPE_U8, uint8_t);
264 GET_VALUE(CTLTYPE_S16, int16_t);
265 GET_VALUE(CTLTYPE_U16, uint16_t);
266 GET_VALUE(CTLTYPE_S32, int32_t);
267 GET_VALUE(CTLTYPE_U32, uint32_t);
268 GET_VALUE(CTLTYPE_S64, int64_t);
269 GET_VALUE(CTLTYPE_U64, uint64_t);
272 if (oidformat_is_timeval(of)) {
277 if (sysctl(o->id, o->len, &tv, &tvsize, 0, 0) != 0)
279 oidvalue_set_float(ov,
280 (double)tv.tv_sec + (double)tv.tv_usec / 1000000);
282 } else if (strcmp(of->format, "S,loadavg") == 0) {
287 * Only return the one minute load average, as
288 * the others can be inferred using avg_over_time().
291 if (sysctl(o->id, o->len, &la, &lasize, 0, 0) != 0)
293 oidvalue_set_float(ov,
294 (double)la.ldavg[0] / (double)la.fscale);
302 /* Convert temperatures from decikelvin to degrees Celsius. */
303 if (oidformat_is_temperature(of)) {
307 v = oidvalue_get_float(ov);
309 oidvalue_set_float(ov, NAN);
311 e = of->format[2] >= '0' && of->format[2] <= '9' ?
312 of->format[2] - '0' : 1;
313 oidvalue_set_float(ov, v / pow(10, e) - 273.15);
320 * The full name of an OID, stored as a series of components.
329 * Initializes the OID name object with an empty value.
332 oidname_init(struct oidname *on)
338 /* Fetches the name and labels of an OID, reusing the previous results. */
340 oid_get_name(const struct oid *o, struct oidname *on)
342 int lookup[CTL_MAXNAME + 2];
346 /* Fetch the name and split it up in separate components. */
347 lookup[0] = CTL_SYSCTL;
348 lookup[1] = CTL_SYSCTL_NAME;
349 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0]));
350 len = sizeof(on->names);
351 if (sysctl(lookup, 2 + o->len, on->names, &len, 0, 0) != 0)
352 err(1, "sysctl(name)");
353 for (c = strchr(on->names, '.'); c != NULL; c = strchr(c + 1, '.'))
356 /* No need to fetch labels for components that we already have. */
358 for (i = 0; i < o->len && i < on->oid.len && o->id[i] == on->oid.id[i];
360 label += strlen(label) + 1;
362 /* Fetch the remaining labels. */
364 for (; i < o->len; ++i) {
365 len = on->labels + sizeof(on->labels) - label;
366 if (sysctl(lookup, 2 + i + 1, label, &len, 0, 0) == 0) {
368 } else if (errno == ENOENT) {
371 err(1, "sysctl(oidlabel)");
377 /* Populates the name and labels of an OID to a buffer. */
379 oid_get_metric(const struct oidname *on, const struct oidformat *of,
380 char *metric, size_t mlen)
382 const char *name, *label;
384 char separator, buf[BUFSIZ];
386 /* Print the name of the metric. */
387 snprintf(metric, mlen, "%s", "sysctl");
390 for (i = 0; i < on->oid.len; ++i) {
391 if (*label == '\0') {
392 strlcat(metric, "_", mlen);
393 while (*name != '\0') {
394 /* Map unsupported characters to underscores. */
395 snprintf(buf, sizeof(buf), "%c",
396 isalnum(*name) ? *name : '_');
397 strlcat(metric, buf, mlen);
401 name += strlen(name) + 1;
402 label += strlen(label) + 1;
404 if (oidformat_is_temperature(of))
405 strlcat(metric, "_celsius", mlen);
406 else if (oidformat_is_timeval(of))
407 strlcat(metric, "_seconds", mlen);
409 /* Print the labels of the metric. */
413 for (i = 0; i < on->oid.len; ++i) {
414 if (*label != '\0') {
415 assert(label[strspn(label,
416 "abcdefghijklmnopqrstuvwxyz"
417 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
418 "0123456789_")] == '\0');
419 snprintf(buf, sizeof(buf), "%c%s=\"", separator, label);
420 strlcat(metric, buf, mlen);
421 while (*name != '\0') {
422 /* Escape backslashes and double quotes. */
423 if (*name == '\\' || *name == '"')
424 strlcat(metric, "\\", mlen);
425 snprintf(buf, sizeof(buf), "%c", *name++);
426 strlcat(metric, buf, mlen);
428 strlcat(metric, "\"", mlen);
431 name += strlen(name) + 1;
432 label += strlen(label) + 1;
434 if (separator != '{')
435 strlcat(metric, "}", mlen);
438 /* Returns whether the OID name has any labels associated to it. */
440 oidname_has_labels(const struct oidname *on)
444 for (i = 0; i < on->oid.len; ++i)
445 if (on->labels[i] != 0)
451 * The description of an OID.
453 struct oiddescription {
454 char description[BUFSIZ];
458 * Fetches the description of an OID.
461 oid_get_description(const struct oid *o, struct oiddescription *od)
463 int lookup[CTL_MAXNAME + 2];
467 lookup[0] = CTL_SYSCTL;
468 lookup[1] = CTL_SYSCTL_OIDDESCR;
469 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0]));
470 odlen = sizeof(od->description);
471 if (sysctl(lookup, 2 + o->len, &od->description, &odlen, 0, 0) != 0) {
474 err(1, "sysctl(oiddescr)");
477 newline = strchr(od->description, '\n');
481 return (*od->description != '\0');
484 /* Prints the description of an OID to a file stream. */
486 oiddescription_print(const struct oiddescription *od, FILE *fp)
489 fprintf(fp, "%s", od->description);
493 oid_print(const struct oid *o, struct oidname *on, bool print_description,
494 bool exclude, bool include, FILE *fp)
498 struct oiddescription od;
502 if (!oid_get_format(o, &of) || !oid_get_value(o, &of, &ov))
506 oid_get_metric(on, &of, metric, sizeof(metric));
508 if (exclude && regexec(&exc_regex, metric, 0, NULL, 0) == 0)
511 if (include && regexec(&inc_regex, metric, 0, NULL, 0) != 0)
514 has_desc = oid_get_description(o, &od);
516 * Skip metrics with "(LEGACY)" in the name. It's used by several
517 * redundant ZFS sysctls whose names alias with the non-legacy versions.
519 if (has_desc && strnstr(od.description, "(LEGACY)", BUFSIZ) != NULL)
522 * Print the line with the description. Prometheus expects a
523 * single unique description for every metric, which cannot be
524 * guaranteed by sysctl if labels are present. Omit the
525 * description if labels are present.
527 if (print_description && !oidname_has_labels(on) && has_desc) {
528 fprintf(fp, "# HELP ");
529 fprintf(fp, "%s", metric);
531 oiddescription_print(&od, fp);
535 /* Print the line with the value. */
536 fprintf(fp, "%s", metric);
538 oidvalue_print(&ov, fp);
542 /* Gzip compresses a buffer of memory. */
544 buf_gzip(const char *in, size_t inlen, char *out, size_t *outlen)
547 .next_in = __DECONST(unsigned char *, in),
549 .next_out = (unsigned char *)out,
550 .avail_out = *outlen,
553 if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
554 MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK ||
555 deflate(&stream, Z_FINISH) != Z_STREAM_END) {
558 *outlen = stream.total_out;
559 return (deflateEnd(&stream) == Z_OK);
566 fprintf(stderr, "%s",
567 "usage: prometheus_sysctl_exporter [-dgh] [-e pattern] [-i pattern]\n"
573 main(int argc, char *argv[])
580 bool exclude, include, gzip_mode, http_mode, print_descriptions;
583 /* Parse command line flags. */
584 include = exclude = gzip_mode = http_mode = print_descriptions = false;
585 while ((ch = getopt(argc, argv, "de:ghi:")) != -1) {
588 print_descriptions = true;
591 error = regcomp(&exc_regex, optarg, REG_EXTENDED);
593 regerror(error, &exc_regex, errbuf, sizeof(errbuf));
594 errx(1, "bad regular expression '%s': %s",
606 error = regcomp(&inc_regex, optarg, REG_EXTENDED);
608 regerror(error, &inc_regex, errbuf, sizeof(errbuf));
609 errx(1, "bad regular expression '%s': %s",
621 /* HTTP output: cache metrics in buffer. */
623 fp = open_memstream(&http_buf, &http_buflen);
625 err(1, "open_memstream");
634 /* Print all OIDs. */
637 oid_print(&o, &on, print_descriptions, exclude, include, fp);
638 } while (oid_get_next(&o, &o));
642 /* Print only trees provided as arguments. */
643 for (i = 0; i < argc; ++i) {
646 if (!oid_get_by_name(&root, argv[i])) {
648 * Ignore trees provided as arguments that
649 * can't be found. They might belong, for
650 * example, to kernel modules not currently
657 oid_print(&o, &on, print_descriptions, exclude, include, fp);
658 } while (oid_get_next(&o, &o) &&
659 oid_is_beneath(&o, &root));
664 const char *content_encoding = "";
666 if (ferror(fp) || fclose(fp) != 0)
667 err(1, "Cannot generate output");
669 /* Gzip compress the output. */
674 buflen = http_buflen;
675 buf = malloc(buflen);
677 err(1, "Cannot allocate compression buffer");
678 if (buf_gzip(http_buf, http_buflen, buf, &buflen)) {
679 content_encoding = "Content-Encoding: gzip\r\n";
682 http_buflen = buflen;
688 /* Print HTTP header and metrics. */
689 dprintf(STDOUT_FILENO,
690 "HTTP/1.1 200 OK\r\n"
691 "Connection: close\r\n"
693 "Content-Length: %zu\r\n"
694 "Content-Type: text/plain; version=0.0.4\r\n"
696 content_encoding, http_buflen);
697 write(STDOUT_FILENO, http_buf, http_buflen);
701 if (shutdown(STDIN_FILENO, SHUT_WR) == 0) {
704 while (read(STDIN_FILENO, buf, sizeof(buf)) > 0) {