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
oid_get_root(struct oid * o)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
oid_get_by_name(struct oid * o,const char * name)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
oid_is_beneath(struct oid * oa,struct oid * ob)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
oid_get_next(const struct oid * cur,struct oid * next)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
oidformat_is_temperature(const struct oidformat * of)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
oidformat_is_timeval(const struct oidformat * of)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
oid_get_format(const struct oid * o,struct oidformat * of)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
oidvalue_get_float(const struct oidvalue * ov)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
oidvalue_set_signed(struct oidvalue * ov,intmax_t s)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
oidvalue_set_unsigned(struct oidvalue * ov,uintmax_t u)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
oidvalue_set_float(struct oidvalue * ov,double f)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
oidvalue_print(const struct oidvalue * ov,FILE * fp)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
oid_get_value(const struct oid * o,const struct oidformat * of,struct oidvalue * ov)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
oidname_init(struct oidname * on)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
oid_get_name(const struct oid * o,struct oidname * on)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
oidname_print(const struct oidname * on,const struct oidformat * of,FILE * fp)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
oidname_has_labels(const struct oidname * on)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
oid_get_description(const struct oid * o,struct oiddescription * od)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
oiddescription_print(const struct oiddescription * od,FILE * fp)478 oiddescription_print(const struct oiddescription *od, FILE *fp)
479 {
480
481 fprintf(fp, "%s", od->description);
482 }
483
484 static void
oid_print(const struct oid * o,struct oidname * on,bool print_description,FILE * fp)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
buf_gzip(const char * in,size_t inlen,char * out,size_t * outlen)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
usage(void)539 usage(void)
540 {
541
542 fprintf(stderr,
543 "usage: prometheus_sysctl_exporter [-dgh] [prefix ...]\n");
544 exit(1);
545 }
546
547 int
main(int argc,char * argv[])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