2 * Copyright (c) 2016 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>
47 * Cursor for iterating over all of the system's sysctl OIDs.
54 /* Initializes the cursor to point to start of the tree. */
56 oid_get_root(struct oid *o)
63 /* Obtains the OID for a sysctl by name. */
65 oid_get_by_name(struct oid *o, const char *name)
68 o->len = nitems(o->id);
69 if (sysctlnametomib(name, o->id, &o->len) != 0)
70 err(1, "sysctl(%s)", name);
73 /* Returns whether an OID is placed below another OID. */
75 oid_is_beneath(struct oid *oa, struct oid *ob)
78 return (oa->len >= ob->len &&
79 memcmp(oa->id, ob->id, ob->len * sizeof(oa->id[0])) == 0);
82 /* Advances the cursor to the next OID. */
84 oid_get_next(const struct oid *cur, struct oid *next)
86 int lookup[CTL_MAXNAME + 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) {
96 err(1, "sysctl(next)");
98 next->len = nextsize / sizeof(next->id[0]);
103 * OID formatting metadata.
110 /* Returns whether the OID represents a temperature value. */
112 oidformat_is_temperature(const struct oidformat *of)
115 return (of->format[0] == 'I' && of->format[1] == 'K');
118 /* Returns whether the OID represents a timeval structure. */
120 oidformat_is_timeval(const struct oidformat *of)
123 return (strcmp(of->format, "S,timeval") == 0);
126 /* Fetches the formatting metadata for an OID. */
128 oid_get_format(const struct oid *o, struct oidformat *of)
130 int lookup[CTL_MAXNAME + 2];
135 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0]));
137 if (sysctl(lookup, 2 + o->len, of, &oflen, 0, 0) != 0) {
140 err(1, "sysctl(oidfmt)");
146 * Container for holding the value of an OID.
149 enum { SIGNED, UNSIGNED, FLOAT } type;
157 /* Extracts the value of an OID, converting it to a floating-point number. */
159 oidvalue_get_float(const struct oidvalue *ov)
164 return (ov->value.s);
166 return (ov->value.u);
168 return (ov->value.f);
170 assert(0 && "Unknown value type");
174 /* Sets the value of an OID as a signed integer. */
176 oidvalue_set_signed(struct oidvalue *ov, intmax_t s)
183 /* Sets the value of an OID as an unsigned integer. */
185 oidvalue_set_unsigned(struct oidvalue *ov, uintmax_t u)
192 /* Sets the value of an OID as a floating-point number. */
194 oidvalue_set_float(struct oidvalue *ov, double f)
201 /* Prints the value of an OID to a file stream. */
203 oidvalue_print(const struct oidvalue *ov, FILE *fp)
208 fprintf(fp, "%jd", ov->value.s);
211 fprintf(fp, "%ju", ov->value.u);
214 switch (fpclassify(ov->value.f)) {
216 if (signbit(ov->value.f))
225 fprintf(fp, "%.6f", ov->value.f);
232 /* Fetches the value of an OID. */
234 oid_get_value(const struct oid *o, const struct oidformat *of,
238 switch (of->kind & CTLTYPE) {
239 #define GET_VALUE(ctltype, type) \
244 valuesize = sizeof(value); \
245 if (sysctl(o->id, o->len, &value, &valuesize, 0, 0) != 0) \
248 oidvalue_set_unsigned(ov, value); \
250 oidvalue_set_signed(ov, value); \
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);
267 if (oidformat_is_timeval(of)) {
272 if (sysctl(o->id, o->len, &tv, &tvsize, 0, 0) != 0)
274 oidvalue_set_float(ov,
275 (double)tv.tv_sec + (double)tv.tv_usec / 1000000);
277 } else if (strcmp(of->format, "S,loadavg") == 0) {
282 * Only return the one minute load average, as
283 * the others can be inferred using avg_over_time().
286 if (sysctl(o->id, o->len, &la, &lasize, 0, 0) != 0)
288 oidvalue_set_float(ov,
289 (double)la.ldavg[0] / (double)la.fscale);
297 /* Convert temperatures from decikelvin to degrees Celcius. */
298 if (oidformat_is_temperature(of)) {
302 v = oidvalue_get_float(ov);
304 oidvalue_set_float(ov, NAN);
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);
315 * The full name of an OID, stored as a series of components.
324 * Initializes the OID name object with an empty value.
327 oidname_init(struct oidname *on)
333 /* Fetches the name and labels of an OID, reusing the previous results. */
335 oid_get_name(const struct oid *o, struct oidname *on)
337 int lookup[CTL_MAXNAME + 2];
341 /* Fetch the name and split it up in separate components. */
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, '.'))
351 /* No need to fetch labels for components that we already have. */
353 for (i = 0; i < o->len && i < on->oid.len && o->id[i] == on->oid.id[i];
355 label += strlen(label) + 1;
357 /* Fetch the remaining labels. */
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) {
363 } else if (errno == ENOENT) {
366 err(1, "sysctl(oidlabel)");
372 /* Prints the name and labels of an OID to a file stream. */
374 oidname_print(const struct oidname *on, const struct oidformat *of,
377 const char *name, *label;
381 /* Print the name of the metric. */
382 fprintf(fp, "sysctl");
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);
393 name += strlen(name) + 1;
394 label += strlen(label) + 1;
396 if (oidformat_is_temperature(of))
397 fprintf(fp, "_celcius");
398 else if (oidformat_is_timeval(of))
399 fprintf(fp, "_seconds");
401 /* Print the labels of the metric. */
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);
418 name += strlen(name) + 1;
419 label += strlen(label) + 1;
421 if (separator != '{')
425 /* Returns whether the OID name has any labels associated to it. */
427 oidname_has_labels(const struct oidname *on)
431 for (i = 0; i < on->oid.len; ++i)
432 if (on->labels[i] != 0)
438 * The description of an OID.
440 struct oiddescription {
441 char description[BUFSIZ];
445 * Fetches the description of an OID.
448 oid_get_description(const struct oid *o, struct oiddescription *od)
450 int lookup[CTL_MAXNAME + 2];
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) {
461 err(1, "sysctl(oiddescr)");
464 newline = strchr(od->description, '\n');
468 return (*od->description != '\0');
471 /* Prints the description of an OID to a file stream. */
473 oiddescription_print(const struct oiddescription *od, FILE *fp)
476 fprintf(fp, "%s", od->description);
480 oid_print(const struct oid *o, struct oidname *on, bool print_description,
485 struct oiddescription od;
487 if (!oid_get_format(o, &of) || !oid_get_value(o, &of, &ov))
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.
497 if (print_description && !oidname_has_labels(on) &&
498 oid_get_description(o, &od)) {
499 fprintf(fp, "# HELP ");
500 oidname_print(on, &of, fp);
502 oiddescription_print(&od, fp);
506 /* Print the line with the value. */
507 oidname_print(on, &of, fp);
509 oidvalue_print(&ov, fp);
513 /* Gzip compresses a buffer of memory. */
515 buf_gzip(const char *in, size_t inlen, char *out, size_t *outlen)
518 .next_in = __DECONST(unsigned char *, in),
520 .next_out = (unsigned char *)out,
521 .avail_out = *outlen,
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) {
529 *outlen = stream.total_out;
530 return (deflateEnd(&stream) == Z_OK);
538 "usage: prometheus_sysctl_exporter [-dgh] [prefix ...]\n");
543 main(int argc, char *argv[])
550 bool gzip_mode, http_mode, print_descriptions;
552 /* Parse command line flags. */
553 gzip_mode = http_mode = print_descriptions = false;
554 while ((ch = getopt(argc, argv, "dgh")) != -1) {
557 print_descriptions = true;
572 /* HTTP output: cache metrics in buffer. */
574 fp = open_memstream(&http_buf, &http_buflen);
576 err(1, "open_memstream");
585 /* Print all OIDs. */
588 oid_print(&o, &on, print_descriptions, fp);
589 } while (oid_get_next(&o, &o));
593 /* Print only trees provided as arguments. */
594 for (i = 0; i < argc; ++i) {
597 oid_get_by_name(&root, argv[i]);
600 oid_print(&o, &on, print_descriptions, fp);
601 } while (oid_get_next(&o, &o) &&
602 oid_is_beneath(&o, &root));
607 const char *content_encoding = "";
609 if (ferror(fp) || fclose(fp) != 0)
610 err(1, "Cannot generate output");
612 /* Gzip compress the output. */
617 buflen = http_buflen;
618 buf = malloc(buflen);
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";
625 http_buflen = buflen;
631 /* Print HTTP header and metrics. */
632 dprintf(STDOUT_FILENO,
633 "HTTP/1.1 200 OK\r\n"
634 "Connection: close\r\n"
636 "Content-Length: %zu\r\n"
637 "Content-Type: text/plain; version=0.0.4\r\n"
639 content_encoding, http_buflen);
640 write(STDOUT_FILENO, http_buf, http_buflen);
644 if (shutdown(STDIN_FILENO, SHUT_WR) == 0) {
647 while (read(STDIN_FILENO, buf, sizeof(buf)) > 0) {