From 799410cb4a38cf6c6e85eb3c86d050a4010d8c54 Mon Sep 17 00:00:00 2001 From: "Andres J. Diaz" Date: Fri, 30 Jul 2010 16:06:48 +0200 Subject: [PATCH] Add redis plugin. 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: Host "localhost" Timeout 2000 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 | 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(+) create mode 100644 src/redis.c diff --git a/.gitignore b/.gitignore index e8f9af66..cbdd62f8 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 8d4d275d..0c7a4221 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. + * 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 fc12c088..eb21ffe6 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 00d0e20e..fbe505ec 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 9cdecc02..8389e830 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -114,6 +114,7 @@ #@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 @@ -739,6 +740,15 @@ # # +# +# +# Host "redis.example.com" +# Port 6379 +# Database 0 +# Timeout 2000 +# +# + # # # Host "router.example.com" diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 01f0f824..f1bc8744 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -3751,6 +3751,49 @@ Defaults to B. =back +=head2 Plugin C + +The C plugin connect to a list of redis servers and gather +information about the server state. The C plugin support multiserver +configuration, each server configuration block is called node and identify +one redis instance (host and port). If no B block found, then any +configuration option is assigned to a virtual node called C. Here +are a configuration example code: + + + + Host "localhost" + Port 6379 + Timeout 2000 + + + +=over 4 + +=item B I + +The B 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 I + +The B option is the hostname or IP address (setting as string) where +redis instance is running on. + +=item B I + +The B option is the TCP port where redis instance in IP address or +hostname setted in B option is running. + +=item B I + +The B 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 defined globally. + =head2 Plugin C The C plugin uses the RRDtool accelerator daemon, L, diff --git a/src/redis.c b/src/redis.c new file mode 100644 index 00000000..5eed268a --- /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 + * + * 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 + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "configfile.h" +#include "utils_avltree.h" + +#include +#include + +#define REDIS_DEF_HOST "127.0.0.1" +#define REDIS_DEF_PORT 6379 +#define MAX_REDIS_NODE_NAME 64 + +/* Redis plugin configuration example: + + + + Host localhost + Port 6379 + Timeout 2000 + + + +*/ + +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 7a962f0b..e77ab996 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 -- 2.11.0