]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/prometheus_sysctl_exporter/prometheus_sysctl_exporter.c
bhnd(9): Fix a few mandoc related issues
[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 <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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
478 oiddescription_print(const struct oiddescription *od, FILE *fp)
479 {
480
481         fprintf(fp, "%s", od->description);
482 }
483
484 static void
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
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
539 usage(void)
540 {
541
542         fprintf(stderr,
543             "usage: prometheus_sysctl_exporter [-dgh] [prefix ...]\n");
544         exit(1);
545 }
546
547 int
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 }