Code

Merge branch 'ad/redis'
authorFlorian Forster <octo@leeloo.lan.home.verplant.org>
Tue, 17 Aug 2010 16:43:37 +0000 (18:43 +0200)
committerFlorian Forster <octo@leeloo.lan.home.verplant.org>
Tue, 17 Aug 2010 16:43:37 +0000 (18:43 +0200)
.gitignore
README
configure.in
src/Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/redis.c [new file with mode: 0644]
src/types.db
src/write_redis.c [new file with mode: 0644]

index e8f9af66d7879336910ff56dd76f8b70d796dd23..cbdd62f821579ae7369bbd1931b9bc4ef0bd60f2 100644 (file)
@@ -65,3 +65,9 @@ bindings/java/org/collectd/java/*.class
 
 # python stuff
 *.pyc
+
+# tag stuff
+src/tags
+
+# backup stuff
+*~
diff --git a/README b/README
index 8d4d275d568cc1206eca148945ebfd59d3e06cba..0c7a4221369fb496cbd79e87cd22031886cc3427 100644 (file)
--- a/README
+++ b/README
@@ -235,6 +235,10 @@ Features
       collectd without the need to start a heavy interpreter every interval.
       See collectd-python(5) for details.
 
+    - redis
+      The redis plugin gathers information from a redis server, including:
+      uptime, used memory, total connections etc.
+
     - routeros
       Query interface and wireless registration statistics from RouterOS.
 
@@ -507,6 +511,10 @@ Prerequisites
   * libclntsh (optional)
     Used by the `oracle' plugin.
 
+  * libcredis (optional)
+    Used by the redis plugin. Please note that you require a 0.2.2 version
+    or higher. <http://code.google.com/p/credis/>
+
   * libcurl (optional)
     If you want to use the `apache', `ascent', `curl', `nginx', or `write_http'
     plugin.
index fc12c0885787dd209b851f7f3fcc541fda364441..8ebf124447cd89225c894dd392f43999dcfae226 100644 (file)
@@ -1279,6 +1279,64 @@ then
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBKVM_OPENFILES, test "x$with_kvm_openfiles" = "xyes")
 
+# --with-libcredis {{{
+AC_ARG_WITH(libcredis, [AS_HELP_STRING([--with-libcredis@<:@=PREFIX@:>@], [Path to libcredis.])],
+[
+ if test "x$withval" = "xyes"
+ then
+        with_libcredis="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_libcredis="no"
+ else
+        with_libcredis="yes"
+        LIBCREDIS_CPPFLAGS="$LIBCREDIS_CPPFLAGS -I$withval/include"
+        LIBCREDIS_LDFLAGS="$LIBCREDIS_LDFLAGS -L$withval/lib"
+ fi; fi
+],
+[with_libcredis="yes"])
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+
+CPPFLAGS="$CPPFLAGS $LIBCREDIS_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBCREDIS_LDFLAGS"
+
+if test "x$with_libcredis" = "xyes"
+then
+       if test "x$LIBCREDIS_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([libcredis CPPFLAGS: $LIBCREDIS_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(credis.h,
+       [with_libcredis="yes"],
+       [with_libcredis="no ('credis.h' not found)"])
+fi
+if test "x$with_libcredis" = "xyes"
+then
+       if test "x$LIBCREDIS_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([libcredis LDFLAGS: $LIBCREDIS_LDFLAGS])
+       fi
+       AC_CHECK_LIB(credis, credis_info,
+       [with_libcredis="yes"],
+       [with_libcredis="no (symbol 'credis_info' not found)"])
+
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_libcredis" = "xyes"
+then
+       BUILD_WITH_LIBCREDIS_CPPFLAGS="$LIBCREDIS_CPPFLAGS"
+       BUILD_WITH_LIBCREDIS_LDFLAGS="$LIBCREDIS_LDFLAGS"
+       AC_SUBST(BUILD_WITH_LIBCREDIS_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBCREDIS_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBCREDIS, test "x$with_libcredis" = "xyes")
+# }}}
+
 # --with-libcurl {{{
 with_curl_config="curl-config"
 with_curl_cflags=""
@@ -4472,6 +4530,7 @@ AC_PLUGIN([powerdns],    [yes],                [PowerDNS statistics])
 AC_PLUGIN([processes],   [$plugin_processes],  [Process statistics])
 AC_PLUGIN([protocols],   [$plugin_protocols],  [Protocol (IP, TCP, ...) statistics])
 AC_PLUGIN([python],      [$with_python],       [Embed a Python interpreter])
+AC_PLUGIN([redis],       [$with_libcredis],    [Redis plugin])
 AC_PLUGIN([routeros],    [$with_librouteros],  [RouterOS plugin])
 AC_PLUGIN([rrdcached],   [$librrd_rrdc_update], [RRDTool output plugin])
 AC_PLUGIN([rrdtool],     [$with_librrd],       [RRDTool output plugin])
@@ -4502,6 +4561,7 @@ AC_PLUGIN([vmem],        [$plugin_vmem],       [Virtual memory statistics])
 AC_PLUGIN([vserver],     [$plugin_vserver],    [Linux VServer statistics])
 AC_PLUGIN([wireless],    [$plugin_wireless],   [Wireless statistics])
 AC_PLUGIN([write_http],  [$with_libcurl],      [HTTP output plugin])
+AC_PLUGIN([write_redis], [$with_libcredis],    [Redis output plugin])
 AC_PLUGIN([xmms],        [$with_libxmms],      [XMMS statistics])
 AC_PLUGIN([zfs_arc],     [$plugin_zfs_arc],    [ZFS ARC statistics])
 
@@ -4676,6 +4736,7 @@ Configuration:
   Libraries:
     libcurl . . . . . . . $with_libcurl
     libdbi  . . . . . . . $with_libdbi
+    libcredis . . . . . . $with_libcredis
     libesmtp  . . . . . . $with_libesmtp
     libganglia  . . . . . $with_libganglia
     libgcrypt . . . . . . $with_libgcrypt
@@ -4791,6 +4852,7 @@ Configuration:
     processes . . . . . . $enable_processes
     protocols . . . . . . $enable_protocols
     python  . . . . . . . $enable_python
+    redis . . . . . . . . $enable_redis
     routeros  . . . . . . $enable_routeros
     rrdcached . . . . . . $enable_rrdcached
     rrdtool . . . . . . . $enable_rrdtool
@@ -4821,6 +4883,7 @@ Configuration:
     vserver . . . . . . . $enable_vserver
     wireless  . . . . . . $enable_wireless
     write_http  . . . . . $enable_write_http
+    write_redis . . . . . $enable_write_redis
     xmms  . . . . . . . . $enable_xmms
     zfs_arc . . . . . . . $enable_zfs_arc
 
index 0800629174e9dac91d5963bc711d1fde79a6d2f9..4bcc5ab2ec76745d956711264a70b8adfb2ed447 100644 (file)
@@ -898,6 +898,16 @@ collectd_LDADD += "-dlopen" protocols.la
 collectd_DEPENDENCIES += protocols.la
 endif
 
+if BUILD_PLUGIN_REDIS
+pkglib_LTLIBRARIES += redis.la
+redis_la_SOURCES = redis.c
+redis_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBCREDIS_LDFLAGS)
+redis_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBCREDIS_CPPFLAGS)
+redis_la_LIBADD = -lcredis
+collectd_LDADD += "-dlopen" redis.la
+collectd_DEPENDENCIES += redis.la
+endif
+
 if BUILD_PLUGIN_ROUTEROS
 pkglib_LTLIBRARIES += routeros.la
 routeros_la_SOURCES = routeros.c
@@ -1213,6 +1223,16 @@ endif
 collectd_DEPENDENCIES += write_http.la
 endif
 
+if BUILD_PLUGIN_WRITE_REDIS
+pkglib_LTLIBRARIES += write_redis.la
+write_redis_la_SOURCES = write_redis.c
+write_redis_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBCREDIS_LDFLAGS)
+write_redis_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBCREDIS_CPPFLAGS)
+write_redis_la_LIBADD = -lcredis
+collectd_LDADD += "-dlopen" write_redis.la
+collectd_DEPENDENCIES += write_redis.la
+endif
+
 if BUILD_PLUGIN_XMMS
 pkglib_LTLIBRARIES += xmms.la
 xmms_la_SOURCES = xmms.c
index 9cdecc0254092c32d5c7bd4aad9222e67a22e594..cc125ddf2ebb7fdd88e8431c24dedd330f789959 100644 (file)
 #@BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes
 #@BUILD_PLUGIN_PROTOCOLS_TRUE@LoadPlugin protocols
 #@BUILD_PLUGIN_PYTHON_TRUE@LoadPlugin python
+#@BUILD_PLUGIN_REDIS_TRUE@LoadPlugin redis
 #@BUILD_PLUGIN_ROUTEROS_TRUE@LoadPlugin routeros
 #@BUILD_PLUGIN_RRDCACHED_TRUE@LoadPlugin rrdcached
 @LOAD_PLUGIN_RRDTOOL@LoadPlugin rrdtool
 #@BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver
 #@BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless
 #@BUILD_PLUGIN_WRITE_HTTP_TRUE@LoadPlugin write_http
+#@BUILD_PLUGIN_WRITE_REDIS_TRUE@LoadPlugin write_redis
 #@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
 #@BUILD_PLUGIN_ZFS_ARC_TRUE@LoadPlugin zfs_arc
 
 #      </Module>
 #</Plugin>
 
+#<Plugin redis>
+#   <Node example>
+#      Host "redis.example.com"
+#      Port "6379"
+#      Timeout 2000
+#   </Node>
+#</Plugin>
+
 #<Plugin routeros>
 #      <Router>
 #              Host "router.example.com"
 #      </URL>
 #</Plugin>
 
+#<Plugin write_redis>
+#      <Node "example">
+#              Host "localhost"
+#              Port "6379"
+#              Timeout 1000
+#      </Node>
+#</Plugin>
+
 ##############################################################################
 # Filter configuration                                                       #
 #----------------------------------------------------------------------------#
index 936088eb7df6cda19b4a910706ef15b81eb5210f..483f2c5d7d60e00e09b4c19a021251b793bd9447 100644 (file)
@@ -3724,6 +3724,52 @@ Defaults to B<false>.
 
 =back
 
+=head2 Plugin C<redis>
+
+The I<Redis plugin> connects to one or more Redis servers and gathers
+information about each server's state. For each server there is a I<Node> block
+which configures the connection parameters for this node.
+
+  <Plugin redis>
+    <Node "example">
+        Host "localhost"
+        Port "6379"
+        Timeout 2000
+    </Node>
+  </Plugin>
+
+The information shown in the synopsis above is the I<default configuration>
+which is used by the plugin if no configuration is present.
+
+=over 4
+
+=item B<Node> I<Nodename>
+
+The B<Node> block identifies a new Redis node, that is a new Redis instance
+running in an specified host and port. The name for node is a canonical
+identifier which is used as I<plugin instance>. It is limited to
+64E<nbsp>characters in length.
+
+=item B<Host> I<Hostname>
+
+The B<Host> option is the hostname or IP-address where the Redis instance is
+running on.
+
+=item B<Port> I<Port>
+
+The B<Port> option is the TCP port on which the Redis instance accepts
+connections. Either a service name of a port number may be given. Please note
+that numerical port numbers must be given as a string, too.
+
+=item B<Timeout> I<Timeout in miliseconds>
+
+The B<Timeout> option set the socket timeout for node response. Since the Redis
+read function is blocking, you should keep this value as low as possible. Keep
+in mind that the sum of all B<Timeout> values for all B<Nodes> should be lower
+than B<Interval> defined globally.
+
+=back
+
 =head2 Plugin C<rrdcached>
 
 The C<rrdcached> plugin uses the RRDtool accelerator daemon, L<rrdcached(1)>,
diff --git a/src/redis.c b/src/redis.c
new file mode 100644 (file)
index 0000000..30bd8da
--- /dev/null
@@ -0,0 +1,311 @@
+/**
+ * collectd - src/redis.c, based on src/memcached.c
+ * Copyright (C) 2010       Andrés J. Díaz <ajdiaz@connectical.com>
+ *
+ * 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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * 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:
+ *   Andrés J. Díaz <ajdiaz@connectical.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <pthread.h>
+#include <credis.h>
+
+#define REDIS_DEF_HOST   "localhost"
+#define REDIS_DEF_PORT    6379
+#define REDIS_DEF_TIMEOUT 2000
+#define MAX_REDIS_NODE_NAME 64
+
+/* Redis plugin configuration example:
+ *
+ * <Plugin redis>
+ *   <Node "mynode">
+ *     Host "localhost"
+ *     Port "6379"
+ *     Timeout 2000
+ *   </Node>
+ * </Plugin>
+ */
+
+struct redis_node_s;
+typedef struct redis_node_s redis_node_t;
+struct redis_node_s
+{
+  char name[MAX_REDIS_NODE_NAME];
+  char host[HOST_NAME_MAX];
+  int port;
+  int timeout;
+
+  redis_node_t *next;
+};
+
+static redis_node_t *nodes_head = NULL;
+
+static int redis_node_add (const redis_node_t *rn) /* {{{ */
+{
+  redis_node_t *rn_copy;
+  redis_node_t *rn_ptr;
+
+  /* Check for duplicates first */
+  for (rn_ptr = nodes_head; rn_ptr != NULL; rn_ptr = rn_ptr->next)
+    if (strcmp (rn->name, rn_ptr->name) == 0)
+      break;
+
+  if (rn_ptr != NULL)
+  {
+    ERROR ("redis plugin: A node with the name `%s' already exists.",
+        rn->name);
+    return (-1);
+  }
+
+  rn_copy = malloc (sizeof (*rn_copy));
+  if (rn_copy == NULL)
+  {
+    ERROR ("redis plugin: malloc failed adding redis_node to the tree.");
+    return (-1);
+  }
+
+  memcpy (rn_copy, rn, sizeof (*rn_copy));
+  rn_copy->next = NULL;
+
+  DEBUG ("redis plugin: Adding node \"%s\".", rn->name);
+
+  if (nodes_head == NULL)
+    nodes_head = rn_copy;
+  else
+  {
+    rn_ptr = nodes_head;
+    while (rn_ptr->next != NULL)
+      rn_ptr = rn_ptr->next;
+    rn_ptr->next = rn_copy;
+  }
+
+  return (0);
+} /* }}} */
+
+static int redis_config_node (oconfig_item_t *ci) /* {{{ */
+{
+  redis_node_t rn;
+  int i;
+  int status;
+
+  memset (&rn, 0, sizeof (rn));
+  sstrncpy (rn.host, REDIS_DEF_HOST, sizeof (rn.host));
+  rn.port = REDIS_DEF_PORT;
+  rn.timeout = REDIS_DEF_TIMEOUT;
+
+  status = cf_util_get_string_buffer (ci, rn.name, sizeof (rn.name));
+  if (status != 0)
+    return (status);
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Host", option->key) == 0)
+      status = cf_util_get_string_buffer (option, rn.host, sizeof (rn.host));
+    else if (strcasecmp ("Port", option->key) == 0)
+    {
+      status = cf_util_get_port_number (option);
+      if (status > 0)
+      {
+        rn.port = status;
+        status = 0;
+      }
+    }
+    else if (strcasecmp ("Timeout", option->key) == 0)
+      status = cf_util_get_int (option, &rn.timeout);
+    else
+      WARNING ("redis plugin: Option `%s' not allowed inside a `Node' "
+          "block. I'll ignore this option.", option->key);
+
+    if (status != 0)
+      break;
+  }
+
+  if (status != 0)
+    return (status);
+
+  return (redis_node_add (&rn));
+} /* }}} int redis_config_node */
+
+static int redis_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Node", option->key) == 0)
+      redis_config_node (option);
+    else
+      WARNING ("redis plugin: Option `%s' not allowed in redis"
+          " configuration. It will be ignored.", option->key);
+  }
+
+  if (nodes_head == NULL)
+  {
+    ERROR ("redis plugin: No valid node configuration could be found.");
+    return (ENOENT);
+  }
+
+  return (0);
+} /* }}} */
+
+  __attribute__ ((nonnull(2)))
+static void redis_submit_g (char *plugin_instance,
+    const char *type, const char *type_instance,
+    gauge_t value) /* {{{ */
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0].gauge = value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "redis", sizeof (vl.plugin));
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance,
+        sizeof (vl.plugin_instance));
+  sstrncpy (vl.type, type, sizeof (vl.type));
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance,
+        sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* }}} */
+
+  __attribute__ ((nonnull(2)))
+static void redis_submit_c (char *plugin_instance,
+    const char *type, const char *type_instance,
+    counter_t value) /* {{{ */
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0].counter = value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "redis", sizeof (vl.plugin));
+  if (plugin_instance != NULL)
+    sstrncpy (vl.plugin_instance, plugin_instance,
+        sizeof (vl.plugin_instance));
+  sstrncpy (vl.type, type, sizeof (vl.type));
+  if (type_instance != NULL)
+    sstrncpy (vl.type_instance, type_instance,
+        sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* }}} */
+
+static int redis_init (void) /* {{{ */
+{
+  redis_node_t rn = { "default", REDIS_DEF_HOST, REDIS_DEF_PORT,
+    REDIS_DEF_TIMEOUT, /* next = */ NULL };
+
+  if (nodes_head == NULL)
+    redis_node_add (&rn);
+
+  return (0);
+} /* }}} int redis_init */
+
+static int redis_read (void) /* {{{ */
+{
+  redis_node_t *rn;
+
+  for (rn = nodes_head; rn != NULL; rn = rn->next)
+  {
+    REDIS rh;
+    REDIS_INFO info;
+
+    int status;
+
+    DEBUG ("redis plugin: querying info from node `%s' (%s:%d).", rn->name, rn->host, rn->port);
+
+    rh = credis_connect (rn->host, rn->port, rn->timeout);
+    if (rh == NULL)
+    {
+      ERROR ("redis plugin: unable to connect to node `%s' (%s:%d).", rn->name, rn->host, rn->port);
+      continue;
+    }
+
+    memset (&info, 0, sizeof (info));
+    status = credis_info (rh, &info);
+    if (status != 0)
+    {
+      WARNING ("redis plugin: unable to get info from node `%s'.", rn->name);
+      credis_close (rh);
+      continue;
+    }
+
+    /* typedef struct _cr_info {
+     *   char redis_version[CREDIS_VERSION_STRING_SIZE];
+     *   int bgsave_in_progress;
+     *   int connected_clients;
+     *   int connected_slaves;
+     *   unsigned int used_memory;
+     *   long long changes_since_last_save;
+     *   int last_save_time;
+     *   long long total_connections_received;
+     *   long long total_commands_processed;
+     *   int uptime_in_seconds;
+     *   int uptime_in_days;
+     *   int role;
+     * } REDIS_INFO; */
+
+    DEBUG ("redis plugin: received info from node `%s': connected_clients = %d; "
+        "connected_slaves = %d; used_memory = %lu; changes_since_last_save = %lld; "
+        "bgsave_in_progress = %d; total_connections_received = %lld; "
+        "total_commands_processed = %lld; uptime_in_seconds = %ld", rn->name,
+        info.connected_clients, info.connected_slaves, info.used_memory,
+        info.changes_since_last_save, info.bgsave_in_progress,
+        info.total_connections_received, info.total_commands_processed,
+        info.uptime_in_seconds);
+
+    redis_submit_g (rn->name, "current_connections", "clients", info.connected_clients);
+    redis_submit_g (rn->name, "current_connections", "slaves", info.connected_slaves);
+    redis_submit_g (rn->name, "memory", "used", info.used_memory);
+    redis_submit_g (rn->name, "volatile_changes", NULL, info.changes_since_last_save);
+    redis_submit_c (rn->name, "total_connections", NULL, info.total_connections_received);
+    redis_submit_c (rn->name, "total_operations", NULL, info.total_commands_processed);
+
+    credis_close (rh);
+  }
+
+  return 0;
+}
+/* }}} */
+
+void module_register (void) /* {{{ */
+{
+  plugin_register_complex_config ("redis", redis_config);
+  plugin_register_init ("redis", redis_init);
+  plugin_register_read ("redis", redis_read);
+  /* TODO: plugin_register_write: one redis list per value id with
+   * X elements */
+}
+/* }}} */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
index 7a962f0be74408ce6e1d7cfca9115420802112d8..f75e60074ed3165c402bc629db8f5c8d95936164 100644 (file)
@@ -27,6 +27,7 @@ counter                       value:COUNTER:U:U
 cpufreq                        value:GAUGE:0:U
 cpu                    value:COUNTER:0:4294967295
 current                        value:GAUGE:U:U
