src/collectd{ctl,-nagios}.c: Remove the standards-flags again.
[collectd.git] / src / collectd-nagios.c
index 1bf5b3f..af8744d 100644 (file)
@@ -1,18 +1,40 @@
-#include "config.h"
+/**
+ * collectd-nagios - src/collectd-nagios.c
+ * Copyright (C) 2008-2010  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#if !defined(__GNUC__) || !__GNUC__
+# define __attribute__(x) /**/
+#endif
 
 #include <stdlib.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
+#include <strings.h>
 #include <assert.h>
 
-#include <sys/socket.h>
-#include <sys/un.h>
-
-/*
- * This is copied directly from collectd.h. Make changes there!
- */
 #if NAN_STATIC_DEFAULT
 # include <math.h>
 /* #endif NAN_STATIC_DEFAULT*/
 # ifndef isnan
 #  define isnan(f) ((f) != (f))
 # endif /* !defined(isnan) */
+# ifndef isfinite
+#  define isfinite(f) (((f) - (f)) == 0.0)
+# endif
+# ifndef isinf
+#  define isinf(f) (!isfinite(f) && !isnan(f))
+# endif
 #endif /* NAN_ZERO_ZERO */
 
+#include "libcollectdclient/client.h"
+
 #define RET_OKAY     0
 #define RET_WARNING  1
 #define RET_CRITICAL 2
@@ -46,6 +76,7 @@
 #define CON_NONE     0
 #define CON_AVERAGE  1
 #define CON_SUM      2
+#define CON_PERCENTAGE  3
 
 struct range_s
 {
@@ -65,23 +96,96 @@ static char *hostname_g = NULL;
 static range_t range_critical_g;
 static range_t range_warning_g;
 static int consolitation_g = CON_NONE;
+static _Bool nan_is_error_g = 0;
 
 static char **match_ds_g = NULL;
 static int    match_ds_num_g = 0;
 
-static int ignore_ds (const char *name)
+/* `strdup' is an XSI extension. I don't want to pull in all of XSI just for
+ * that, so here's an own implementation.. It's easy enough. The GCC attributes
+ * are supposed to get good performance..  -octo */
+__attribute__((malloc, nonnull (1)))
+static char *cn_strdup (const char *str) /* {{{ */
+{
+  size_t strsize;
+  char *ret;
+
+  strsize = strlen (str) + 1;
+  ret = (char *) malloc (strsize);
+  if (ret != NULL)
+    memcpy (ret, str, strsize);
+  return (ret);
+} /* }}} char *cn_strdup */
+
+static int filter_ds (size_t *values_num,
+               double **values, char ***values_names)
 {
-       int i;
+       gauge_t *new_values;
+       char   **new_names;
+
+       size_t i;
 
        if (match_ds_g == NULL)
-               return (0);
+               return (RET_OKAY);
+
+       new_values = (gauge_t *)calloc (match_ds_num_g, sizeof (*new_values));
+       if (new_values == NULL)
+       {
+               fprintf (stderr, "malloc failed: %s\n", strerror (errno));
+               return (RET_UNKNOWN);
+       }
 
-       for (i = 0; i < match_ds_num_g; i++)
-               if (strcasecmp (match_ds_g[i], name) == 0)
-                       return (0);
+       new_names = (char **)calloc (match_ds_num_g, sizeof (*new_names));
+       if (new_names == NULL)
+       {
+               fprintf (stderr, "malloc failed: %s\n", strerror (errno));
+               free (new_values);
+               return (RET_UNKNOWN);
+       }
 
-       return (1);
-} /* int ignore_ds */
+       for (i = 0; i < (size_t) match_ds_num_g; i++)
+       {
+               size_t j;
+
+               /* match_ds_g keeps pointers into argv but the names will be freed */
+               new_names[i] = cn_strdup (match_ds_g[i]);
+               if (new_names[i] == NULL)
+               {
+                       fprintf (stderr, "cn_strdup failed: %s\n", strerror (errno));
+                       free (new_values);
+                       for (j = 0; j < i; j++)
+                               free (new_names[j]);
+                       free (new_names);
+                       return (RET_UNKNOWN);
+               }
+
+               for (j = 0; j < *values_num; j++)
+                       if (strcasecmp (new_names[i], (*values_names)[j]) == 0)
+                               break;
+
+               if (j == *values_num)
+               {
+                       printf ("ERROR: DS `%s' is not available.\n", new_names[i]);
+                       free (new_values);
+                       for (j = 0; j <= i; j++)
+                               free (new_names[j]);
+                       free (new_names);
+                       return (RET_CRITICAL);
+               }
+
+               new_values[i] = (*values)[j];
+       }
+
+       free (*values);
+       for (i = 0; i < *values_num; i++)
+               free ((*values_names)[i]);
+       free (*values_names);
+
+       *values       = new_values;
+       *values_names = new_names;
+       *values_num   = match_ds_num_g;
+       return (RET_OKAY);
+} /* int filter_ds */
 
 static void parse_range (char *string, range_t *range)
 {
@@ -124,7 +228,7 @@ static void parse_range (char *string, range_t *range)
                range->max = atof (max_ptr);
 } /* void parse_range */
 
