Merge pull request #3339 from jkohen/patch-1
[collectd.git] / src / redis.c
1 /**
2  * collectd - src/redis.c, based on src/memcached.c
3  * Copyright (C) 2010       Andrés J. Díaz <ajdiaz@connectical.com>
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; either version 2 of the License, or (at your
8  * option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Andrés J. Díaz <ajdiaz@connectical.com>
21  **/
22
23 #include "collectd.h"
24 #include "common.h"
25 #include "plugin.h"
26 #include "configfile.h"
27
28 #include <pthread.h>
29 #include <credis.h>
30
31 #ifndef HOST_NAME_MAX
32 # define HOST_NAME_MAX _POSIX_HOST_NAME_MAX
33 #endif
34
35 #define REDIS_DEF_HOST   "localhost"
36 #define REDIS_DEF_PASSWD ""
37 #define REDIS_DEF_PORT    6379
38 #define REDIS_DEF_TIMEOUT 2000
39 #define MAX_REDIS_NODE_NAME 64
40 #define MAX_REDIS_PASSWD_LENGTH 512
41
42 /* Redis plugin configuration example:
43  *
44  * <Plugin redis>
45  *   <Node "mynode">
46  *     Host "localhost"
47  *     Port "6379"
48  *     Timeout 2000
49  *   </Node>
50  * </Plugin>
51  */
52
53 struct redis_node_s;
54 typedef struct redis_node_s redis_node_t;
55 struct redis_node_s
56 {
57   char name[MAX_REDIS_NODE_NAME];
58   char host[HOST_NAME_MAX];
59   char passwd[MAX_REDIS_PASSWD_LENGTH];
60   int port;
61   int timeout;
62
63   redis_node_t *next;
64 };
65
66 static redis_node_t *nodes_head = NULL;
67
68 static int redis_node_add (const redis_node_t *rn) /* {{{ */
69 {
70   redis_node_t *rn_copy;
71   redis_node_t *rn_ptr;
72
73   /* Check for duplicates first */
74   for (rn_ptr = nodes_head; rn_ptr != NULL; rn_ptr = rn_ptr->next)
75     if (strcmp (rn->name, rn_ptr->name) == 0)
76       break;
77
78   if (rn_ptr != NULL)
79   {
80     ERROR ("redis plugin: A node with the name `%s' already exists.",
81         rn->name);
82     return (-1);
83   }
84
85   rn_copy = malloc (sizeof (*rn_copy));
86   if (rn_copy == NULL)
87   {
88     ERROR ("redis plugin: malloc failed adding redis_node to the tree.");
89     return (-1);
90   }
91
92   memcpy (rn_copy, rn, sizeof (*rn_copy));
93   rn_copy->next = NULL;
94
95   DEBUG ("redis plugin: Adding node \"%s\".", rn->name);
96
97   if (nodes_head == NULL)
98     nodes_head = rn_copy;
99   else
100   {
101     rn_ptr = nodes_head;
102     while (rn_ptr->next != NULL)
103       rn_ptr = rn_ptr->next;
104     rn_ptr->next = rn_copy;
105   }
106
107   return (0);
108 } /* }}} */
109
110 static int redis_config_node (oconfig_item_t *ci) /* {{{ */
111 {
112   redis_node_t rn;
113   int i;
114   int status;
115
116   memset (&rn, 0, sizeof (rn));
117   sstrncpy (rn.host, REDIS_DEF_HOST, sizeof (rn.host));
118   rn.port = REDIS_DEF_PORT;
119   rn.timeout = REDIS_DEF_TIMEOUT;
120
121   status = cf_util_get_string_buffer (ci, rn.name, sizeof (rn.name));
122   if (status != 0)
123     return (status);
124
125   for (i = 0; i < ci->children_num; i++)
126   {
127     oconfig_item_t *option = ci->children + i;
128
129     if (strcasecmp ("Host", option->key) == 0)
130       status = cf_util_get_string_buffer (option, rn.host, sizeof (rn.host));
131     else if (strcasecmp ("Port", option->key) == 0)
132     {
133       status = cf_util_get_port_number (option);
134       if (status > 0)
135       {
136         rn.port = status;
137         status = 0;
138       }
139     }
140     else if (strcasecmp ("Timeout", option->key) == 0)
141       status = cf_util_get_int (option, &rn.timeout);
142     else if (strcasecmp ("Password", option->key) == 0)
143       status = cf_util_get_string_buffer (option, rn.passwd, sizeof (rn.passwd));
144     else
145       WARNING ("redis plugin: Option `%s' not allowed inside a `Node' "
146           "block. I'll ignore this option.", option->key);
147
148     if (status != 0)
149       break;
150   }
151
152   if (status != 0)
153     return (status);
154
155   return (redis_node_add (&rn));
156 } /* }}} int redis_config_node */
157
158 static int redis_config (oconfig_item_t *ci) /* {{{ */
159 {
160   int i;
161
162   for (i = 0; i < ci->children_num; i++)
163   {
164     oconfig_item_t *option = ci->children + i;
165
166     if (strcasecmp ("Node", option->key) == 0)
167       redis_config_node (option);
168     else
169       WARNING ("redis plugin: Option `%s' not allowed in redis"
170           " configuration. It will be ignored.", option->key);
171   }
172
173   if (nodes_head == NULL)
174   {
175     ERROR ("redis plugin: No valid node configuration could be found.");
176     return (ENOENT);
177   }
178
179   return (0);
180 } /* }}} */
181
182   __attribute__ ((nonnull(2)))
183 static void redis_submit_g (char *plugin_instance,
184     const char *type, const char *type_instance,
185     gauge_t value) /* {{{ */
186 {
187   value_t values[1];
188   value_list_t vl = VALUE_LIST_INIT;
189
190   values[0].gauge = value;
191
192   vl.values = values;
193   vl.values_len = 1;
194   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
195   sstrncpy (vl.plugin, "redis", sizeof (vl.plugin));
196   if (plugin_instance != NULL)
197     sstrncpy (vl.plugin_instance, plugin_instance,
198         sizeof (vl.plugin_instance));
199   sstrncpy (vl.type, type, sizeof (vl.type));
200   if (type_instance != NULL)
201     sstrncpy (vl.type_instance, type_instance,
202         sizeof (vl.type_instance));
203
204   plugin_dispatch_values (&vl);
205 } /* }}} */
206
207   __attribute__ ((nonnull(2)))
208 static void redis_submit_d (char *plugin_instance,
209     const char *type, const char *type_instance,
210     derive_t value) /* {{{ */
211 {
212   value_t values[1];
213   value_list_t vl = VALUE_LIST_INIT;
214
215   values[0].derive = value;
216
217   vl.values = values;
218   vl.values_len = 1;
219   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
220   sstrncpy (vl.plugin, "redis", sizeof (vl.plugin));
221   if (plugin_instance != NULL)
222     sstrncpy (vl.plugin_instance, plugin_instance,
223         sizeof (vl.plugin_instance));
224   sstrncpy (vl.type, type, sizeof (vl.type));
225   if (type_instance != NULL)
226     sstrncpy (vl.type_instance, type_instance,
227         sizeof (vl.type_instance));
228
229   plugin_dispatch_values (&vl);
230 } /* }}} */
231
232 static int redis_init (void) /* {{{ */
233 {
234   redis_node_t rn = { "default", REDIS_DEF_HOST, REDIS_DEF_PASSWD,
235     REDIS_DEF_PORT, REDIS_DEF_TIMEOUT, /* next = */ NULL };
236
237   if (nodes_head == NULL)
238     redis_node_add (&rn);
239
240   return (0);
241 } /* }}} int redis_init */
242
243 static int redis_read (void) /* {{{ */
244 {
245   redis_node_t *rn;
246
247   for (rn = nodes_head; rn != NULL; rn = rn->next)
248   {
249     REDIS rh;
250     REDIS_INFO info;
251
252     int status;
253
254     DEBUG ("redis plugin: querying info from node `%s' (%s:%d).", rn->name, rn->host, rn->port);
255
256     rh = credis_connect (rn->host, rn->port, rn->timeout);
257     if (rh == NULL)
258     {
259       ERROR ("redis plugin: unable to connect to node `%s' (%s:%d).", rn->name, rn->host, rn->port);
260       continue;
261     }
262
263     if (strlen (rn->passwd) > 0)
264     {
265       DEBUG ("redis plugin: authenticanting node `%s' passwd(%s).", rn->name, rn->passwd);
266       status = credis_auth(rh, rn->passwd);
267       if (status != 0)
268       {
269         WARNING ("redis plugin: unable to authenticate on node `%s'.", rn->name);
270         credis_close (rh);
271         continue;
272       }
273     }
274
275     memset (&info, 0, sizeof (info));
276     status = credis_info (rh, &info);
277     if (status != 0)
278     {
279       WARNING ("redis plugin: unable to get info from node `%s'.", rn->name);
280       credis_close (rh);
281       continue;
282     }
283
284     /* typedef struct _cr_info {
285      *   char redis_version[CREDIS_VERSION_STRING_SIZE];
286      *   int bgsave_in_progress;
287      *   int connected_clients;
288      *   int connected_slaves;
289      *   unsigned int used_memory;
290      *   long long changes_since_last_save;
291      *   int last_save_time;
292      *   long long total_connections_received;
293      *   long long total_commands_processed;
294      *   int uptime_in_seconds;
295      *   int uptime_in_days;
296      *   int role;
297      * } REDIS_INFO; */
298
299     DEBUG ("redis plugin: received info from node `%s': connected_clients = %d; "
300         "connected_slaves = %d; used_memory = %lu; changes_since_last_save = %lld; "
301         "bgsave_in_progress = %d; total_connections_received = %lld; "
302         "total_commands_processed = %lld; uptime_in_seconds = %ld", rn->name,
303         info.connected_clients, info.connected_slaves, info.used_memory,
304         info.changes_since_last_save, info.bgsave_in_progress,
305         info.total_connections_received, info.total_commands_processed,
306         info.uptime_in_seconds);
307
308     redis_submit_g (rn->name, "current_connections", "clients", info.connected_clients);
309     redis_submit_g (rn->name, "current_connections", "slaves", info.connected_slaves);
310     redis_submit_g (rn->name, "memory", "used", info.used_memory);
311     redis_submit_g (rn->name, "volatile_changes", NULL, info.changes_since_last_save);
312     redis_submit_d (rn->name, "total_connections", NULL, info.total_connections_received);
313     redis_submit_d (rn->name, "total_operations", NULL, info.total_commands_processed);
314
315     credis_close (rh);
316   }
317
318   return 0;
319 }
320 /* }}} */
321
322 void module_register (void) /* {{{ */
323 {
324   plugin_register_complex_config ("redis", redis_config);
325   plugin_register_init ("redis", redis_init);
326   plugin_register_read ("redis", redis_read);
327   /* TODO: plugin_register_write: one redis list per value id with
328    * X elements */
329 }
330 /* }}} */
331
332 /* vim: set sw=2 sts=2 et fdm=marker : */