+current_connections    value:GAUGE:0:U
 delay                  seconds:GAUGE:-1000000:1000000
 derive                 value:DERIVE:0:U
 df                     used:GAUGE:0:1125899906842623, free:GAUGE:0:1125899906842623
@@ -157,6 +158,7 @@ time_dispersion             seconds:GAUGE:-1000000:1000000
 timeleft               timeleft:GAUGE:0:3600
 time_offset            seconds:GAUGE:-1000000:1000000
 total_bytes            value:DERIVE:0:U
+total_connections      value:DERIVE:0:U
 total_operations       value:DERIVE:0:U
 total_requests         value:DERIVE:0:U
 total_sessions         value:DERIVE:0:U
@@ -171,6 +173,7 @@ vmpage_action               value:COUNTER:0:4294967295
 vmpage_faults          minflt:COUNTER:0:9223372036854775807, majflt:COUNTER:0:9223372036854775807
 vmpage_io              in:COUNTER:0:4294967295, out:COUNTER:0:4294967295
 vmpage_number          value:GAUGE:0:4294967295
+volatile_changes       value:GAUGE:0:U
 voltage_threshold      value:GAUGE:U:U, threshold:GAUGE:U:U
 voltage                        value:GAUGE:U:U
 vs_memory              value:GAUGE:0:9223372036854775807
