Add redis plugin.
authorAndres J. Diaz <ajdiaz@connectical.com>
Fri, 30 Jul 2010 14:06:48 +0000 (16:06 +0200)
committerFlorian Forster <octo@noris.net>
Wed, 4 Aug 2010 09:37:19 +0000 (11:37 +0200)
This commit adds a new redis plugin, which connect to a number of redis
server and get information about their status, using the libcredis > 0.2.2
library. The plugin get the following information:

* bgsave_in_progress
* change_since_last_save
* connected_clients
* connected_slaves
* total_command_processed
* total_connection_received
* uptime_in_seconds
* used_memory

Here is an example of configuration file:

<Plugin redis>
<Node example>
Host "localhost"
Timeout 2000
</Node>
</Plugin>

And the output of rrdtool plugin must be something similar to this:

redis-example/bgsave_in_progress.rrd
redis-example/changes_since_last_save.rrd
redis-example/connected_clients.rrd
redis-example/connected_slaves.rrd
redis-example/total_commands_processed.rrd
redis-example/total_connections_received.rrd
redis-example/uptime_in_seconds.rrd
redis-example/used_memory.rrd

Enjoy! :)

.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

index e8f9af6..cbdd62f 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 8d4d275..0c7a422 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 fc12c08..eb21ffe 100644 (file)
@@ -3051,6 +3051,64 @@ then
 fi
 # }}} --with-python
 
+# --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-librouteros {{{
 AC_ARG_WITH(librouteros, [AS_HELP_STRING([--with-librouteros@<:@=PREFIX@:>@], [Path to librouteros.])],
 [
@@ -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])
@@ -4676,6 +4735,7 @@ Configuration:
   Libraries:
     libcurl . . . . . . . $with_libcurl
     libdbi  . . . . . . . $with_libdbi
+    libcredis . . . . . . $with_libcredis
     libesmtp  . . . . . . $with_libesmtp
     libganglia  . . . . . $with_libganglia
     libgcrypt . . . . . . $with_libgcrypt
@@ -4791,6 +4851,7 @@ Configuration:
     processes . . . . . . $enable_processes
     protocols . . . . . . $enable_protocols
     python  . . . . . . . $enable_python
+    redis . . . . . . . . $enable_redis
     routeros  . . . . . . $enable_routeros
     rrdcached . . . . . . $enable_rrdcached
     rrdtool . . . . . . . $enable_rrdtool
index 00d0e20..fbe505e 100644 (file)
@@ -885,6 +885,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_LIBREDIS_LDFLAGS)
+redis_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBREDIS_CFLAGS)
+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
index 9cdecc0..8389e83 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
 #      </Module>
 #</Plugin>
 
+#<Plugin redis>
+#   <Node example>
+#      Host "redis.example.com"
+#      Port 6379
+#      Database 0
+#      Timeout 2000
+#   </Node>
+#</Plugin>
+
 #<Plugin routeros>
 #      <Router>
 #              Host "router.example.com"
index 01f0f82..f1bc874 100644 (file)
@@ -3751,6 +3751,49 @@ Defaults to B<false>.
 
 =back
 
+=head2 Plugin C<redis>
+
+The C<redis> plugin connect to a list of redis servers and gather
+information about the server state. The C<redis> plugin support multiserver
+configuration, each server configuration block is called node and identify
+one redis instance (host and port). If no B<Node> block found, then any
+configuration option is assigned to a virtual node called C<default>. Here
+are a configuration example code:
+
+  <Plugin redis>
+    <Node example>
+        Host "localhost"
+        Port 6379
+        Timeout 2000
+    </Node>
+  </Plugin>
+
+=over 4
+
+=item B<Node> I<Nodename>
+
+The B<Node> block identify 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 plugin instance, it's limited to 64 characters
+length.
+
+=item B<Host> I<Hostname>
+
+The B<Host> option is the hostname or IP address (setting as string) where
+redis instance is running on.
+
+=item B<Port> I<Port>
+
+The B<Port> option is the TCP port where redis instance in IP address or
+hostname setted in B<Host> option is running.
+
+=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 may keep this value as low as possible.
+Keep in mind that the sum of all Timeout values for all Nodes might be lower
+than B<Interval> defined globally.
+
 =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..5eed268