-int match_range (range_t *range, double value)
+static int match_range (range_t *range, double value)
 {
        int ret = 0;
 
@@ -134,136 +238,7 @@ int match_range (range_t *range, double value)
                ret = 1;
 
        return (((ret - range->invert) == 0) ? 0 : 1);
-}
-
-static int get_values (int *ret_values_num, double **ret_values,
-               char ***ret_values_names)
-{
-       struct sockaddr_un sa;
-       int status;
-       int fd;
-       FILE *fh_in, *fh_out;
-       char buffer[4096];
-
-       int values_num;
-       double *values;
-       char **values_names;
-
-       int i;
-
-       fd = socket (PF_UNIX, SOCK_STREAM, 0);
-       if (fd < 0)
-       {
-               fprintf (stderr, "socket failed: %s\n",
-                               strerror (errno));
-               return (-1);
-       }
-
-       memset (&sa, '\0', sizeof (sa));
-       sa.sun_family = AF_UNIX;
-       strncpy (sa.sun_path, socket_file_g,
-                       sizeof (sa.sun_path) - 1);
-
-       status = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
-       if (status != 0)
-       {
-               fprintf (stderr, "connect failed: %s\n",
-                               strerror (errno));
-               return (-1);
-       }
-
-       fh_in = fdopen (fd, "r");
-       if (fh_in == NULL)
-       {
-               fprintf (stderr, "fdopen failed: %s\n",
-                               strerror (errno));
-               close (fd);
-               return (-1);
-       }
-
-       fh_out = fdopen (fd, "w");
-       if (fh_out == NULL)
-       {
-               fprintf (stderr, "fdopen failed: %s\n",
-                               strerror (errno));
-               fclose (fh_in);
-               return (-1);
-       }
-
-       fprintf (fh_out, "GETVAL %s/%s\n", hostname_g, value_string_g);
-       fflush (fh_out);
-
-       if (fgets (buffer, sizeof (buffer), fh_in) == NULL)
-       {
-               fprintf (stderr, "fgets failed: %s\n",
-                               strerror (errno));
-               fclose (fh_in);
-               fclose (fh_out);
-               return (-1);
-       }
-
-       {
-               char *ptr = strchr (buffer, ' ');
-
-               if (ptr != NULL)
-                       *ptr = '\0';
-
-               values_num = atoi (buffer);
-               if (values_num < 1)
-                       return (-1);
-       }
-
-       values = (double *) malloc (values_num * sizeof (double));
-       if (values == NULL)
-       {
-               fprintf (stderr, "malloc failed: %s\n",
-                               strerror (errno));
-               return (-1);
-       }
-
-       values_names = (char **) malloc (values_num * sizeof (char *));
-       if (values_names == NULL)
-       {
-               fprintf (stderr, "malloc failed: %s\n",
-                               strerror (errno));
-               free (values);
-               return (-1);
-       }
-
-       i = 0;
-       while (fgets (buffer, sizeof (buffer), fh_in) != NULL)
-       {
-               char *key;
-               char *value;
-
-               key = buffer;
-
-               value = strchr (key, '=');
-               if (value == NULL)
-                       continue;
-               *value = '\0'; value++;
-
-               if (ignore_ds (key) != 0)
-                       continue;
-
-               values_names[i] = strdup (key);
-               values[i] = atof (value);
-
-               i++;
-               if (i >= values_num)
-                       break;
-       }
-       values_num = i;
-
-       fclose (fh_in); fh_in = NULL; fd = -1;
-       fclose (fh_out); fh_out = NULL;
-
-       *ret_values_num = values_num;
-       *ret_values = values;
-       *ret_values_names = values_names;
-
-       return (0);
-} /* int get_values */
+} /* int match_range */
 
 static void usage (const char *name)
 {
@@ -276,10 +251,11 @@ static void usage (const char *name)
                        "  -d <ds>        Select the DS to examine. May be repeated to examine multiple\n"
                        "                 DSes. By default all DSes are used.\n"
                        "  -g <consol>    Method to use to consolidate several DSes.\n"
-                       "                 Valid arguments are `none', `average' and `sum'\n"
+                       "                 See below for a list of valid arguments.\n"
                        "  -H <host>      Hostname to query the values for.\n"
                        "  -c <range>     Critical range\n"
                        "  -w <range>     Warning range\n"
+                       "  -m             Treat \"Not a Number\" (NaN) as critical (default: warning)\n"
                        "\n"
                        "Consolidation functions:\n"
                        "  none:          Apply the warning- and critical-ranges to each data-source\n"
@@ -287,22 +263,31 @@ static void usage (const char *name)
                        "  average:       Calculate the average of all matching DSes and apply the\n"
                        "                 warning- and critical-ranges to the calculated average.\n"
                        "  sum:           Apply the ranges to the sum of all DSes.\n"
+                       "  percentage:    Apply the ranges to the ratio (in percent) of the first value\n"
+                       "                 and the sum of all values."
                        "\n", name);
        exit (1);
 } /* void usage */
 