diff --git a/src/write_redis.c b/src/write_redis.c
new file mode 100644 (file)
index 0000000..58f2cae
--- /dev/null
@@ -0,0 +1,238 @@
+/**
+ * collectd - src/write_redis.c
+ * Copyright (C) 2010  Florian Forster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Florian Forster <ff at octo.it>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "configfile.h"
+
+#include <pthread.h>
+#include <credis.h>
+
+struct wr_node_s
+{
+  char name[DATA_MAX_NAME_LEN];
+
+  char *host;
+  int port;
+  int timeout;
+
+  REDIS conn;
+  pthread_mutex_t lock;
+};
+typedef struct wr_node_s wr_node_t;
+
+/*
+ * Functions
+ */
+static int wr_write (const data_set_t *ds, /* {{{ */
+    const value_list_t *vl,
+    user_data_t *ud)
+{
+  wr_node_t *node = ud->data;
+  char ident[512];
+  char key[512];
+  char value[512];
+  size_t value_size;
+  char *value_ptr;
+  int status;
+  int i;
+
+  status = FORMAT_VL (ident, sizeof (ident), vl);
+  if (status != 0)
+    return (status);
+  ssnprintf (key, sizeof (key), "collectd/%s", ident);
+
+  memset (value, 0, sizeof (value));
+  value_size = sizeof (value);
+  value_ptr = &value[0];
+
+#define APPEND(...) do {                                             \
+  status = snprintf (value_ptr, value_size, __VA_ARGS__);            \
+  if (((size_t) status) > value_size)                                \
+  {                                                                  \
+    value_ptr += value_size;                                         \
+    value_size = 0;                                                  \
+  }                                                                  \
+  else                                                               \
+  {                                                                  \
+    value_ptr += status;                                             \
+    value_size -= status;                                            \
+  }                                                                  \
+} while (0)
+
+  APPEND ("%lu", (unsigned long) vl->time);
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+      APPEND ("%llu", vl->values[i].counter);
+    else if (ds->ds[i].type == DS_TYPE_GAUGE)
+      APPEND ("%g", vl->values[i].gauge);
+    else if (ds->ds[i].type == DS_TYPE_DERIVE)
+      APPEND ("%"PRIi64, vl->values[i].derive);
+    else if (ds->ds[i].type == DS_TYPE_ABSOLUTE)
+      APPEND ("%"PRIu64, vl->values[i].absolute);
+    else
+      assert (23 == 42);
+  }
+
+#undef APPEND
+
+  pthread_mutex_lock (&node->lock);
+
+  if (node->conn == NULL)
+  {
+    node->conn = credis_connect (node->host, node->port, node->timeout);
+    if (node->conn == NULL)
+    {
+      ERROR ("write_redis plugin: Connecting to host \"%s\" (port %i) failed.",
+          (node->host != NULL) ? node->host : "localhost",
+          (node->port != 0) ? node->port : 6379);
+      pthread_mutex_unlock (&node->lock);
+      return (-1);
+    }
+  }
+
+  /* "credis_zadd" doesn't handle a NULL pointer gracefully, so I'd rather
+   * have a meaningful assertion message than a normal segmentation fault. */
+  assert (node->conn != NULL);
+  status = credis_zadd (node->conn, key, (double) vl->time, value);
+
+  credis_sadd (node->conn, "collectd/values", ident);
+
+  pthread_mutex_unlock (&node->lock);
+
+  return (0);
+} /* }}} int wr_write */
+
+static void wr_config_free (void *ptr) /* {{{ */
+{
+  wr_node_t *node = ptr;
+
+  if (node == NULL)
+    return;
+
+  if (node->conn != NULL)
+  {
+    credis_close (node->conn);
+    node->conn = NULL;
+  }
+
+  sfree (node->host);
+  sfree (node);
+} /* }}} void wr_config_free */
+
+static int wr_config_node (oconfig_item_t *ci) /* {{{ */
+{
+  wr_node_t *node;
+  int status;
+  int i;
+
+  node = malloc (sizeof (*node));
+  if (node == NULL)
+    return (ENOMEM);
+  memset (node, 0, sizeof (*node));
+  node->host = NULL;
+  node->port = 0;
+  node->timeout = 1000;
+  node->conn = NULL;
+  pthread_mutex_init (&node->lock, /* attr = */ NULL);
+
+  status = cf_util_get_string_buffer (ci, node->name, sizeof (node->name));
+  if (status != 0)
+  {
+    sfree (node);
+    return (status);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &node->host);
+    else if (strcasecmp ("Port", child->key) == 0)
+    {
+      status = cf_util_get_port_number (child);
+      if (status > 0)
+      {
+        node->port = status;
+        status = 0;
+      }
+    }
+    else if (strcasecmp ("Timeout", child->key) == 0)
+      status = cf_util_get_int (child, &node->timeout);
+    else
+      WARNING ("write_redis plugin: Ignoring unknown config option \"%s\".",
+          child->key);
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  if (status == 0)
+  {
+    char cb_name[DATA_MAX_NAME_LEN];
+    user_data_t ud;
+
+    ssnprintf (cb_name, sizeof (cb_name), "write_redis/%s", node->name);
+
+    ud.data = node;
+    ud.free_func = wr_config_free;
+
+    status = plugin_register_write (cb_name, wr_write, &ud);
+  }
+
+  if (status != 0)
+    wr_config_free (node);
+
+  return (status);
+} /* }}} int wr_config_node */
+
+static int wr_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Node", child->key) == 0)
+      wr_config_node (child);
+    else
+      WARNING ("write_redis plugin: Ignoring unknown "
+          "configuration option \"%s\" at top level.", child->key);
+  }
+
+  return (0);
+} /* }}} int wr_config */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("write_redis", wr_config);
+}
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */