Merge branch 'ssnprintf-cleanup'
[collectd.git] / src / collectd-nagios.c
1 /**
2  * collectd-nagios - src/collectd-nagios.c
3  * Copyright (C) 2008-2010  Florian octo Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian octo Forster <octo at collectd.org>
25  **/
26
27 #if HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #if !defined(__GNUC__) || !__GNUC__
32 #define __attribute__(x) /**/
33 #endif
34
35 #include <assert.h>
36 #include <errno.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <strings.h>
41 #include <unistd.h>
42
43 #if NAN_STATIC_DEFAULT
44 #include <math.h>
45 /* #endif NAN_STATIC_DEFAULT*/
46 #elif NAN_STATIC_ISOC
47 #ifndef __USE_ISOC99
48 #define DISABLE_ISOC99 1
49 #define __USE_ISOC99 1
50 #endif /* !defined(__USE_ISOC99) */
51 #include <math.h>
52 #if DISABLE_ISOC99
53 #undef DISABLE_ISOC99
54 #undef __USE_ISOC99
55 #endif /* DISABLE_ISOC99 */
56 /* #endif NAN_STATIC_ISOC */
57 #elif NAN_ZERO_ZERO
58 #include <math.h>
59 #ifdef NAN
60 #undef NAN
61 #endif
62 #define NAN (0.0 / 0.0)
63 #ifndef isnan
64 #define isnan(f) ((f) != (f))
65 #endif /* !defined(isnan) */
66 #ifndef isfinite
67 #define isfinite(f) (((f) - (f)) == 0.0)
68 #endif
69 #ifndef isinf
70 #define isinf(f) (!isfinite(f) && !isnan(f))
71 #endif
72 #endif /* NAN_ZERO_ZERO */
73
74 #include "libcollectdclient/collectd/client.h"
75
76 #define RET_OKAY 0
77 #define RET_WARNING 1
78 #define RET_CRITICAL 2
79 #define RET_UNKNOWN 3
80
81 #define CON_NONE 0
82 #define CON_AVERAGE 1
83 #define CON_SUM 2
84 #define CON_PERCENTAGE 3
85
86 struct range_s {
87   double min;
88   double max;
89   int invert;
90 };
91 typedef struct range_s range_t;
92
93 extern char *optarg;
94 extern int optind, opterr, optopt;
95
96 static char *socket_file_g = NULL;
97 static char *value_string_g = NULL;
98 static char *hostname_g = NULL;
99
100 static range_t range_critical_g;
101 static range_t range_warning_g;
102 static int consolitation_g = CON_NONE;
103 static _Bool nan_is_error_g = 0;
104
105 static char **match_ds_g = NULL;
106 static size_t match_ds_num_g = 0;
107
108 /* `strdup' is an XSI extension. I don't want to pull in all of XSI just for
109  * that, so here's an own implementation.. It's easy enough. The GCC attributes
110  * are supposed to get good performance..  -octo */
111 __attribute__((malloc, nonnull(1))) static char *
112 cn_strdup(const char *str) /* {{{ */
113 {
114   size_t strsize;
115   char *ret;
116
117   strsize = strlen(str) + 1;
118   ret = (char *)malloc(strsize);
119   if (ret != NULL)
120     memcpy(ret, str, strsize);
121   return ret;
122 } /* }}} char *cn_strdup */
123
124 static int filter_ds(size_t *values_num, double **values,
125                      char ***values_names) {
126   gauge_t *new_values;
127   char **new_names;
128
129   if (match_ds_g == NULL)
130     return RET_OKAY;
131
132   new_values = (gauge_t *)calloc(match_ds_num_g, sizeof(*new_values));
133   if (new_values == NULL) {
134     fprintf(stderr, "calloc failed: %s\n", strerror(errno));
135     return RET_UNKNOWN;
136   }
137
138   new_names = (char **)calloc(match_ds_num_g, sizeof(*new_names));
139   if (new_names == NULL) {
140     fprintf(stderr, "calloc failed: %s\n", strerror(errno));
141     free(new_values);
142     return RET_UNKNOWN;
143   }
144
145   for (size_t i = 0; i < match_ds_num_g; i++) {
146     size_t j;
147
148     /* match_ds_g keeps pointers into argv but the names will be freed */
149     new_names[i] = cn_strdup(match_ds_g[i]);
150     if (new_names[i] == NULL) {
151       fprintf(stderr, "cn_strdup failed: %s\n", strerror(errno));
152       free(new_values);
153       for (j = 0; j < i; j++)
154         free(new_names[j]);
155       free(new_names);
156       return RET_UNKNOWN;
157     }
158
159     for (j = 0; j < *values_num; j++)
160       if (strcasecmp(new_names[i], (*values_names)[j]) == 0)
161         break;
162
163     if (j == *values_num) {
164       printf("ERROR: DS `%s' is not available.\n", new_names[i]);
165       free(new_values);
166       for (j = 0; j <= i; j++)
167         free(new_names[j]);
168       free(new_names);
169       return RET_CRITICAL;
170     }
171
172     new_values[i] = (*values)[j];
173   }
174
175   free(*values);
176   for (size_t i = 0; i < *values_num; i++)
177     free((*values_names)[i]);
178   free(*values_names);
179
180   *values = new_values;
181   *values_names = new_names;
182   *values_num = match_ds_num_g;
183   return RET_OKAY;
184 } /* int filter_ds */
185
186 static void parse_range(char *string, range_t *range) {
187   char *min_ptr;
188   char *max_ptr;
189
190   if (*string == '@') {
191     range->invert = 1;
192     string++;
193   }
194
195   max_ptr = strchr(string, ':');
196   if (max_ptr == NULL) {
197     min_ptr = NULL;
198     max_ptr = string;
199   } else {
200     min_ptr = string;
201     *max_ptr = '\0';
202     max_ptr++;
203   }
204
205   assert(max_ptr != NULL);
206
207   /* `10' == `0:10' */
208   if (min_ptr == NULL)
209     range->min = 0.0;
210   /* :10 == ~:10 == -inf:10 */
211   else if ((*min_ptr == '\0') || (*min_ptr == '~'))
212     range->min = NAN;
213   else
214     range->min = atof(min_ptr);
215
216   if ((*max_ptr == '\0') || (*max_ptr == '~'))
217     range->max = NAN;
218   else
219     range->max = atof(max_ptr);
220 } /* void parse_range */
221
222 static int match_range(range_t *range, double value) {
223   int ret = 0;
224
225   if (!isnan(range->min) && (range->min > value))
226     ret = 1;
227   if (!isnan(range->max) && (range->max < value))
228     ret = 1;
229
230   return ((ret - range->invert) == 0) ? 0 : 1;
231 } /* int match_range */
232
233 __attribute__((noreturn)) static void usage(const char *name) {
234   fprintf(stderr,
235           "Usage: %s <-s socket> <-n value_spec> <-H hostname> [options]\n"
236           "\n"
237           "Valid options are:\n"
238           "  -s <socket>    Path to collectd's UNIX-socket.\n"
239           "  -n <v_spec>    Value specification to get from collectd.\n"
240           "                 Format: `plugin-instance/type-instance'\n"
241           "  -d <ds>        Select the DS to examine. May be repeated to "
242           "examine multiple\n"
243           "                 DSes. By default all DSes are used.\n"
244           "  -g <consol>    Method to use to consolidate several DSes.\n"
245           "                 See below for a list of valid arguments.\n"
246           "  -H <host>      Hostname to query the values for.\n"
247           "  -c <range>     Critical range\n"
248           "  -w <range>     Warning range\n"
249           "  -m             Treat \"Not a Number\" (NaN) as critical (default: "
250           "warning)\n"
251           "\n"
252           "Consolidation functions:\n"
253           "  none:          Apply the warning- and critical-ranges to each "
254           "data-source\n"
255           "                 individually.\n"
256           "  average:       Calculate the average of all matching DSes and "
257           "apply the\n"
258           "                 warning- and critical-ranges to the calculated "
259           "average.\n"
260           "  sum:           Apply the ranges to the sum of all DSes.\n"
261           "  percentage:    Apply the ranges to the ratio (in percent) of the "
262           "first value\n"
263           "                 and the sum of all values."
264           "\n",
265           name);
266   exit(1);
267 } /* void usage */
268
269 static int do_listval(lcc_connection_t *connection) {
270   lcc_identifier_t *ret_ident = NULL;
271   size_t ret_ident_num = 0;
272
273   char *hostname = NULL;
274
275   int status;
276
277   status = lcc_listval(connection, &ret_ident, &ret_ident_num);
278   if (status != 0) {
279     printf("UNKNOWN: %s\n", lcc_strerror(connection));
280     if (ret_ident != NULL)
281       free(ret_ident);
282     return RET_UNKNOWN;
283   }
284
285   status = lcc_sort_identifiers(connection, ret_ident, ret_ident_num);
286   if (status != 0) {
287     printf("UNKNOWN: %s\n", lcc_strerror(connection));
288     if (ret_ident != NULL)
289       free(ret_ident);
290     return RET_UNKNOWN;
291   }
292
293   for (size_t i = 0; i < ret_ident_num; ++i) {
294     char id[1024];
295
296     if ((hostname_g != NULL) && (strcasecmp(hostname_g, ret_ident[i].host)))
297       continue;
298
299     if ((hostname == NULL) || strcasecmp(hostname, ret_ident[i].host)) {
300       free(hostname);
301       hostname = strdup(ret_ident[i].host);
302       printf("Host: %s\n", hostname);
303     }
304
305     /* empty hostname; not to be printed again */
306     ret_ident[i].host[0] = '\0';
307
308     status =
309         lcc_identifier_to_string(connection, id, sizeof(id), ret_ident + i);
310     if (status != 0) {
311       printf("ERROR: listval: Failed to convert returned "
312              "identifier to a string: %s\n",
313              lcc_strerror(connection));
314       free(hostname);
315       hostname = NULL;
316       continue;
317     }
318
319     /* skip over the (empty) hostname and following '/' */
320     printf("\t%s\n", id + 1);
321   }
322
323   free(ret_ident);
324   free(hostname);
325   return RET_OKAY;
326 } /* int do_listval */
327
328 static int do_check_con_none(size_t values_num, double *values,
329                              char **values_names) {
330   int num_critical = 0;
331   int num_warning = 0;
332   int num_okay = 0;
333   const char *status_str = "UNKNOWN";
334   int status_code = RET_UNKNOWN;
335
336   for (size_t i = 0; i < values_num; i++) {
337     if (isnan(values[i])) {
338       if (nan_is_error_g)
339         num_critical++;
340       else
341         num_warning++;
342     } else if (match_range(&range_critical_g, values[i]) != 0)
343       num_critical++;
344     else if (match_range(&range_warning_g, values[i]) != 0)
345       num_warning++;
346     else
347       num_okay++;
348   }
349
350   if ((num_critical == 0) && (num_warning == 0) && (num_okay == 0)) {
351     printf("WARNING: No defined values found\n");
352     return RET_WARNING;
353   } else if ((num_critical == 0) && (num_warning == 0)) {
354     status_str = "OKAY";
355     status_code = RET_OKAY;
356   } else if (num_critical == 0) {
357     status_str = "WARNING";
358     status_code = RET_WARNING;
359   } else {
360     status_str = "CRITICAL";
361     status_code = RET_CRITICAL;
362   }
363
364   printf("%s: %i critical, %i warning, %i okay", status_str, num_critical,
365          num_warning, num_okay);
366   if (values_num > 0) {
367     printf(" |");
368     for (size_t i = 0; i < values_num; i++)
369       printf(" %s=%f;;;;", values_names[i], values[i]);
370   }
371   printf("\n");
372
373   return status_code;
374 } /* int do_check_con_none */
375
376 static int do_check_con_average(size_t values_num, double *values,
377                                 char **values_names) {
378   double total;
379   int total_num;
380   double average;
381   const char *status_str = "UNKNOWN";
382   int status_code = RET_UNKNOWN;
383
384   total = 0.0;
385   total_num = 0;
386   for (size_t i = 0; i < values_num; i++) {
387     if (isnan(values[i])) {
388       if (!nan_is_error_g)
389         continue;
390
391       printf("CRITICAL: Data source \"%s\" is NaN\n", values_names[i]);
392       return RET_CRITICAL;
393     }
394
395     total += values[i];
396     total_num++;
397   }
398
399   if (total_num == 0) {
400     printf("WARNING: No defined values found\n");
401     return RET_WARNING;
402   }
403
404   average = total / total_num;
405
406   if (match_range(&range_critical_g, average) != 0) {
407     status_str = "CRITICAL";
408     status_code = RET_CRITICAL;
409   } else if (match_range(&range_warning_g, average) != 0) {
410     status_str = "WARNING";
411     status_code = RET_WARNING;
412   } else {
413     status_str = "OKAY";
414     status_code = RET_OKAY;
415   }
416
417   printf("%s: %g average |", status_str, average);
418   for (size_t i = 0; i < values_num; i++)
419     printf(" %s=%f;;;;", values_names[i], values[i]);
420   printf("\n");
421
422   return status_code;
423 } /* int do_check_con_average */
424
425 static int do_check_con_sum(size_t values_num, double *values,
426                             char **values_names) {
427   double total;
428   int total_num;
429   const char *status_str = "UNKNOWN";
430   int status_code = RET_UNKNOWN;
431
432   total = 0.0;
433   total_num = 0;
434   for (size_t i = 0; i < values_num; i++) {
435     if (isnan(values[i])) {
436       if (!nan_is_error_g)
437         continue;
438
439       printf("CRITICAL: Data source \"%s\" is NaN\n", values_names[i]);
440       return RET_CRITICAL;
441     }
442
443     total += values[i];
444     total_num++;
445   }
446
447   if (total_num == 0) {
448     printf("WARNING: No defined values found\n");
449     return RET_WARNING;
450   }
451
452   if (match_range(&range_critical_g, total) != 0) {
453     status_str = "CRITICAL";
454     status_code = RET_CRITICAL;
455   } else if (match_range(&range_warning_g, total) != 0) {
456     status_str = "WARNING";
457     status_code = RET_WARNING;
458   } else {
459     status_str = "OKAY";
460     status_code = RET_OKAY;
461   }
462
463   printf("%s: %g sum |", status_str, total);
464   for (size_t i = 0; i < values_num; i++)
465     printf(" %s=%f;;;;", values_names[i], values[i]);
466   printf("\n");
467
468   return status_code;
469 } /* int do_check_con_sum */
470
471 static int do_check_con_percentage(size_t values_num, double *values,
472                                    char **values_names) {
473   double sum = 0.0;
474   double percentage;
475
476   const char *status_str = "UNKNOWN";
477   int status_code = RET_UNKNOWN;
478
479   if ((values_num < 1) || (isnan(values[0]))) {
480     printf("WARNING: The first value is not defined\n");
481     return RET_WARNING;
482   }
483
484   for (size_t i = 0; i < values_num; i++) {
485     if (isnan(values[i])) {
486       if (!nan_is_error_g)
487         continue;
488
489       printf("CRITICAL: Data source \"%s\" is NaN\n", values_names[i]);
490       return RET_CRITICAL;
491     }
492
493     sum += values[i];
494   }
495
496   if (sum == 0.0) {
497     printf("WARNING: Values sum up to zero\n");
498     return RET_WARNING;
499   }
500
501   percentage = 100.0 * values[0] / sum;
502
503   if (match_range(&range_critical_g, percentage) != 0) {
504     status_str = "CRITICAL";
505     status_code = RET_CRITICAL;
506   } else if (match_range(&range_warning_g, percentage) != 0) {
507     status_str = "WARNING";
508     status_code = RET_WARNING;
509   } else {
510     status_str = "OKAY";
511     status_code = RET_OKAY;
512   }
513
514   printf("%s: %lf percent |", status_str, percentage);
515   for (size_t i = 0; i < values_num; i++)
516     printf(" %s=%lf;;;;", values_names[i], values[i]);
517   return status_code;
518 } /* int do_check_con_percentage */
519
520 static int do_check(lcc_connection_t *connection) {
521   gauge_t *values;
522   char **values_names;
523   size_t values_num;
524   char ident_str[1024];
525   lcc_identifier_t ident;
526   int status;
527
528   snprintf(ident_str, sizeof(ident_str), "%s/%s", hostname_g, value_string_g);
529   ident_str[sizeof(ident_str) - 1] = 0;
530
531   status = lcc_string_to_identifier(connection, &ident, ident_str);
532   if (status != 0) {
533     printf("ERROR: Creating an identifier failed: %s.\n",
534            lcc_strerror(connection));
535     LCC_DESTROY(connection);
536     return RET_CRITICAL;
537   }
538
539   status = lcc_getval(connection, &ident, &values_num, &values, &values_names);
540   if (status != 0) {
541     printf("ERROR: Retrieving values from the daemon failed: %s.\n",
542            lcc_strerror(connection));
543     LCC_DESTROY(connection);
544     return RET_CRITICAL;
545   }
546
547   LCC_DESTROY(connection);
548
549   status = filter_ds(&values_num, &values, &values_names);
550   if (status != RET_OKAY)
551     return status;
552
553   status = RET_UNKNOWN;
554   if (consolitation_g == CON_NONE)
555     status = do_check_con_none(values_num, values, values_names);
556   else if (consolitation_g == CON_AVERAGE)
557     status = do_check_con_average(values_num, values, values_names);
558   else if (consolitation_g == CON_SUM)
559     status = do_check_con_sum(values_num, values, values_names);
560   else if (consolitation_g == CON_PERCENTAGE)
561     status = do_check_con_percentage(values_num, values, values_names);
562
563   free(values);
564   if (values_names != NULL)
565     for (size_t i = 0; i < values_num; i++)
566       free(values_names[i]);
567   free(values_names);
568
569   return status;
570 } /* int do_check */
571
572 int main(int argc, char **argv) {
573   char address[1024];
574   lcc_connection_t *connection;
575
576   int status;
577
578   range_critical_g.min = NAN;
579   range_critical_g.max = NAN;
580   range_critical_g.invert = 0;
581
582   range_warning_g.min = NAN;
583   range_warning_g.max = NAN;
584   range_warning_g.invert = 0;
585
586   while (42) {
587     int c;
588
589     c = getopt(argc, argv, "w:c:s:n:H:g:d:hm");
590     if (c < 0)
591       break;
592
593     switch (c) {
594     case 'c':
595       parse_range(optarg, &range_critical_g);
596       break;
597     case 'w':
598       parse_range(optarg, &range_warning_g);
599       break;
600     case 's':
601       socket_file_g = optarg;
602       break;
603     case 'n':
604       value_string_g = optarg;
605       break;
606     case 'H':
607       hostname_g = optarg;
608       break;
609     case 'g':
610       if (strcasecmp(optarg, "none") == 0)
611         consolitation_g = CON_NONE;
612       else if (strcasecmp(optarg, "average") == 0)
613         consolitation_g = CON_AVERAGE;
614       else if (strcasecmp(optarg, "sum") == 0)
615         consolitation_g = CON_SUM;
616       else if (strcasecmp(optarg, "percentage") == 0)
617         consolitation_g = CON_PERCENTAGE;
618       else {
619         fprintf(stderr, "Unknown consolidation function `%s'.\n", optarg);
620         usage(argv[0]);
621       }
622       break;
623     case 'd': {
624       char **tmp;
625       tmp = realloc(match_ds_g, (match_ds_num_g + 1) * sizeof(char *));
626       if (tmp == NULL) {
627         fprintf(stderr, "realloc failed: %s\n", strerror(errno));
628         return RET_UNKNOWN;
629       }
630       match_ds_g = tmp;
631       match_ds_g[match_ds_num_g] = cn_strdup(optarg);
632       if (match_ds_g[match_ds_num_g] == NULL) {
633         fprintf(stderr, "cn_strdup failed: %s\n", strerror(errno));
634         return RET_UNKNOWN;
635       }
636       match_ds_num_g++;
637       break;
638     }
639     case 'm':
640       nan_is_error_g = 1;
641       break;
642     default:
643       usage(argv[0]);
644     } /* switch (c) */
645   }
646
647   if ((socket_file_g == NULL) || (value_string_g == NULL) ||
648       ((hostname_g == NULL) && (strcasecmp(value_string_g, "LIST")))) {
649     fprintf(stderr, "Missing required arguments.\n");
650     usage(argv[0]);
651   }
652
653   snprintf(address, sizeof(address), "unix:%s", socket_file_g);
654   address[sizeof(address) - 1] = 0;
655
656   connection = NULL;
657   status = lcc_connect(address, &connection);
658   if (status != 0) {
659     printf("ERROR: Connecting to daemon at %s failed.\n", socket_file_g);
660     return RET_CRITICAL;
661   }
662
663   if (0 == strcasecmp(value_string_g, "LIST"))
664     return do_listval(connection);
665
666   return do_check(connection);
667 } /* int main */