]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.c
libarchive: merge from vendor branch
[FreeBSD/FreeBSD.git] / usr.sbin / prometheus_sysctl_exporter / prometheus_sysctl_exporter.c
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 <regex.h>
40 #include <stdbool.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46 #include <zlib.h>
47
48 /* Regular expressions for filtering output. */
49 static regex_t inc_regex;
50 static regex_t exc_regex;
51
52 /*
53  * Cursor for iterating over all of the system's sysctl OIDs.
54  */
55 struct oid {
56         int     id[CTL_MAXNAME];
57         size_t  len;
58 };
59
60 /* Initializes the cursor to point to start of the tree. */
61 static void
62 oid_get_root(struct oid *o)
63 {
64
65         o->id[0] = CTL_KERN;
66         o->len = 1;
67 }
68
69 /* Obtains the OID for a sysctl by name. */
70 static bool
71 oid_get_by_name(struct oid *o, const char *name)
72 {
73
74         o->len = nitems(o->id);
75         return (sysctlnametomib(name, o->id, &o->len) == 0);
76 }
77
78 /* Returns whether an OID is placed below another OID. */
79 static bool
80 oid_is_beneath(struct oid *oa, struct oid *ob)
81 {
82
83         return (oa->len >= ob->len &&
84             memcmp(oa->id, ob->id, ob->len * sizeof(oa->id[0])) == 0);
85 }
86
87 /* Advances the cursor to the next OID. */
88 static bool
89 oid_get_next(const struct oid *cur, struct oid *next)
90 {
91         int lookup[CTL_MAXNAME + 2];
92         size_t nextsize;
93
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) {
99                 if (errno == ENOENT)
100                         return (false);
101                 err(1, "sysctl(next)");
102         }
103         next->len = nextsize / sizeof(next->id[0]);
104         return (true);
105 }
106
107 /*
108  * OID formatting metadata.
109  */
110 struct oidformat {
111         unsigned int    kind;
112         char            format[BUFSIZ];
113 };
114
115 /* Returns whether the OID represents a temperature value. */
116 static bool
117 oidformat_is_temperature(const struct oidformat *of)
118 {
119
120         return (of->format[0] == 'I' && of->format[1] == 'K');
121 }
122
123 /* Returns whether the OID represents a timeval structure. */
124 static bool
125 oidformat_is_timeval(const struct oidformat *of)
126 {
127
128         return (strcmp(of->format, "S,timeval") == 0);
129 }
130
131 /* Fetches the formatting metadata for an OID. */
132 static bool
133 oid_get_format(const struct oid *o, struct oidformat *of)
134 {
135         int lookup[CTL_MAXNAME + 2];
136         size_t oflen;
137
138         lookup[0] = CTL_SYSCTL;
139         lookup[1] = CTL_SYSCTL_OIDFMT;
140         memcpy(lookup + 2, o->id, o->len * sizeof(lookup[0]));
141         oflen = sizeof(*of);
142         if (sysctl(lookup, 2 + o->len, of, &oflen, 0, 0) != 0) {
143                 if (errno == ENOENT)
144                         return (false);
145                 err(1, "sysctl(oidfmt)");
146         }
147         return (true);
148 }
149
150 /*
151  * Container for holding the value of an OID.
152  */
153 struct oidvalue {
154         enum { SIGNED, UNSIGNED, FLOAT } type;
155         union {
156                 intmax_t        s;
157                 uintmax_t       u;
158                 double          f;
159         } value;
160 };
161
162 /* Extracts the value of an OID, converting it to a floating-point number. */
163 static double
164 oidvalue_get_float(const struct oidvalue *ov)
165 {
166
167         switch (ov->type) {
168         case SIGNED:
169                 return (ov->value.s);
170         case UNSIGNED:
171                 return (ov->value.u);
172         case FLOAT:
173                 return (ov->value.f);
174         default:
175                 assert(0 && "Unknown value type");
176         }
177 }
178
179 /* Sets the value of an OID as a signed integer. */
180 static void
181 oidvalue_set_signed(struct oidvalue *ov, intmax_t s)
182 {
183
184         ov->type = SIGNED;
185         ov->value.s = s;
186 }
187
188 /* Sets the value of an OID as an unsigned integer. */
189 static void
190 oidvalue_set_unsigned(struct oidvalue *ov, uintmax_t u)
191 {
192
193         ov->type = UNSIGNED;
194         ov->value.u = u;
195 }
196
197 /* Sets the value of an OID as a floating-point number. */
198 static void
199 oidvalue_set_float(struct oidvalue *ov, double f)
200 {
201
202         ov->type = FLOAT;
203         ov->value.f = f;
204 }
205
206 /* Prints the value of an OID to a file stream. */
207 static void
208 oidvalue_print(const struct oidvalue *ov, FILE *fp)
209 {
210
211         switch (ov->type) {
212         case SIGNED:
213                 fprintf(fp, "%jd", ov->value.s);
214                 break;
215         case UNSIGNED:
216                 fprintf(fp, "%ju", ov->value.u);
217                 break;
218         case FLOAT:
219                 switch (fpclassify(ov->value.f)) {
220                 case FP_INFINITE:
221                         if (signbit(ov->value.f))
222                                 fprintf(fp, "-Inf");
223                         else
224                                 fprintf(fp, "+Inf");
225                         break;
226                 case FP_NAN:
227                         fprintf(fp, "Nan");
228                         break;
229                 default:
230                         fprintf(fp, "%.6f", ov->value.f);
231                         break;
232                 }
233                 break;
234         }
235 }
236
237 /* Fetches the value of an OID. */
238 static bool
239 oid_get_value(const struct oid *o, const struct oidformat *of,
240     struct oidvalue *ov)
241 {
242
243         switch (of->kind & CTLTYPE) {
244 #define GET_VALUE(ctltype, type) \
245         case (ctltype): {                                               \
246                 type value;                                             \
247                 size_t valuesize;                                       \
248                                                                         \
249                 valuesize = sizeof(value);                              \
250                 if (sysctl(o->id, o->len, &value, &valuesize, 0, 0) != 0) \
251                         return (false);                                 \
252                 if ((type)-1 > 0)                                       \
253                         oidvalue_set_unsigned(ov, value);               \
254                 else                                                    \
255                         oidvalue_set_signed(ov, value);                 \
256                 break;                                                  \
257         }
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);
270 #undef GET_VALUE
271         case CTLTYPE_OPAQUE:
272                 if (oidformat_is_timeval(of)) {
273                         struct timeval tv;
274                         size_t tvsize;
275
276                         tvsize = sizeof(tv);
277                         if (sysctl(o->id, o->len, &tv, &tvsize, 0, 0) != 0)
278                                 return (false);
279                         oidvalue_set_float(ov,
280                             (double)tv.tv_sec + (double)tv.tv_usec / 1000000);
281                         return (true);
282                 } else if (strcmp(of->format, "S,loadavg") == 0) {
283                         struct loadavg la;
284                         size_t lasize;
285
286                         /*
287                          * Only return the one minute load average, as
288                          * the others can be inferred using avg_over_time().
289                          */
290                         lasize = sizeof(la);
291                         if (sysctl(o->id, o->len, &la, &lasize, 0, 0) != 0)
292                                 return (false);
293                         oidvalue_set_float(ov,
294                             (double)la.ldavg[0] / (double)la.fscale);
295                         return (true);
296                 }
297                 return (false);
298         default:
299                 return (false);
300         }
301
302         /* Convert temperatures from decikelvin to degrees Celsius. */
303         if (oidformat_is_temperature(of)) {
304                 double v;
305                 int e;
306
307                 v = oidvalue_get_float(ov);
308                 if (v < 0) {
309                         oidvalue_set_float(ov, NAN);
310                 } else {
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);
314                 }
315         }
316         return (true);
317 }
318
319 /*
320  * The full name of an OID, stored as a series of components.
321  */
322 struct oidname {
323         struct oid      oid;
324         char            names[BUFSIZ];
325         char            labels[BUFSIZ];
326 };
327
328 /*
329  * Initializes the OID name object with an empty value.
330  */
331 static void
332 oidname_init(struct oidname *on)
333 {
334
335         on->oid.len = 0;
336 }
337
338 /* Fetches the name and labels of an OID, reusing the previous results. */
339 static void
340 oid_get_name(const struct oid *o, struct oidname *on)
341 {
342         int lookup[CTL_MAXNAME + 2];
343         char *c, *label;
344         size_t i, len;
345
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, '.'))
354                 *c = '\0';
355
356         /* No need to fetch labels for components that we already have. */
357         label = on->labels;
358         for (i = 0; i < o->len && i < on->oid.len && o->id[i] == on->oid.id[i];
359             ++i)
360                 label += strlen(label) + 1;
361
362         /* Fetch the remaining labels. */
363         lookup[1] = 6;
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) {
367                         label += len;
368                 } else if (errno == ENOENT) {
369                         *label++ = '\0';
370                 } else {
371                         err(1, "sysctl(oidlabel)");
372                 }
373         }
374         on->oid = *o;
375 }
376
377 /* Populates the name and labels of an OID to a buffer. */
378 static void
379 oid_get_metric(const struct oidname *on, const struct oidformat *of,
380     char *metric, size_t mlen)
381 {
382         const char *name, *label;
383         size_t i;
384         char separator, buf[BUFSIZ];
385
386         /* Print the name of the metric. */
387         snprintf(metric, mlen, "%s", "sysctl");
388         name = on->names;
389         label = on->labels;
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);
398                                 ++name;
399                         }
400                 }
401                 name += strlen(name) + 1;
402                 label += strlen(label) + 1;
403         }
404         if (oidformat_is_temperature(of))
405                 strlcat(metric, "_celsius", mlen);
406         else if (oidformat_is_timeval(of))
407                 strlcat(metric, "_seconds", mlen);
408
409         /* Print the labels of the metric. */
410         name = on->names;
411         label = on->labels;
412         separator = '{';
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);
427                         }
428                         strlcat(metric, "\"", mlen);
429                         separator = ',';
430                 }
431                 name += strlen(name) + 1;
432                 label += strlen(label) + 1;
433         }
434         if (separator != '{')
435                 strlcat(metric, "}", mlen);
436 }
437
438 /* Returns whether the OID name has any labels associated to it. */
439 static bool
440 oidname_has_labels(const struct oidname *on)
441 {
442         size_t i;
443
444         for (i = 0; i < on->oid.len; ++i)
445                 if (on->labels[i] != 0)
446                         return (true);
447         return (false);
448 }
449
450 /*
451  * The description of an OID.
452  */
453 struct oiddescription {
454         char description[BUFSIZ];
455 };
456
457 /*
458  * Fetches the description of an OID.
459  */
460 static bool
461 oid_get_description(const struct oid *o, struct oiddescription *od)
462 {
463         int lookup[CTL_MAXNAME + 2];
464         char *newline;
465         size_t odlen;
466
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) {
472                 if (errno == ENOENT)
473                         return (false);
474                 err(1, "sysctl(oiddescr)");
475         }
476
477         newline = strchr(od->description, '\n');
478         if (newline != NULL)
479                 *newline = '\0';
480
481         return (*od->description != '\0');
482 }
483
484 /* Prints the description of an OID to a file stream. */
485 static void
486 oiddescription_print(const struct oiddescription *od, FILE *fp)
487 {
488
489         fprintf(fp, "%s", od->description);
490 }
491
492 static void
493 oid_print(const struct oid *o, struct oidname *on, bool print_description,
494     bool exclude, bool include, FILE *fp)
495 {
496         struct oidformat of;
497         struct oidvalue ov;
498         struct oiddescription od;
499         char metric[BUFSIZ];
500         bool has_desc;
501
502         if (!oid_get_format(o, &of) || !oid_get_value(o, &of, &ov))
503                 return;
504         oid_get_name(o, on);
505
506         oid_get_metric(on, &of, metric, sizeof(metric));
507
508         if (exclude && regexec(&exc_regex, metric, 0, NULL, 0) == 0)
509                 return;
510
511         if (include && regexec(&inc_regex, metric, 0, NULL, 0) != 0)
512                 return;
513
514         has_desc = oid_get_description(o, &od);
515         /*
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.
518          */
519         if (has_desc && strnstr(od.description, "(LEGACY)", BUFSIZ) != NULL)
520                 return;
521         /*
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.
526          */
527         if (print_description && !oidname_has_labels(on) && has_desc) {
528                 fprintf(fp, "# HELP ");
529                 fprintf(fp, "%s", metric);
530                 fputc(' ', fp);
531                 oiddescription_print(&od, fp);
532                 fputc('\n', fp);
533         }
534
535         /* Print the line with the value. */
536         fprintf(fp, "%s", metric);
537         fputc(' ', fp);
538         oidvalue_print(&ov, fp);
539         fputc('\n', fp);
540 }
541
542 /* Gzip compresses a buffer of memory. */
543 static bool
544 buf_gzip(const char *in, size_t inlen, char *out, size_t *outlen)
545 {
546         z_stream stream = {
547             .next_in    = __DECONST(unsigned char *, in),
548             .avail_in   = inlen,
549             .next_out   = (unsigned char *)out,
550             .avail_out  = *outlen,
551         };
552
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) {
556                 return (false);
557         }
558         *outlen = stream.total_out;
559         return (deflateEnd(&stream) == Z_OK);
560 }
561
562 static void
563 usage(void)
564 {
565
566         fprintf(stderr, "%s",
567             "usage: prometheus_sysctl_exporter [-dgh] [-e pattern] [-i pattern]\n"
568             "\t[prefix ...]\n");
569         exit(1);
570 }
571
572 int
573 main(int argc, char *argv[])
574 {
575         struct oidname on;
576         char *http_buf;
577         FILE *fp;
578         size_t http_buflen;
579         int ch, error;
580         bool exclude, include, gzip_mode, http_mode, print_descriptions;
581         char errbuf[BUFSIZ];
582
583         /* Parse command line flags. */
584         include = exclude = gzip_mode = http_mode = print_descriptions = false;
585         while ((ch = getopt(argc, argv, "de:ghi:")) != -1) {
586                 switch (ch) {
587                 case 'd':
588                         print_descriptions = true;
589                         break;
590                 case 'e':
591                         error = regcomp(&exc_regex, optarg, REG_EXTENDED);
592                         if (error != 0) {
593                                 regerror(error, &exc_regex, errbuf, sizeof(errbuf));
594                                 errx(1, "bad regular expression '%s': %s",
595                                     optarg, errbuf);
596                         }
597                         exclude = true;
598                         break;
599                 case 'g':
600                         gzip_mode = true;
601                         break;
602                 case 'h':
603                         http_mode = true;
604                         break;
605                 case 'i':
606                         error = regcomp(&inc_regex, optarg, REG_EXTENDED);
607                         if (error != 0) {
608                                 regerror(error, &inc_regex, errbuf, sizeof(errbuf));
609                                 errx(1, "bad regular expression '%s': %s",
610                                     optarg, errbuf);
611                         }
612                         include = true;
613                         break;
614                 default:
615                         usage();
616                 }
617         }
618         argc -= optind;
619         argv += optind;
620
621         /* HTTP output: cache metrics in buffer. */
622         if (http_mode) {
623                 fp = open_memstream(&http_buf, &http_buflen);
624                 if (fp == NULL)
625                         err(1, "open_memstream");
626         } else {
627                 fp = stdout;
628         }
629
630         oidname_init(&on);
631         if (argc == 0) {
632                 struct oid o;
633
634                 /* Print all OIDs. */
635                 oid_get_root(&o);
636                 do {
637                         oid_print(&o, &on, print_descriptions, exclude, include, fp);
638                 } while (oid_get_next(&o, &o));
639         } else {
640                 int i;
641
642                 /* Print only trees provided as arguments. */
643                 for (i = 0; i < argc; ++i) {
644                         struct oid o, root;
645
646                         if (!oid_get_by_name(&root, argv[i])) {
647                                 /*
648                                  * Ignore trees provided as arguments that
649                                  * can't be found.  They might belong, for
650                                  * example, to kernel modules not currently
651                                  * loaded.
652                                  */
653                                 continue;
654                         }
655                         o = root;
656                         do {
657                                 oid_print(&o, &on, print_descriptions, exclude, include, fp);
658                         } while (oid_get_next(&o, &o) &&
659                             oid_is_beneath(&o, &root));
660                 }
661         }
662
663         if (http_mode) {
664                 const char *content_encoding = "";
665
666                 if (ferror(fp) || fclose(fp) != 0)
667                         err(1, "Cannot generate output");
668
669                 /* Gzip compress the output. */
670                 if (gzip_mode) {
671                         char *buf;
672                         size_t buflen;
673
674                         buflen = http_buflen;
675                         buf = malloc(buflen);
676                         if (buf == NULL)
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";
680                                 free(http_buf);
681                                 http_buf = buf;
682                                 http_buflen = buflen;
683                         } else {
684                                 free(buf);
685                         }
686                 }
687
688                 /* Print HTTP header and metrics. */
689                 dprintf(STDOUT_FILENO,
690                     "HTTP/1.1 200 OK\r\n"
691                     "Connection: close\r\n"
692                     "%s"
693                     "Content-Length: %zu\r\n"
694                     "Content-Type: text/plain; version=0.0.4\r\n"
695                     "\r\n",
696                     content_encoding, http_buflen);
697                 write(STDOUT_FILENO, http_buf, http_buflen);
698                 free(http_buf);
699
700                 /* Drain output. */
701                 if (shutdown(STDIN_FILENO, SHUT_WR) == 0) {
702                         char buf[1024];
703
704                         while (read(STDIN_FILENO, buf, sizeof(buf)) > 0) {
705                         }
706                 }
707         }
708         return (0);
709 }