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