[collectd] [PATCH] Add redis plugin.
Andres J. Diaz
ajdiaz at connectical.com
Fri Jul 30 16:36:38 CEST 2010
Hi guys!
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
Obviously a libcredis copy must be installed on the system previously.
I test the plugin with libcredis 0.2.2
<http://code.google.com/p/credis/downloads/detail?name=credis-0.2.2.tar.gz>
Enjoy! :)
Br,
Andres
Signed-off-by: Andres J. Diaz <ajdiaz at connectical.com>
---
.gitignore | 6 +
README | 8 +
configure.in | 61 +++++++++
src/Makefile.am | 10 ++
src/collectd.conf.in | 10 ++
src/collectd.conf.pod | 43 ++++++
src/redis.c | 353 +++++++++++++++++++++++++++++++++++++++++++++++++
src/types.db | 8 +
8 files changed, 499 insertions(+), 0 deletions(-)
create mode 100644 src/redis.c
diff --git a/.gitignore b/.gitignore
index e8f9af6..cbdd62f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
--- 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.
diff --git a/configure.in b/configure.in
index fc12c08..eb21ffe 100644
--- a/configure.in
+++ b/configure.in
@@ -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
diff --git a/src/Makefile.am b/src/Makefile.am
index 00d0e20..fbe505e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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
diff --git a/src/collectd.conf.in b/src/collectd.conf.in
index 9cdecc0..8389e83 100644
--- a/src/collectd.conf.in
+++ b/src/collectd.conf.in
@@ -114,6 +114,7 @@
#@BUILD_PLUGIN_PROCESSES_TRUE at LoadPlugin processes
#@BUILD_PLUGIN_PROTOCOLS_TRUE at LoadPlugin protocols
#@BUILD_PLUGIN_PYTHON_TRUE at LoadPlugin python
+#@BUILD_PLUGIN_REDIS_TRUE at LoadPlugin redis
#@BUILD_PLUGIN_ROUTEROS_TRUE at LoadPlugin routeros
#@BUILD_PLUGIN_RRDCACHED_TRUE at LoadPlugin rrdcached
@LOAD_PLUGIN_RRDTOOL at LoadPlugin rrdtool
@@ -739,6 +740,15 @@
# </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"
diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod
index 01f0f82..f1bc874 100644
--- a/src/collectd.conf.pod
+++ b/src/collectd.conf.pod
@@ -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
index 0000000..5eed268
--- /dev/null
+++ b/src/redis.c
@@ -0,0 +1,353 @@
+/**
+ * collectd - src/redis.c, based on src/memcached.c
+ * Copyright (C) 2010 Andrés J. DÃaz <ajdiaz at 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 at 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 */
+}
+/* }}} */
+
diff --git a/src/types.db b/src/types.db
index 7a962f0..e77ab99 100644
--- a/src/types.db
+++ b/src/types.db
@@ -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
--
1.6.4.4
More information about the collectd
mailing list