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