-int do_check_con_none (int values_num, double *values, char **values_names)
+static int do_check_con_none (size_t values_num,
+               double *values, char **values_names)
 {
-       int i;
-
        int num_critical = 0;
        int num_warning  = 0;
        int num_okay = 0;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
+       size_t i;
 
        for (i = 0; i < values_num; i++)
        {
                if (isnan (values[i]))
-                       num_warning++;
+               {
+                       if (nan_is_error_g)
+                               num_critical++;
+                       else
+                               num_warning++;
+               }
                else if (match_range (&range_critical_g, values[i]) != 0)
                        num_critical++;
                else if (match_range (&range_warning_g, values[i]) != 0)
@@ -311,77 +296,125 @@ int do_check_con_none (int values_num, double *values, char **values_names)
                        num_okay++;
        }
 
-       printf ("%i critical, %i warning, %i okay",
+       if ((num_critical == 0) && (num_warning == 0) && (num_okay == 0))
+       {
+               printf ("WARNING: No defined values found\n");
+               return (RET_WARNING);
+       }
+       else if ((num_critical == 0) && (num_warning == 0))
+       {
+               status_str = "OKAY";
+               status_code = RET_OKAY;
+       }
+       else if (num_critical == 0)
+       {
+               status_str = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+
+       printf ("%s: %i critical, %i warning, %i okay", status_str,
                        num_critical, num_warning, num_okay);
        if (values_num > 0)
        {
                printf (" |");
                for (i = 0; i < values_num; i++)
-                       printf (" %s=%lf;;;;", values_names[i], values[i]);
+                       printf (" %s=%f;;;;", values_names[i], values[i]);
        }
        printf ("\n");
 
-       if ((num_critical != 0) || (values_num == 0))
-               return (RET_CRITICAL);
-       else if (num_warning != 0)
-               return (RET_WARNING);
-
-       return (RET_OKAY);
+       return (status_code);
 } /* int do_check_con_none */
 
-int do_check_con_average (int values_num, double *values, char **values_names)
+static int do_check_con_average (size_t values_num,
+               double *values, char **values_names)
 {
-       int i;
+       size_t i;
        double total;
        int total_num;
        double average;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
 
        total = 0.0;
        total_num = 0;
        for (i = 0; i < values_num; i++)
        {
-               if (!isnan (values[i]))
+               if (isnan (values[i]))
                {
-                       total += values[i];
-                       total_num++;
+                       if (!nan_is_error_g)
+                               continue;
+
+                       printf ("CRITICAL: Data source \"%s\" is NaN\n",
+                                       values_names[i]);
+                       return (RET_CRITICAL);
                }
-       }
 
-       if (total_num == 0)
-               average = NAN;
-       else
-               average = total / total_num;
-       printf ("%lf average |", average);
-       for (i = 0; i < values_num; i++)
-               printf (" %s=%lf;;;;", values_names[i], values[i]);
+               total += values[i];
+               total_num++;
+       }
 
        if (total_num == 0)
+       {
+               printf ("WARNING: No defined values found\n");
                return (RET_WARNING);
+       }
 
-       if (isnan (average)
-                       || match_range (&range_critical_g, average))
-               return (RET_CRITICAL);
+       average = total / total_num;
+
+       if (match_range (&range_critical_g, average) != 0)
+       {
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
        else if (match_range (&range_warning_g, average) != 0)
-               return (RET_WARNING);
+       {
+               status_str = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str = "OKAY";
+               status_code = RET_OKAY;
+       }
 
-       return (RET_OKAY);
+       printf ("%s: %g average |", status_str, average);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%f;;;;", values_names[i], values[i]);
+       printf ("\n");
+
+       return (status_code);
 } /* int do_check_con_average */
 
-int do_check_con_sum (int values_num, double *values, char **values_names)
+static int do_check_con_sum (size_t values_num,
+               double *values, char **values_names)
 {
-       int i;
+       size_t i;
        double total;
        int total_num;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
 
        total = 0.0;
        total_num = 0;
        for (i = 0; i < values_num; i++)
        {
-               if (!isnan (values[i]))
+               if (isnan (values[i]))
                {
-                       total += values[i];
-                       total_num++;
+                       if (!nan_is_error_g)
+                               continue;
+
+                       printf ("CRITICAL: Data source \"%s\" is NaN\n",
+                                       values_names[i]);
+                       return (RET_CRITICAL);
                }
+
+               total += values[i];
+               total_num++;
        }
 
        if (total_num == 0)
@@ -392,47 +425,161 @@ int do_check_con_sum (int values_num, double *values, char **values_names)
 
        if (match_range (&range_critical_g, total) != 0)
        {
-               printf ("CRITICAL: Sum = %lf\n", total);
-               return (RET_CRITICAL);
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
        }
        else if (match_range (&range_warning_g, total) != 0)
        {
-               printf ("WARNING: Sum = %lf\n", total);
-               return (RET_WARNING);
+               status_str = "WARNING";
+               status_code = RET_WARNING;
        }
        else
        {
-               printf ("OKAY: Sum = %lf\n", total);
-               return (RET_OKAY);
+               status_str = "OKAY";
+               status_code = RET_OKAY;
        }
 
-       return (RET_UNKNOWN);
+       printf ("%s: %g sum |", status_str, total);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%f;;;;", values_names[i], values[i]);
+       printf ("\n");
+
+       return (status_code);
 } /* int do_check_con_sum */
 
-int do_check (void)
+static int do_check_con_percentage (size_t values_num,
+               double *values, char **values_names)
+{
+       size_t i;
+       double sum = 0.0;
+       double percentage;
+
+       const char *status_str  = "UNKNOWN";
+       int         status_code = RET_UNKNOWN;
+
+       if ((values_num < 1) || (isnan (values[0])))
+       {
+               printf ("WARNING: The first value is not defined\n");
+               return (RET_WARNING);
+       }
+
+       for (i = 0; i < values_num; i++)
+       {
+               if (isnan (values[i]))
+               {
+                       if (!nan_is_error_g)
+                               continue;
+
+                       printf ("CRITICAL: Data source \"%s\" is NaN\n",
+                                       values_names[i]);
+                       return (RET_CRITICAL);
+               }
+
+               sum += values[i];
+       }
+
+       if (sum == 0.0)
+       {
+               printf ("WARNING: Values sum up to zero\n");
+               return (RET_WARNING);
+       }
+
+       percentage = 100.0 * values[0] / sum;
+
+       if (match_range (&range_critical_g, percentage) != 0)
+       {
+               status_str  = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+       else if (match_range (&range_warning_g, percentage) != 0)
+       {
+               status_str  = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str  = "OKAY";
+               status_code = RET_OKAY;
+       }
+
+       printf ("%s: %lf percent |", status_str, percentage);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%lf;;;;", values_names[i], values[i]);
+       return (status_code);
+} /* int do_check_con_percentage */
+
+static int do_check (void)
 {
-       double  *values;
+       lcc_connection_t *connection;
+       gauge_t *values;
        char   **values_names;
-       int      values_num;
+       size_t   values_num;
+       char address[1024];
+       char ident_str[1024];
+       lcc_identifier_t ident;
+       size_t i;
+       int status;
+
+       snprintf (address, sizeof (address), "unix:%s", socket_file_g);
+       address[sizeof (address) - 1] = 0;
+
+       snprintf (ident_str, sizeof (ident_str), "%s/%s",
+                       hostname_g, value_string_g);
+       ident_str[sizeof (ident_str) - 1] = 0;
+
+       connection = NULL;
+       status = lcc_connect (address, &connection);
+       if (status != 0)
+       {
+               printf ("ERROR: Connecting to daemon at %s failed.\n",
+                               socket_file_g);
+               return (RET_CRITICAL);
+       }
+
+       memset (&ident, 0, sizeof (ident));
+       status = lcc_string_to_identifier (connection, &ident, ident_str);
+       if (status != 0)
+       {
+               printf ("ERROR: Creating an identifier failed: %s.\n",
+                               lcc_strerror (connection));
+               LCC_DESTROY (connection);
+               return (RET_CRITICAL);
+       }
 
-       if (get_values (&values_num, &values, &values_names) != 0)
+       status = lcc_getval (connection, &ident,
+                       &values_num, &values, &values_names);
+       if (status != 0)
        {
-               fputs ("ERROR: Cannot get values from daemon\n", stdout);
+               printf ("ERROR: Retrieving values from the daemon failed: %s.\n",
+                               lcc_strerror (connection));
+               LCC_DESTROY (connection);
                return (RET_CRITICAL);
        }
 
+       LCC_DESTROY (connection);
+
+       status = filter_ds (&values_num, &values, &values_names);
+       if (status != RET_OKAY)
+               return (status);
+
+       status = RET_UNKNOWN;
        if (consolitation_g == CON_NONE)
-               return (do_check_con_none (values_num, values, values_names));
+               status =  do_check_con_none (values_num, values, values_names);
        else if (consolitation_g == CON_AVERAGE)
-               return (do_check_con_average (values_num, values, values_names));
+               status =  do_check_con_average (values_num, values, values_names);
        else if (consolitation_g == CON_SUM)
-               return (do_check_con_sum (values_num, values, values_names));
+               status = do_check_con_sum (values_num, values, values_names);
+       else if (consolitation_g == CON_PERCENTAGE)
+               status = do_check_con_percentage (values_num, values, values_names);
 
        free (values);
-       free (values_names); /* FIXME? */
+       if (values_names != NULL)
+               for (i = 0; i < values_num; i++)
+                       free (values_names[i]);
+       free (values_names);
 
-       return (RET_UNKNOWN);
-}
+       return (status);
+} /* int do_check */
 
 int main (int argc, char **argv)
 {
@@ -448,7 +595,7 @@ int main (int argc, char **argv)
        {
                int c;
 
-               c = getopt (argc, argv, "w:c:s:n:H:g:d:h");
+               c = getopt (argc, argv, "w:c:s:n:H:g:d:hm");
                if (c < 0)
                        break;
 
@@ -476,8 +623,14 @@ int main (int argc, char **argv)
                                        consolitation_g = CON_AVERAGE;
                                else if (strcasecmp (optarg, "sum") == 0)
                                        consolitation_g = CON_SUM;
+                               else if (strcasecmp (optarg, "percentage") == 0)
+                                       consolitation_g = CON_PERCENTAGE;
                                else
+                               {
+                                       fprintf (stderr, "Unknown consolidation function `%s'.\n",
+                                                       optarg);
                                        usage (argv[0]);
+                               }
                                break;
                        case 'd':
                        {
@@ -492,16 +645,19 @@ int main (int argc, char **argv)
                                        return (RET_UNKNOWN);
                                }
                                match_ds_g = tmp;
-                               match_ds_g[match_ds_num_g] = strdup (optarg);
+                               match_ds_g[match_ds_num_g] = cn_strdup (optarg);
                                if (match_ds_g[match_ds_num_g] == NULL)
                                {
-                                       fprintf (stderr, "strdup failed: %s\n",
+                                       fprintf (stderr, "cn_strdup failed: %s\n",
                                                        strerror (errno));
                                        return (RET_UNKNOWN);
                                }
                                match_ds_num_g++;
                                break;
                        }
+                       case 'm':
+                               nan_is_error_g = 1;
+                               break;
                        default:
                                usage (argv[0]);
                } /* switch (c) */
@@ -509,7 +665,10 @@ int main (int argc, char **argv)
 
        if ((socket_file_g == NULL) || (value_string_g == NULL)
                        || (hostname_g == NULL))
+       {
+               fprintf (stderr, "Missing required arguments.\n");
                usage (argv[0]);
+       }
 
        return (do_check ());
 } /* int main */