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