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 **/
27 #if HAVE_CONFIG_H
28 #include "config.h"
29 #endif
31 #if !defined(__GNUC__) || !__GNUC__
32 #define __attribute__(x) /**/
33 #endif
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>
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 */
74 #include "libcollectdclient/collectd/client.h"
76 #define RET_OKAY 0
77 #define RET_WARNING 1
78 #define RET_CRITICAL 2
79 #define RET_UNKNOWN 3
81 #define CON_NONE 0
82 #define CON_AVERAGE 1
83 #define CON_SUM 2
84 #define CON_PERCENTAGE 3
86 struct range_s {
87 double min;
88 double max;
89 int invert;
90 };
91 typedef struct range_s range_t;
93 extern char *optarg;
94 extern int optind, opterr, optopt;
96 static char *socket_file_g = NULL;
97 static char *value_string_g = NULL;
98 static char *hostname_g = NULL;
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;
105 static char **match_ds_g = NULL;
106 static size_t match_ds_num_g = 0;
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;
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 */
124 static int filter_ds(size_t *values_num, double **values,
125 char ***values_names) {
126 gauge_t *new_values;
127 char **new_names;
129 if (match_ds_g == NULL)
130 return (RET_OKAY);
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 }
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 }
145 for (size_t i = 0; i < match_ds_num_g; i++) {
146 size_t j;
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 }
159 for (j = 0; j < *values_num; j++)
160 if (strcasecmp(new_names[i], (*values_names)[j]) == 0)
161 break;
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 }
172 new_values[i] = (*values)[j];
173 }
175 free(*values);
176 for (size_t i = 0; i < *values_num; i++)
177 free((*values_names)[i]);
178 free(*values_names);
180 *values = new_values;
181 *values_names = new_names;
182 *values_num = match_ds_num_g;
183 return (RET_OKAY);
184 } /* int filter_ds */
186 static void parse_range(char *string, range_t *range) {
187 char *min_ptr;
188 char *max_ptr;
190 if (*string == '@') {
191 range->invert = 1;
192 string++;
193 }
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 }
205 assert(max_ptr != NULL);
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);
216 if ((*max_ptr == '\0') || (*max_ptr == '~'))
217 range->max = NAN;
218 else
219 range->max = atof(max_ptr);
220 } /* void parse_range */
222 static int match_range(range_t *range, double value) {
223 int ret = 0;
225 if (!isnan(range->min) && (range->min > value))
226 ret = 1;
227 if (!isnan(range->max) && (range->max < value))
228 ret = 1;
230 return (((ret - range->invert) == 0) ? 0 : 1);
231 } /* int match_range */
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 */
269 static int do_listval(lcc_connection_t *connection) {
270 lcc_identifier_t *ret_ident = NULL;
271 size_t ret_ident_num = 0;
273 char *hostname = NULL;
275 int status;
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 }
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 }
293 for (size_t i = 0; i < ret_ident_num; ++i) {
294 char id[1024];
296 if ((hostname_g != NULL) && (strcasecmp(hostname_g, ret_ident[i].host)))
297 continue;
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 }
305 /* empty hostname; not to be printed again */
306 ret_ident[i].host[0] = '\0';
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 }
319 /* skip over the (empty) hostname and following '/' */
320 printf("\t%s\n", id + 1);
321 }
323 free(ret_ident);
324 free(hostname);
325 return (RET_OKAY);
326 } /* int do_listval */
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;
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 }
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 }
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");
373 return (status_code);
374 } /* int do_check_con_none */
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;
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;
391 printf("CRITICAL: Data source \"%s\" is NaN\n", values_names[i]);
392 return (RET_CRITICAL);
393 }
395 total += values[i];
396 total_num++;
397 }
399 if (total_num == 0) {
400 printf("WARNING: No defined values found\n");
401 return (RET_WARNING);
402 }
404 average = total / total_num;
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 }
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");
422 return (status_code);
423 } /* int do_check_con_average */
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;
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;
439 printf("CRITICAL: Data source \"%s\" is NaN\n", values_names[i]);
440 return (RET_CRITICAL);
441 }
443 total += values[i];
444 total_num++;
445 }
447 if (total_num == 0) {
448 printf("WARNING: No defined values found\n");
449 return (RET_WARNING);
450 }
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 }
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");
468 return (status_code);
469 } /* int do_check_con_sum */
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;
476 const char *status_str = "UNKNOWN";
477 int status_code = RET_UNKNOWN;
479 if ((values_num < 1) || (isnan(values[0]))) {
480 printf("WARNING: The first value is not defined\n");
481 return (RET_WARNING);
482 }
484 for (size_t i = 0; i < values_num; i++) {
485 if (isnan(values[i])) {
486 if (!nan_is_error_g)
487 continue;
489 printf("CRITICAL: Data source \"%s\" is NaN\n", values_names[i]);
490 return (RET_CRITICAL);
491 }
493 sum += values[i];
494 }
496 if (sum == 0.0) {
497 printf("WARNING: Values sum up to zero\n");
498 return (RET_WARNING);
499 }
501 percentage = 100.0 * values[0] / sum;
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 }
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 */
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;
528 snprintf(ident_str, sizeof(ident_str), "%s/%s", hostname_g, value_string_g);
529 ident_str[sizeof(ident_str) - 1] = 0;
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 }
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 }
547 LCC_DESTROY(connection);
549 status = filter_ds(&values_num, &values, &values_names);
550 if (status != RET_OKAY)
551 return (status);
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);
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);
569 return (status);
570 } /* int do_check */
572 int main(int argc, char **argv) {
573 char address[1024];
574 lcc_connection_t *connection;
576 int status;
578 range_critical_g.min = NAN;
579 range_critical_g.max = NAN;
580 range_critical_g.invert = 0;
582 range_warning_g.min = NAN;
583 range_warning_g.max = NAN;
584 range_warning_g.invert = 0;
586 while (42) {
587 int c;
589 c = getopt(argc, argv, "w:c:s:n:H:g:d:hm");
590 if (c < 0)
591 break;
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 }
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 }
653 snprintf(address, sizeof(address), "unix:%s", socket_file_g);
654 address[sizeof(address) - 1] = 0;
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 }
663 if (0 == strcasecmp(value_string_g, "LIST"))
664 return (do_listval(connection));
666 return (do_check(connection));
667 } /* int main */