src/collectd-nagios.c: Improve handling of lines returned from `GETVAL'.
[collectd.git] / src / collectd-nagios.c
1 #include "config.h"
2
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <stdio.h>
6 #include <errno.h>
7 #include <string.h>
8 #include <assert.h>
9
10 #include <sys/socket.h>
11 #include <sys/un.h>
12
13 /*
14  * This is copied directly from collectd.h. Make changes there!
15  */
16 #if NAN_STATIC_DEFAULT
17 # include <math.h>
18 /* #endif NAN_STATIC_DEFAULT*/
19 #elif NAN_STATIC_ISOC
20 # ifndef __USE_ISOC99
21 #  define DISABLE_ISOC99 1
22 #  define __USE_ISOC99 1
23 # endif /* !defined(__USE_ISOC99) */
24 # include <math.h>
25 # if DISABLE_ISOC99
26 #  undef DISABLE_ISOC99
27 #  undef __USE_ISOC99
28 # endif /* DISABLE_ISOC99 */
29 /* #endif NAN_STATIC_ISOC */
30 #elif NAN_ZERO_ZERO
31 # include <math.h>
32 # ifdef NAN
33 #  undef NAN
34 # endif
35 # define NAN (0.0 / 0.0)
36 # ifndef isnan
37 #  define isnan(f) ((f) != (f))
38 # endif /* !defined(isnan) */
39 #endif /* NAN_ZERO_ZERO */
40
41 #define RET_OKAY     0
42 #define RET_WARNING  1
43 #define RET_CRITICAL 2
44 #define RET_UNKNOWN  3
45
46 #define CON_NONE     0
47 #define CON_AVERAGE  1
48 #define CON_SUM      2
49
50 struct range_s
51 {
52         double min;
53         double max;
54         int    invert;
55 };
56 typedef struct range_s range_t;
57
58 extern char *optarg;
59 extern int optind, opterr, optopt;
60
61 static char *socket_file_g = NULL;
62 static char *value_string_g = NULL;
63 static char *hostname_g = NULL;
64
65 static range_t range_critical_g;
66 static range_t range_warning_g;
67 static int consolitation_g = CON_NONE;
68
69 static char **match_ds_g = NULL;
70 static int    match_ds_num_g = 0;
71
72 static int ignore_ds (const char *name)
73 {
74         int i;
75
76         if (match_ds_g == NULL)
77                 return (0);
78
79         for (i = 0; i < match_ds_num_g; i++)
80                 if (strcasecmp (match_ds_g[i], name) == 0)
81                         return (0);
82
83         return (1);
84 } /* int ignore_ds */
85
86 static void parse_range (char *string, range_t *range)
87 {
88         char *min_ptr;
89         char *max_ptr;
90
91         if (*string == '@')
92         {
93                 range->invert = 1;
94                 string++;
95         }
96
97         max_ptr = strchr (string, ':');
98         if (max_ptr == NULL)
99         {
100                 min_ptr = NULL;
101                 max_ptr = string;
102         }
103         else
104         {
105                 min_ptr = string;
106                 *max_ptr = '\0';
107                 max_ptr++;
108         }
109
110         assert (max_ptr != NULL);
111
112         /* `10' == `0:10' */
113         if (min_ptr == NULL)
114                 range->min = 0.0;
115         /* :10 == ~:10 == -inf:10 */
116         else if ((*min_ptr == '\0') || (*min_ptr == '~'))
117                 range->min = NAN;
118         else
119                 range->min = atof (min_ptr);
120
121         if ((*max_ptr == '\0') || (*max_ptr == '~'))
122                 range->max = NAN;
123         else
124                 range->max = atof (max_ptr);
125 } /* void parse_range */
126
127 int match_range (range_t *range, double value)
128 {
129         int ret = 0;
130
131         if (!isnan (range->min) && (range->min > value))
132                 ret = 1;
133         if (!isnan (range->max) && (range->max < value))
134                 ret = 1;
135
136         return (((ret - range->invert) == 0) ? 0 : 1);
137 }
138
139 static int get_values (int *ret_values_num, double **ret_values,
140                 char ***ret_values_names)
141 {
142         struct sockaddr_un sa;
143         int status;
144         int fd;
145         FILE *fh_in, *fh_out;
146         char buffer[4096];
147
148         int values_num;
149         double *values;
150         char **values_names;
151
152         int i;
153         int j;
154
155         fd = socket (PF_UNIX, SOCK_STREAM, 0);
156         if (fd < 0)
157         {
158                 fprintf (stderr, "socket failed: %s\n",
159                                 strerror (errno));
160                 return (-1);
161         }
162
163         memset (&sa, '\0', sizeof (sa));
164         sa.sun_family = AF_UNIX;
165         strncpy (sa.sun_path, socket_file_g,
166                         sizeof (sa.sun_path) - 1);
167
168         status = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
169         if (status != 0)
170         {
171                 fprintf (stderr, "connect failed: %s\n",
172                                 strerror (errno));
173                 return (-1);
174         }
175
176         fh_in = fdopen (fd, "r");
177         if (fh_in == NULL)
178         {
179                 fprintf (stderr, "fdopen failed: %s\n",
180                                 strerror (errno));
181                 close (fd);
182                 return (-1);
183         }
184
185         fh_out = fdopen (fd, "w");
186         if (fh_out == NULL)
187         {
188                 fprintf (stderr, "fdopen failed: %s\n",
189                                 strerror (errno));
190                 fclose (fh_in);
191                 return (-1);
192         }
193
194         fprintf (fh_out, "GETVAL %s/%s\n", hostname_g, value_string_g);
195         fflush (fh_out);
196
197         if (fgets (buffer, sizeof (buffer), fh_in) == NULL)
198         {
199                 fprintf (stderr, "fgets failed: %s\n",
200                                 strerror (errno));
201                 fclose (fh_in);
202                 fclose (fh_out);
203                 return (-1);
204         }
205
206         {
207                 char *ptr = strchr (buffer, ' ');
208
209                 if (ptr != NULL)
210                         *ptr = '\0';
211
212                 values_num = atoi (buffer);
213                 if (values_num < 1)
214                         return (-1);
215         }
216
217         values = (double *) malloc (values_num * sizeof (double));
218         if (values == NULL)
219         {
220                 fprintf (stderr, "malloc failed: %s\n",
221                                 strerror (errno));
222                 return (-1);
223         }
224
225         values_names = (char **) malloc (values_num * sizeof (char *));
226         if (values_names == NULL)
227         {
228                 fprintf (stderr, "malloc failed: %s\n",
229                                 strerror (errno));
230                 free (values);
231                 return (-1);
232         }
233         memset (values_names, 0, values_num * sizeof (char *));
234
235         i = 0; /* index of the values returned by the server */
236         j = 0; /* number of values in `values_names' and `values' */
237         while (fgets (buffer, sizeof (buffer), fh_in) != NULL)
238         {
239                 do /* while (0) */
240                 {
241                         char *key;
242                         char *value;
243                         char *endptr;
244
245                         key = buffer;
246
247                         value = strchr (key, '=');
248                         if (value == NULL)
249                         {
250                                 fprintf (stderr, "Cannot parse line: %s\n", buffer);
251                                 break;
252                         }
253                         *value = 0;
254                         value++;
255
256                         if (ignore_ds (key) != 0)
257                                 break;
258
259                         endptr = NULL;
260                         errno = 0;
261                         values[j] = strtod (value, &endptr);
262                         if ((endptr == value) || (errno != 0))
263                         {
264                                 fprintf (stderr, "Could not parse buffer "
265                                                 "as number: %s\n", value);
266                                 break;
267                         }
268
269                         values_names[j] = strdup (key);
270                         if (values_names[j] == NULL)
271                         {
272                                 fprintf (stderr, "strdup failed.\n");
273                                 break;
274                         }
275                         j++;
276                 } while (0);
277
278                 i++;
279                 if (i >= values_num)
280                         break;
281         }
282         /* Set `values_num' to the number of values actually stored in the
283          * array. */
284         values_num = j;
285
286         fclose (fh_in); fh_in = NULL; fd = -1;
287         fclose (fh_out); fh_out = NULL;
288
289         *ret_values_num = values_num;
290         *ret_values = values;
291         *ret_values_names = values_names;
292
293         return (0);
294 } /* int get_values */
295
296 static void usage (const char *name)
297 {
298         fprintf (stderr, "Usage: %s <-s socket> <-n value_spec> <-H hostname> [options]\n"
299                         "\n"
300                         "Valid options are:\n"
301                         "  -s <socket>    Path to collectd's UNIX-socket.\n"
302                         "  -n <v_spec>    Value specification to get from collectd.\n"
303                         "                 Format: `plugin-instance/type-instance'\n"
304                         "  -d <ds>        Select the DS to examine. May be repeated to examine multiple\n"
305                         "                 DSes. By default all DSes are used.\n"
306                         "  -g <consol>    Method to use to consolidate several DSes.\n"
307                         "                 Valid arguments are `none', `average' and `sum'\n"
308                         "  -H <host>      Hostname to query the values for.\n"
309                         "  -c <range>     Critical range\n"
310                         "  -w <range>     Warning range\n"
311                         "\n"
312                         "Consolidation functions:\n"
313                         "  none:          Apply the warning- and critical-ranges to each data-source\n"
314                         "                 individually.\n"
315                         "  average:       Calculate the average of all matching DSes and apply the\n"
316                         "                 warning- and critical-ranges to the calculated average.\n"
317                         "  sum:           Apply the ranges to the sum of all DSes.\n"
318                         "\n", name);
319         exit (1);
320 } /* void usage */
321
322 int do_check_con_none (int values_num, double *values, char **values_names)
323 {
324         int i;
325
326         int num_critical = 0;
327         int num_warning  = 0;
328         int num_okay = 0;
329
330         for (i = 0; i < values_num; i++)
331         {
332                 if (isnan (values[i]))
333                         num_warning++;
334                 else if (match_range (&range_critical_g, values[i]) != 0)
335                         num_critical++;
336                 else if (match_range (&range_warning_g, values[i]) != 0)
337                         num_warning++;
338                 else
339                         num_okay++;
340         }
341
342         printf ("%i critical, %i warning, %i okay",
343                         num_critical, num_warning, num_okay);
344         if (values_num > 0)
345         {
346                 printf (" |");
347                 for (i = 0; i < values_num; i++)
348                         printf (" %s=%lf;;;;", values_names[i], values[i]);
349         }
350         printf ("\n");
351
352         if ((num_critical != 0) || (values_num == 0))
353                 return (RET_CRITICAL);
354         else if (num_warning != 0)
355                 return (RET_WARNING);
356
357         return (RET_OKAY);
358 } /* int do_check_con_none */
359
360 int do_check_con_average (int values_num, double *values, char **values_names)
361 {
362         int i;
363         double total;
364         int total_num;
365         double average;
366
367         total = 0.0;
368         total_num = 0;
369         for (i = 0; i < values_num; i++)
370         {
371                 if (!isnan (values[i]))
372                 {
373                         total += values[i];
374                         total_num++;
375                 }
376         }
377
378         if (total_num == 0)
379                 average = NAN;
380         else
381                 average = total / total_num;
382         printf ("%lf average |", average);
383         for (i = 0; i < values_num; i++)
384                 printf (" %s=%lf;;;;", values_names[i], values[i]);
385
386         if (total_num == 0)
387                 return (RET_WARNING);
388
389         if (isnan (average)
390                         || match_range (&range_critical_g, average))
391                 return (RET_CRITICAL);
392         else if (match_range (&range_warning_g, average) != 0)
393                 return (RET_WARNING);
394
395         return (RET_OKAY);
396 } /* int do_check_con_average */
397
398 int do_check_con_sum (int values_num, double *values, char **values_names)
399 {
400         int i;
401         double total;
402         int total_num;
403
404         total = 0.0;
405         total_num = 0;
406         for (i = 0; i < values_num; i++)
407         {
408                 if (!isnan (values[i]))
409                 {
410                         total += values[i];
411                         total_num++;
412                 }
413         }
414
415         if (total_num == 0)
416         {
417                 printf ("WARNING: No defined values found\n");
418                 return (RET_WARNING);
419         }
420
421         if (match_range (&range_critical_g, total) != 0)
422         {
423                 printf ("CRITICAL: Sum = %lf\n", total);
424                 return (RET_CRITICAL);
425         }
426         else if (match_range (&range_warning_g, total) != 0)
427         {
428                 printf ("WARNING: Sum = %lf\n", total);
429                 return (RET_WARNING);
430         }
431         else
432         {
433                 printf ("OKAY: Sum = %lf\n", total);
434                 return (RET_OKAY);
435         }
436
437         return (RET_UNKNOWN);
438 } /* int do_check_con_sum */
439
440 int do_check (void)
441 {
442         double  *values;
443         char   **values_names;
444         int      values_num;
445
446         if (get_values (&values_num, &values, &values_names) != 0)
447         {
448                 fputs ("ERROR: Cannot get values from daemon\n", stdout);
449                 return (RET_CRITICAL);
450         }
451
452         if (consolitation_g == CON_NONE)
453                 return (do_check_con_none (values_num, values, values_names));
454         else if (consolitation_g == CON_AVERAGE)
455                 return (do_check_con_average (values_num, values, values_names));
456         else if (consolitation_g == CON_SUM)
457                 return (do_check_con_sum (values_num, values, values_names));
458
459         free (values);
460         free (values_names); /* FIXME? */
461
462         return (RET_UNKNOWN);
463 }
464
465 int main (int argc, char **argv)
466 {
467         range_critical_g.min = NAN;
468         range_critical_g.max = NAN;
469         range_critical_g.invert = 0;
470
471         range_warning_g.min = NAN;
472         range_warning_g.max = NAN;
473         range_warning_g.invert = 0;
474
475         while (42)
476         {
477                 int c;
478
479                 c = getopt (argc, argv, "w:c:s:n:H:g:d:h");
480                 if (c < 0)
481                         break;
482
483                 switch (c)
484                 {
485                         case 'c':
486                                 parse_range (optarg, &range_critical_g);
487                                 break;
488                         case 'w':
489                                 parse_range (optarg, &range_warning_g);
490                                 break;
491                         case 's':
492                                 socket_file_g = optarg;
493                                 break;
494                         case 'n':
495                                 value_string_g = optarg;
496                                 break;
497                         case 'H':
498                                 hostname_g = optarg;
499                                 break;
500                         case 'g':
501                                 if (strcasecmp (optarg, "none") == 0)
502                                         consolitation_g = CON_NONE;
503                                 else if (strcasecmp (optarg, "average") == 0)
504                                         consolitation_g = CON_AVERAGE;
505                                 else if (strcasecmp (optarg, "sum") == 0)
506                                         consolitation_g = CON_SUM;
507                                 else
508                                         usage (argv[0]);
509                                 break;
510                         case 'd':
511                         {
512                                 char **tmp;
513                                 tmp = (char **) realloc (match_ds_g,
514                                                 (match_ds_num_g + 1)
515                                                 * sizeof (char *));
516                                 if (tmp == NULL)
517                                 {
518                                         fprintf (stderr, "realloc failed: %s\n",
519                                                         strerror (errno));
520                                         return (RET_UNKNOWN);
521                                 }
522                                 match_ds_g = tmp;
523                                 match_ds_g[match_ds_num_g] = strdup (optarg);
524                                 if (match_ds_g[match_ds_num_g] == NULL)
525                                 {
526                                         fprintf (stderr, "strdup failed: %s\n",
527                                                         strerror (errno));
528                                         return (RET_UNKNOWN);
529                                 }
530                                 match_ds_num_g++;
531                                 break;
532                         }
533                         default:
534                                 usage (argv[0]);
535                 } /* switch (c) */
536         }
537
538         if ((socket_file_g == NULL) || (value_string_g == NULL)
539                         || (hostname_g == NULL))
540                 usage (argv[0]);
541
542         return (do_check ());
543 } /* int main */