{GPL, other}: Relicense to MIT license.
[collectd.git] / src / collectdctl.c
index f697e5f..2329285 100644 (file)
 # include "config.h"
 #endif
 
-#include "libcollectdclient/client.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
 
 #include <assert.h>
-
 #include <errno.h>
 
-#include <getopt.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include <unistd.h>
+#if NAN_STATIC_DEFAULT
+# include <math.h>
+/* #endif NAN_STATIC_DEFAULT*/
+#elif NAN_STATIC_ISOC
+# ifndef __USE_ISOC99
+#  define DISABLE_ISOC99 1
+#  define __USE_ISOC99 1
+# endif /* !defined(__USE_ISOC99) */
+# include <math.h>
+# if DISABLE_ISOC99
+#  undef DISABLE_ISOC99
+#  undef __USE_ISOC99
+# endif /* DISABLE_ISOC99 */
+/* #endif NAN_STATIC_ISOC */
+#elif NAN_ZERO_ZERO
+# include <math.h>
+# ifdef NAN
+#  undef NAN
+# endif
+# define NAN (0.0 / 0.0)
+# 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/collectd/client.h"
 
 #define DEFAULT_SOCK LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
 
@@ -59,6 +87,7 @@ static void exit_usage (const char *name, int status) {
       " * getval <identifier>\n"
       " * flush [timeout=<seconds>] [plugin=<name>] [identifier=<id>]\n"
       " * listval\n"
+      " * putval <identifier> [interval=<seconds>] <value-list(s)>\n"
 
       "\nIdentifiers:\n\n"
 
@@ -69,15 +98,8 @@ static void exit_usage (const char *name, int status) {
       "Hostname defaults to the local hostname if omitted (e.g., uptime/uptime).\n"
       "No error is returned if the specified identifier does not exist.\n"
 
-      "\nExample:\n\n"
-
-      "  collectdctl flush plugin=rrdtool identifie=somehost/cpu-0/cpu-wait\n\n"
-
-      "Flushes all CPU wait RRD values of the first CPU of the local host.\n"
-      "I.e., writes all pending RRD updates of that data-source to disk.\n"
-
       "\n"PACKAGE" "VERSION", http://collectd.org/\n"
-      "by Florian octo Forster <octo@verplant.org>\n"
+      "by Florian octo Forster <octo@collectd.org>\n"
       "for contributions see `AUTHORS'\n"
       , name);
   exit (status);
@@ -98,6 +120,23 @@ static int count_chars (const char *str, char chr) {
   return count;
 } /* count_chars */
 
+static int array_grow (void **array, int *array_len, size_t elem_size)
+{
+  void *tmp;
+
+  assert ((array != NULL) && (array_len != NULL));
+
+  tmp = realloc (*array, (*array_len + 1) * elem_size);
+  if (tmp == NULL) {
+    fprintf (stderr, "ERROR: Failed to allocate memory.\n");
+    return (-1);
+  }
+
+  *array = tmp;
+  ++(*array_len);
+  return (0);
+} /* array_grow */
+
 static int parse_identifier (lcc_connection_t *c,
     const char *value, lcc_identifier_t *ident)
 {
@@ -182,21 +221,35 @@ static int getval (lcc_connection_t *c, int argc, char **argv)
   for (i = 0; i < ret_values_num; ++i)
     printf ("%s=%e\n", ret_values_names[i], ret_values[i]);
   BAIL_OUT (0);
+#undef BAIL_OUT
 } /* getval */
 
 static int flush (lcc_connection_t *c, int argc, char **argv)
 {
-  lcc_identifier_t  ident;
-  lcc_identifier_t *identp = NULL;
+  int timeout = -1;
 
-  char *plugin  = NULL;
-  int   timeout = -1;
+  lcc_identifier_t *identifiers = NULL;
+  int identifiers_num = 0;
+
+  char **plugins = NULL;
+  int plugins_num = 0;
 
   int status;
   int i;
 
   assert (strcasecmp (argv[0], "flush") == 0);
 
+#define BAIL_OUT(s) \
+  do { \
+    if (identifiers != NULL) \
+      free (identifiers); \
+    identifiers_num = 0; \
+    if (plugins != NULL) \
+      free (plugins); \
+    plugins_num = 0; \
+    return (s); \
+  } while (0)
+
   for (i = 1; i < argc; ++i) {
     char *key, *value;
 
@@ -205,7 +258,7 @@ static int flush (lcc_connection_t *c, int argc, char **argv)
 
     if (! value) {
       fprintf (stderr, "ERROR: flush: Invalid option ``%s''.\n", argv[i]);
-      return (-1);
+      BAIL_OUT (-1);
     }
 
     *value = '\0';
@@ -214,12 +267,12 @@ static int flush (lcc_connection_t *c, int argc, char **argv)
     if (strcasecmp (key, "timeout") == 0) {
       char *endptr = NULL;
 
-      timeout = strtol (value, &endptr, 0);
+      timeout = (int) strtol (value, &endptr, 0);
 
       if (endptr == value) {
         fprintf (stderr, "ERROR: Failed to parse timeout as number: %s.\n",
             value);
-        return (-1);
+        BAIL_OUT (-1);
       }
       else if ((endptr != NULL) && (*endptr != '\0')) {
         fprintf (stderr, "WARNING: Ignoring trailing garbage after timeout: "
@@ -227,27 +280,66 @@ static int flush (lcc_connection_t *c, int argc, char **argv)
       }
     }
     else if (strcasecmp (key, "plugin") == 0) {
-      plugin = value;
+      status = array_grow ((void *)&plugins, &plugins_num,
+          sizeof (*plugins));
+      if (status != 0)
+        BAIL_OUT (status);
+
+      plugins[plugins_num - 1] = value;
     }
     else if (strcasecmp (key, "identifier") == 0) {
-      int status;
+      status = array_grow ((void *)&identifiers, &identifiers_num,
+          sizeof (*identifiers));
+      if (status != 0)
+        BAIL_OUT (status);
 
-      memset (&ident, 0, sizeof (ident));
-      status = parse_identifier (c, value, &ident);
+      memset (identifiers + (identifiers_num - 1), 0, sizeof (*identifiers));
+      status = parse_identifier (c, value,
+          identifiers + (identifiers_num - 1));
       if (status != 0)
-        return (status);
-      identp = &ident;
+        BAIL_OUT (status);
+    }
+    else {
+      fprintf (stderr, "ERROR: flush: Unknown option `%s'.\n", key);
+      BAIL_OUT (-1);
     }
   }
 
-  status = lcc_flush (c, plugin, identp, timeout);
-  if (status != 0) {
-    fprintf (stderr, "ERROR: Flushing failed: %s.\n",
-        lcc_strerror (c));
-    return (-1);
+  if (plugins_num == 0) {
+    status = array_grow ((void *)&plugins, &plugins_num, sizeof (*plugins));
+    if (status != 0)
+      BAIL_OUT (status);
+
+    assert (plugins_num == 1);
+    plugins[0] = NULL;
   }
 
-  return 0;
+  for (i = 0; i < plugins_num; ++i) {
+    if (identifiers_num == 0) {
+      status = lcc_flush (c, plugins[i], NULL, timeout);
+      if (status != 0)
+        fprintf (stderr, "ERROR: Failed to flush plugin `%s': %s.\n",
+            (plugins[i] == NULL) ? "(all)" : plugins[i], lcc_strerror (c));
+    }
+    else {
+      int j;
+
+      for (j = 0; j < identifiers_num; ++j) {
+        status = lcc_flush (c, plugins[i], identifiers + j, timeout);
+        if (status != 0) {
+          char id[1024];
+
+          lcc_identifier_to_string (c, id, sizeof (id), identifiers + j);
+          fprintf (stderr, "ERROR: Failed to flush plugin `%s', "
+              "identifier `%s': %s.\n",
+              (plugins[i] == NULL) ? "(all)" : plugins[i],
+              id, lcc_strerror (c));
+        }
+      }
+    }
+  }
+
+  BAIL_OUT (0);
 #undef BAIL_OUT
 } /* flush */
 
@@ -296,6 +388,161 @@ static int listval (lcc_connection_t *c, int argc, char **argv)
 #undef BAIL_OUT
 } /* listval */
 
+static int putval (lcc_connection_t *c, int argc, char **argv)
+{
+  lcc_value_list_t vl = LCC_VALUE_LIST_INIT;
+
+  /* 64 ought to be enough for anybody ;-) */
+  value_t values[64];
+  int     values_types[64];
+  size_t  values_len = 0;
+
+  int status;
+  int i;
+
+  assert (strcasecmp (argv[0], "putval") == 0);
+
+  if (argc < 3) {
+    fprintf (stderr, "ERROR: putval: Missing identifier "
+        "and/or value list.\n");
+    return (-1);
+  }
+
+  vl.values       = values;
+  vl.values_types = values_types;
+
+  status = parse_identifier (c, argv[1], &vl.identifier);
+  if (status != 0)
+    return (status);
+
+  for (i = 2; i < argc; ++i) {
+    char *tmp;
+
+    tmp = strchr (argv[i], (int)'=');
+
+    if (tmp != NULL) { /* option */
+      char *key   = argv[i];
+      char *value = tmp;
+
+      *value = '\0';
+      ++value;
+
+      if (strcasecmp (key, "interval") == 0) {
+        char *endptr;
+
+        vl.interval = strtol (value, &endptr, 0);
+
+        if (endptr == value) {
+          fprintf (stderr, "ERROR: Failed to parse interval as number: %s.\n",
+              value);
+          return (-1);
+        }
+        else if ((endptr != NULL) && (*endptr != '\0')) {
+          fprintf (stderr, "WARNING: Ignoring trailing garbage after "
+              "interval: %s.\n", endptr);
+        }
+      }
+      else {
+        fprintf (stderr, "ERROR: putval: Unknown option `%s'.\n", key);
+        return (-1);
+      }
+    }
+    else { /* value list */
+      char *value;
+
+      tmp = strchr (argv[i], (int)':');
+
+      if (tmp == NULL) {
+        fprintf (stderr, "ERROR: putval: Invalid value list: %s.\n",
+            argv[i]);
+        return (-1);
+      }
+
+      *tmp = '\0';
+      ++tmp;
+
+      if (strcasecmp (argv[i], "N") == 0) {
+        vl.time = 0;
+      }
+      else {
+        char *endptr;
+
+        vl.time = strtol (argv[i], &endptr, 0);
+
+        if (endptr == argv[i]) {
+          fprintf (stderr, "ERROR: Failed to parse time as number: %s.\n",
+              argv[i]);
+          return (-1);
+        }
+        else if ((endptr != NULL) && (*endptr != '\0')) {
+          fprintf (stderr, "ERROR: Garbage after time: %s.\n", endptr);
+          return (-1);
+        }
+      }
+
+      values_len = 0;
+      value = tmp;
+      while (value != 0) {
+        char *dot, *endptr;
+
+        tmp = strchr (value, (int)':');
+
+        if (tmp != NULL) {
+          *tmp = '\0';
+          ++tmp;
+        }
+
+        /* This is a bit of a hack, but parsing types.db just does not make
+         * much sense imho -- the server might have different types defined
+         * anyway. Also, lcc uses the type information for formatting the
+         * number only, so the real meaning does not matter. -tokkee */
+        dot = strchr (value, (int)'.');
+        endptr = NULL;
+        if (strcasecmp (value, "U") == 0) {
+          values[values_len].gauge = NAN;
+          values_types[values_len] = LCC_TYPE_GAUGE;
+        }
+        else if (dot) { /* floating point value */
+          values[values_len].gauge = strtod (value, &endptr);
+          values_types[values_len] = LCC_TYPE_GAUGE;
+        }
+        else { /* integer */
+          values[values_len].counter = strtol (value, &endptr, 0);
+          values_types[values_len] = LCC_TYPE_COUNTER;
+        }
+        ++values_len;
+
+        if (endptr == value) {
+          fprintf (stderr, "ERROR: Failed to parse value as number: %s.\n",
+              argv[i]);
+          return (-1);
+        }
+        else if ((endptr != NULL) && (*endptr != '\0')) {
+          fprintf (stderr, "ERROR: Garbage after value: %s.\n", endptr);
+          return (-1);
+        }
+
+        value = tmp;
+      }
+
+      assert (values_len >= 1);
+      vl.values_len = values_len;
+
+      status = lcc_putval (c, &vl);
+      if (status != 0) {
+        fprintf (stderr, "ERROR: %s\n", lcc_strerror (c));
+        return (-1);
+      }
+    }
+  }
+
+  if (values_len == 0) {
+    fprintf (stderr, "ERROR: putval: Missing value list(s).\n");
+    return (-1);
+  }
+  return (0);
+} /* putval */
+
 int main (int argc, char **argv) {
   char address[1024] = "unix:"DEFAULT_SOCK;
 
@@ -343,6 +590,8 @@ int main (int argc, char **argv) {
     status = flush (c, argc - optind, argv + optind);
   else if (strcasecmp (argv[optind], "listval") == 0)
     status = listval (c, argc - optind, argv + optind);
+  else if (strcasecmp (argv[optind], "putval") == 0)
+    status = putval (c, argc - optind, argv + optind);
   else {
     fprintf (stderr, "%s: invalid command: %s\n", argv[0], argv[optind]);
     return (1);