--- /dev/null
@@ -0,0 +1,353 @@
+/**
+ * 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 "utils_avltree.h"
+
+#include <pthread.h>
+#include <credis.h>
+
+#define REDIS_DEF_HOST "127.0.0.1"
+#define REDIS_DEF_PORT 6379
+#define MAX_REDIS_NODE_NAME 64
+
+/* Redis plugin configuration example:
+   <Plugin redis>
+    <Node mynode>
+        Host localhost
+        Port 6379
+        Timeout 2000
+    </Node>
+   </Plugin>
+
+*/
+
+static c_avl_tree_t  *redis_tree = NULL;
+static pthread_mutex_t redis_lock = PTHREAD_MUTEX_INITIALIZER;
+
+typedef struct redis_node_s {
+    char name[MAX_REDIS_NODE_NAME];
+    char host[HOST_NAME_MAX];
+    int port;
+    int timeout;
+} redis_node_t;
+
+static int redis_config_node (redis_node_t *rn, oconfig_item_t *ci) /* {{{ */
+{
+    int i;
+    int status = 0;
+
+    if ((ci->values_num != 1)
+            || (ci->values[0].type != OCONFIG_TYPE_STRING))
+    {
+        WARNING ("redis plugin: The `Node' block needs exactly one string "
+                "argument.");
+        return (-1);
+    }
+
+    if (ci->children_num < 1)
+    {
+        WARNING ("redis plugin: The `Node' block needs at least one option.");
+        return (-1);
+    }
+
+    sstrncpy (rn->name, ci->values[0].value.string, sizeof (rn->name));
+
+    for (i = 0; i < ci->children_num; i++)
+    {
+        oconfig_item_t *option = ci->children + i;
+        status = 0;
+
+        if (strcasecmp ("Host", option->key) == 0)
+            status = cf_util_get_string_buffer (option, rn->host, HOST_NAME_MAX);
+        else if (strcasecmp ("Port", option->key) == 0)
+            status = rn->port = cf_util_get_port_number (option);
+        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.", option->key);
+                       status = -1;
+               }
+
+               if (status != 0)
+                       break;
+       }
+
+       return (status);
+
+} /* }}} */
+
+static redis_node_t *redis_node_get (const char *name, redis_node_t *rn) /* {{{ */
+{
+       if (c_avl_get (redis_tree, name, (void *) rn) == 0)
+               return (rn);
+       else
+               return (NULL);
+} /* }}} */
+
+static int redis_node_add (const redis_node_t *rn) /* {{{ */
+{
+       int status;
+       redis_node_t *rn_copy = NULL;
+       redis_node_t *rn_ptr;
+       redis_node_t  rn_get;
+
+       rn_copy = (redis_node_t *) malloc (sizeof (redis_node_t));
+       if (rn_copy == NULL)
+       {
+               sfree (rn_copy);
+               ERROR ("redis plugin: malloc failed adding redis_node to the tree.");
+               return (-1);
+       }
+       memcpy (rn_copy, rn, sizeof (redis_node_t));
+       if (*rn_copy->name == '\0')
+       {
+               (void) strncpy(rn_copy->name, "default", MAX_REDIS_NODE_NAME); /* in theory never fails */
+       }
+
+       DEBUG ("redis plugin: adding entry `%s' to the tree.", rn_copy->name);
+
+       pthread_mutex_lock (&redis_lock);
+
+       if ( (rn_ptr = redis_node_get (rn_copy->name, &rn_get)) != NULL )
+       {
+               WARNING ("redis plugin: the node `%s' override a previous node with same node.", rn_copy->name);
+       }
+
+       status = c_avl_insert (redis_tree, rn_copy->name, rn_copy);
+       pthread_mutex_unlock (&redis_lock);
+
+       if (status != 0)
+       {
+               ERROR ("redis plugin: c_avl_insert (%s) failed adding noew node.", rn_copy->name);
+               sfree (rn_copy);
+               return (-1);
+       }
+
+       return (status);
+} /* }}} */
+
+static int redis_config (oconfig_item_t *ci) /* {{{ */
+{
+    int status;
+    int i;
+
+    redis_node_t rn = {
+        .name = "",
+        .host = "",
+        .port = REDIS_DEF_PORT,
+        .timeout = 2000
+    };
+
+    if (redis_tree == NULL)
+    {
+        redis_tree = c_avl_create ((void *) strcmp);
+        if (redis_tree == NULL)
+        {
+            ERROR ("redis plugin: c_avl_create failed reading config.");
+            return (-1);
+        }
+    }
+
+    status = 0;
+    for (i = 0; i < ci->children_num; i++)
+    {
+        oconfig_item_t *option = ci->children + i;
+
+        if (strcasecmp ("Node", option->key) == 0)
+               {
+                       if ( (status = redis_config_node (&rn, option)) == 0 )
+                               status = redis_node_add (&rn);
+               }
+               else if (strcasecmp ("Host", option->key) == 0)
+            status = cf_util_get_string_buffer (option, rn.host, HOST_NAME_MAX);
+        else if (strcasecmp ("Port", option->key) == 0)
+            status = rn.port = cf_util_get_port_number (option);
+        else if (strcasecmp ("Timeout", option->key) == 0)
+            status = cf_util_get_int (option, &rn.timeout);
+               else
+               {
+                       WARNING ("redis plugin: Option `%s' not allowed in redis"
+                                       " configuration.", option->key);
+                       status = -1;
+               }
+
+
+        if (status != 0)
+            break;
+    }
+
+    if ( status == 0 && *rn.name != '\0') {
+        status = redis_node_add (&rn);
+    }
+
+    return (status);
+} /* }}} */
+
+__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_read (void) /* {{{ */
+{
+    REDIS rh;
+    REDIS_INFO info;
+
+    char key[64];
+    int status;
+    c_avl_iterator_t *iter;
+    redis_node_t *rn;
+
+       status = -1;
+       if ( (iter = c_avl_get_iterator (redis_tree)) == NULL )
+       {
+               ERROR ("redis plugin: unable to iterate redis tree.");
+               return (-1);
+       }
+
+       while (c_avl_iterator_next (iter, (void *) &key, (void *) &rn) == 0)
+       {
+               DEBUG ("redis plugin: querying info from node `%s'.", rn->name);
+
+               if ( (rh = credis_connect (rn->host, rn->port, rn->timeout)) == NULL )
+               {
+                       ERROR ("redis plugin: unable to connect to node `%s' (%s:%d).", rn->name, rn->host, rn->port);
+                       status = -1;
+                       break;
+               }
+
+               if ( (status = credis_info (rh, &info)) == -1 )
+               {
+                       WARNING ("redis plugin: unable to get info from node `%s'.", rn->name);
+                       credis_close (rh);
+                       break;
+               }
+
+               /* 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, "connected_clients", NULL, info.connected_clients);
+               redis_submit_g (rn->name, "connected_slaves", NULL, info.connected_slaves);
+               redis_submit_g (rn->name, "used_memory", NULL, info.used_memory);
+               redis_submit_g (rn->name, "changes_since_last_save", NULL, info.changes_since_last_save);
+               redis_submit_g (rn->name, "bgsave_in_progress", NULL, info.bgsave_in_progress);
+               redis_submit_c (rn->name, "total_connections_received", NULL, info.total_connections_received);
+               redis_submit_c (rn->name, "total_commands_processed", NULL, info.total_commands_processed);
+               redis_submit_c (rn->name, "uptime_in_seconds", NULL, info.uptime_in_seconds);
+
+               credis_close (rh);
+               status = 0;
+       }
+
+       c_avl_iterator_destroy(iter);
+       if ( status != 0 )
+       {
+               return (-1);
+       }
+
+       return 0;
+}
+/* }}} */
+
+
+void module_register (void) /* {{{ */
+{
+       plugin_register_complex_config ("redis", redis_config);
+       plugin_register_read ("redis", redis_read);
+       /* TODO: plugin_register_write: one redis list per value id with
+        * X elements */
+}
+/* }}} */
+
index 7a962f0..e77ab99 100644 (file)
@@ -177,3 +177,11 @@ vs_memory          value:GAUGE:0:9223372036854775807
 vs_processes           value:GAUGE:0:65535
 vs_threads             value:GAUGE:0:65535
 pinba_view              req_per_sec:GAUGE:0:U, req_time:GAUGE:0:U, ru_utime:GAUGE:0:U, ru_stime:GAUGE:0:U, doc_size:GAUGE:0:U, mem_peak:GAUGE:0:U
+connected_clients   value:GAUGE:0:4294967295
+connected_slaves    value:GAUGE:0:4294967295
+used_memory         value:GAUGE:0:U
+changes_since_last_save    value:GAUGE:0:4294967295
+bgsave_in_progress     value:GAUGE:0:4294967295
+total_connections_received     value:COUNTER:0:9223372036854775807
+total_commands_processed      value:COUNTER:0:9223372036854775807
+uptime_in_seconds     value:COUNTER:0:4294967295