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 * Cursor for iterating over all of the system's sysctl OIDs.
55 /* Initializes the cursor to point to start of the tree. */
57 oid_get_root(struct oid *o)
64 /* Obtains the OID for a sysctl by name. */
66 oid_get_by_name(struct oid *o, const char *name)
69 o->len = nitems(o->id);
70 if (sysctlnametomib(name, o->id, &o->len) != 0)
71 err(1, "sysctl(%s)", name);
74 /* Returns whether an OID is placed below another OID. */
76 oid_is_beneath(struct oid *oa, struct oid *ob)
79 return (oa->len >= ob->len &&
80 memcmp(oa->id, ob->id, ob->len * sizeof(oa->id[0])) == 0);
83 /* Advances the cursor to the next OID. */
85 oid_get_next(const struct oid *cur, struct oid *next)
87 int lookup[CTL_MAXNAME + 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) {
97 err(1, "sysctl(next)");
99 next->len = nextsize / sizeof(next->id[0]);
104 * OID formatting metadata.
111 /* Returns whether the OID represents a temperature value. */
113 oidformat_is_temperature(const struct oidformat *of)
116 return (of->format[0] == 'I' && of->format[1] == 'K');
119 /* Returns whether the OID represents a timeval structure. */
121 oidformat_is_timeval(const struct oidformat *of)
124 return (strcmp(of->format, "S,timeval") == 0);
127 /* Fetches the formatting metadata for an OID. */
129 oid_get_format(const struct oid *o, struct oidformat *of)
131 int lookup[CTL_MAXNAME + 2];
136 memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0]));
138 if (sysctl(lookup, 2 + o->len, of, &oflen, 0, 0) != 0) {
141 err(1, "sysctl(oidfmt)");
147 * Container for holding the value of an OID.
150 enum { SIGNED, UNSIGNED, FLOAT } type;
158 /* Extracts the value of an OID, converting it to a floating-point number. */
160 oidvalue_get_float(const struct oidvalue *ov)
165 return (ov->value.s);
167 return (ov->value.u);
169 return (ov->value.f);
171 assert(0 && "Unknown value type");
175 /* Sets the value of an OID as a signed integer. */
177 oidvalue_set_signed(struct oidvalue *ov, intmax_t s)
184 /* Sets the value of an OID as an unsigned integer. */
186 oidvalue_set_unsigned(struct oidvalue *ov, uintmax_t u)
193 /* Sets the value of an OID as a floating-point number. */
195 oidvalue_set_float(struct oidvalue *ov, double f)
202 /* Prints the value of an OID to a file stream. */
204 oidvalue_print(const struct oidvalue *ov, FILE *fp)
209 fprintf(fp, "%jd", ov->value.s);
212 fprintf(fp, "%ju", ov->value.u);
215 switch (fpclassify(ov->value.f)) {
217 if (signbit(ov->value.f))
226 fprintf(fp, "%.6f", ov->value.f);
233 /* Fetches the value of an OID. */
235 oid_get_value(const struct oid *o, const struct oidformat *of,
239 switch (of->kind & CTLTYPE) {
240 #define GET_VALUE(ctltype, type) \
245 valuesize = sizeof(value); \
246 if (sysctl(o->id, o->len, &value, &valuesize, 0, 0) != 0) \
249 oidvalue_set_unsigned(ov, value); \
251 oidvalue_set_signed(ov, value); \
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);
268 if (oidformat_is_timeval(of)) {
273 if (sysctl(o->id, o->len, &tv, &tvsize, 0, 0) != 0)
275 oidvalue_set_float(ov,
276 (double)tv.tv_sec + (double)tv.tv_usec / 1000000);
278 } else if (strcmp(of->format, "S,loadavg") == 0) {
283 * Only return the one minute load average, as
284 * the others can be inferred using avg_over_time().
287 if (sysctl(o->id, o->len, &la, &lasize, 0, 0) != 0)
289 oidvalue_set_float(ov,
290 (double)la.ldavg[0] / (double)la.fscale);
298 /* Convert temperatures from decikelvin to degrees Celcius. */
299 if (oidformat_is_temperature(of)) {
303 v = oidvalue_get_float(ov);
305 oidvalue_set_float(ov, NAN);
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);
316 * The full name of an OID, stored as a series of components.
325 * Initializes the OID name object with an empty value.
328 oidname_init(struct oidname *on)
334 /* Fetches the name and labels of an OID, reusing the previous results. */
336 oid_get_name(const struct oid *o, struct oidname *on)
338 int lookup[CTL_MAXNAME + 2];
342 /* Fetch the name and split it up in separate components. */
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, '.'))
352 /* No need to fetch labels for components that we already have. */
354 for (i = 0; i < o->len && i < on->oid.len && o->id[i] == on->oid.id[i];
356 label += strlen(label) + 1;
358 /* Fetch the remaining labels. */
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) {
364 } else if (errno == ENOENT) {
367 err(1, "sysctl(oidlabel)");
373 /* Prints the name and labels of an OID to a file stream. */
375 oidname_print(const struct oidname *on, const struct oidformat *of,
378 const char *name, *label;
382 /* Print the name of the metric. */
383 fprintf(fp, "sysctl");
386 for (i = 0; i < on->oid.len; ++i) {
387 if (*label == '\0') {
389 while (*name != '\0') {
390 /* Map unsupported characters to underscores. */
391 fputc(isalnum(*name) ? *name : '_', fp);
395 name += strlen(name) + 1;
396 label += strlen(label) + 1;
398 if (oidformat_is_temperature(of))
399 fprintf(fp, "_celcius");
400 else if (oidformat_is_timeval(of))
401 fprintf(fp, "_seconds");
403 /* Print the labels of the metric. */
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 == '"')
423 name += strlen(name) + 1;
424 label += strlen(label) + 1;
426 if (separator != '{')
430 /* Returns whether the OID name has any labels associated to it. */
432 oidname_has_labels(const struct oidname *on)
436 for (i = 0; i < on->oid.len; ++i)
437 if (on->labels[i] != 0)
443 * The description of an OID.
445 struct oiddescription {
446 char description[BUFSIZ];
450 * Fetches the description of an OID.
453 oid_get_description(const struct oid *o, struct oiddescription *od)
455 int lookup[CTL_MAXNAME + 2];
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) {
466 err(1, "sysctl(oiddescr)");
469 newline = strchr(od->description, '\n');
473 return (*od->description != '\0');
476 /* Prints the description of an OID to a file stream. */
478 oiddescription_print(const struct oiddescription *od, FILE *fp)
481 fprintf(fp, "%s", od->description);
485 oid_print(const struct oid *o, struct oidname *on, bool print_description,
490 struct oiddescription od;
492 if (!oid_get_format(o, &of) || !oid_get_value(o, &of, &ov))
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.
502 if (print_description && !oidname_has_labels(on) &&
503 oid_get_description(o, &od)) {
504 fprintf(fp, "# HELP ");
505 oidname_print(on, &of, fp);
507 oiddescription_print(&od, fp);
511 /* Print the line with the value. */
512 oidname_print(on, &of, fp);
514 oidvalue_print(&ov, fp);
518 /* Gzip compresses a buffer of memory. */
520 buf_gzip(const char *in, size_t inlen, char *out, size_t *outlen)
523 .next_in = __DECONST(unsigned char *, in),
525 .next_out = (unsigned char *)out,
526 .avail_out = *outlen,
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) {
534 *outlen = stream.total_out;
535 return (deflateEnd(&stream) == Z_OK);
543 "usage: prometheus_sysctl_exporter [-dgh] [prefix ...]\n");
548 main(int argc, char *argv[])
555 bool gzip_mode, http_mode, print_descriptions;
557 /* Parse command line flags. */
558 gzip_mode = http_mode = print_descriptions = false;
559 while ((ch = getopt(argc, argv, "dgh")) != -1) {
562 print_descriptions = true;
577 /* HTTP output: cache metrics in buffer. */
579 fp = open_memstream(&http_buf, &http_buflen);
581 err(1, "open_memstream");
590 /* Print all OIDs. */
593 oid_print(&o, &on, print_descriptions, fp);
594 } while (oid_get_next(&o, &o));
598 /* Print only trees provided as arguments. */
599 for (i = 0; i < argc; ++i) {
602 oid_get_by_name(&root, argv[i]);
605 oid_print(&o, &on, print_descriptions, fp);
606 } while (oid_get_next(&o, &o) &&
607 oid_is_beneath(&o, &root));
612 const char *content_encoding = "";
614 if (ferror(fp) || fclose(fp) != 0)
615 err(1, "Cannot generate output");
617 /* Gzip compress the output. */
622 buflen = http_buflen;
623 buf = malloc(buflen);
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";
630 http_buflen = buflen;
636 /* Print HTTP header and metrics. */
637 dprintf(STDOUT_FILENO,
638 "HTTP/1.1 200 OK\r\n"
639 "Connection: close\r\n"
641 "Content-Length: %zu\r\n"
642 "Content-Type: text/plain; version=0.0.4\r\n"
644 content_encoding, http_buflen);
645 write(STDOUT_FILENO, http_buf, http_buflen);
649 if (shutdown(STDIN_FILENO, SHUT_WR) == 0) {
652 while (read(STDIN_FILENO, buf, sizeof(buf)) > 0) {