Merge pull request #3195 from dago/gccflags
[collectd.git] / src / memcachec.c
1 /**
2  * collectd - src/memcachec.c
3  * Copyright (C) 2009       Doug MacEachern
4  * Copyright (C) 2006-2009  Florian octo Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
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  *   Doug MacEachern <Doug.MacEachern at hyperic.com>
21  *   Florian octo Forster <octo at collectd.org>
22  **/
23
24 #include "collectd.h"
25
26 #include "plugin.h"
27 #include "utils/common/common.h"
28 #include "utils/match/match.h"
29
30 #include <libmemcached/memcached.h>
31
32 /*
33  * Data types
34  */
35 struct web_match_s;
36 typedef struct web_match_s web_match_t;
37 struct web_match_s /* {{{ */
38 {
39   char *regex;
40   char *exclude_regex;
41   int dstype;
42   char *type;
43   char *instance;
44
45   cu_match_t *match;
46
47   web_match_t *next;
48 }; /* }}} */
49
50 struct web_page_s;
51 typedef struct web_page_s web_page_t;
52 struct web_page_s /* {{{ */
53 {
54   char *plugin_name;
55   char *instance;
56
57   char *server;
58   char *key;
59
60   memcached_st *memc;
61   char *buffer;
62
63   web_match_t *matches;
64
65   web_page_t *next;
66 }; /* }}} */
67
68 /*
69  * Global variables;
70  */
71 static web_page_t *pages_g;
72
73 /*
74  * Private functions
75  */
76 static void cmc_web_match_free(web_match_t *wm) /* {{{ */
77 {
78   if (wm == NULL)
79     return;
80
81   sfree(wm->regex);
82   sfree(wm->type);
83   sfree(wm->instance);
84   match_destroy(wm->match);
85   cmc_web_match_free(wm->next);
86   sfree(wm);
87 } /* }}} void cmc_web_match_free */
88
89 static void cmc_web_page_free(web_page_t *wp) /* {{{ */
90 {
91   if (wp == NULL)
92     return;
93
94   if (wp->memc != NULL)
95     memcached_free(wp->memc);
96   wp->memc = NULL;
97
98   sfree(wp->plugin_name);
99   sfree(wp->instance);
100   sfree(wp->server);
101   sfree(wp->key);
102   sfree(wp->buffer);
103
104   cmc_web_match_free(wp->matches);
105   cmc_web_page_free(wp->next);
106   sfree(wp);
107 } /* }}} void cmc_web_page_free */
108
109 static int cmc_page_init_memc(web_page_t *wp) /* {{{ */
110 {
111   memcached_server_st *server;
112
113   wp->memc = memcached_create(NULL);
114   if (wp->memc == NULL) {
115     ERROR("memcachec plugin: memcached_create failed.");
116     return -1;
117   }
118
119   server = memcached_servers_parse(wp->server);
120   memcached_server_push(wp->memc, server);
121   memcached_server_list_free(server);
122
123   return 0;
124 } /* }}} int cmc_page_init_memc */
125
126 static int cmc_config_add_match_dstype(int *dstype_ret, /* {{{ */
127                                        oconfig_item_t *ci) {
128   int dstype;
129
130   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
131     WARNING("memcachec plugin: `DSType' needs exactly one string argument.");
132     return -1;
133   }
134
135   if (strncasecmp("Gauge", ci->values[0].value.string, strlen("Gauge")) == 0) {
136     dstype = UTILS_MATCH_DS_TYPE_GAUGE;
137     if (strcasecmp("GaugeAverage", ci->values[0].value.string) == 0)
138       dstype |= UTILS_MATCH_CF_GAUGE_AVERAGE;
139     else if (strcasecmp("GaugeMin", ci->values[0].value.string) == 0)
140       dstype |= UTILS_MATCH_CF_GAUGE_MIN;
141     else if (strcasecmp("GaugeMax", ci->values[0].value.string) == 0)
142       dstype |= UTILS_MATCH_CF_GAUGE_MAX;
143     else if (strcasecmp("GaugeLast", ci->values[0].value.string) == 0)
144       dstype |= UTILS_MATCH_CF_GAUGE_LAST;
145     else
146       dstype = 0;
147   } else if (strncasecmp("Counter", ci->values[0].value.string,
148                          strlen("Counter")) == 0) {
149     dstype = UTILS_MATCH_DS_TYPE_COUNTER;
150     if (strcasecmp("CounterSet", ci->values[0].value.string) == 0)
151       dstype |= UTILS_MATCH_CF_COUNTER_SET;
152     else if (strcasecmp("CounterAdd", ci->values[0].value.string) == 0)
153       dstype |= UTILS_MATCH_CF_COUNTER_ADD;
154     else if (strcasecmp("CounterInc", ci->values[0].value.string) == 0)
155       dstype |= UTILS_MATCH_CF_COUNTER_INC;
156     else
157       dstype = 0;
158   } else {
159     dstype = 0;
160   }
161
162   if (dstype == 0) {
163     WARNING("memcachec plugin: `%s' is not a valid argument to `DSType'.",
164             ci->values[0].value.string);
165     return -1;
166   }
167
168   *dstype_ret = dstype;
169   return 0;
170 } /* }}} int cmc_config_add_match_dstype */
171
172 static int cmc_config_add_match(web_page_t *page, /* {{{ */
173                                 oconfig_item_t *ci) {
174   web_match_t *match;
175   int status;
176
177   if (ci->values_num != 0) {
178     WARNING("memcachec plugin: Ignoring arguments for the `Match' block.");
179   }
180
181   match = calloc(1, sizeof(*match));
182   if (match == NULL) {
183     ERROR("memcachec plugin: calloc failed.");
184     return -1;
185   }
186
187   status = 0;
188   for (int i = 0; i < ci->children_num; i++) {
189     oconfig_item_t *child = ci->children + i;
190
191     if (strcasecmp("Regex", child->key) == 0)
192       status = cf_util_get_string(child, &match->regex);
193     else if (strcasecmp("ExcludeRegex", child->key) == 0)
194       status = cf_util_get_string(child, &match->exclude_regex);
195     else if (strcasecmp("DSType", child->key) == 0)
196       status = cmc_config_add_match_dstype(&match->dstype, child);
197     else if (strcasecmp("Type", child->key) == 0)
198       status = cf_util_get_string(child, &match->type);
199     else if (strcasecmp("Instance", child->key) == 0)
200       status = cf_util_get_string(child, &match->instance);
201     else {
202       WARNING("memcachec plugin: Option `%s' not allowed here.", child->key);
203       status = -1;
204     }
205
206     if (status != 0)
207       break;
208   } /* for (i = 0; i < ci->children_num; i++) */
209
210   while (status == 0) {
211     if (match->regex == NULL) {
212       WARNING("memcachec plugin: `Regex' missing in `Match' block.");
213       status = -1;
214     }
215
216     if (match->type == NULL) {
217       WARNING("memcachec plugin: `Type' missing in `Match' block.");
218       status = -1;
219     }
220
221     if (match->dstype == 0) {
222       WARNING("memcachec plugin: `DSType' missing in `Match' block.");
223       status = -1;
224     }
225
226     break;
227   } /* while (status == 0) */
228
229   if (status != 0) {
230     cmc_web_match_free(match);
231     return status;
232   }
233
234   match->match =
235       match_create_simple(match->regex, match->exclude_regex, match->dstype);
236   if (match->match == NULL) {
237     ERROR("memcachec plugin: match_create_simple failed.");
238     cmc_web_match_free(match);
239     return -1;
240   } else {
241     web_match_t *prev;
242
243     prev = page->matches;
244     while ((prev != NULL) && (prev->next != NULL))
245       prev = prev->next;
246
247     if (prev == NULL)
248       page->matches = match;
249     else
250       prev->next = match;
251   }
252
253   return 0;
254 } /* }}} int cmc_config_add_match */
255
256 static int cmc_config_add_page(oconfig_item_t *ci) /* {{{ */
257 {
258   web_page_t *page;
259   int status;
260
261   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
262     WARNING(
263         "memcachec plugin: `Page' blocks need exactly one string argument.");
264     return -1;
265   }
266
267   page = calloc(1, sizeof(*page));
268   if (page == NULL) {
269     ERROR("memcachec plugin: calloc failed.");
270     return -1;
271   }
272   page->server = NULL;
273   page->key = NULL;
274
275   page->instance = strdup(ci->values[0].value.string);
276   if (page->instance == NULL) {
277     ERROR("memcachec plugin: strdup failed.");
278     sfree(page);
279     return -1;
280   }
281
282   /* Process all children */
283   status = 0;
284   for (int i = 0; i < ci->children_num; i++) {
285     oconfig_item_t *child = ci->children + i;
286
287     if (strcasecmp("Server", child->key) == 0)
288       status = cf_util_get_string(child, &page->server);
289     else if (strcasecmp("Key", child->key) == 0)
290       status = cf_util_get_string(child, &page->key);
291     else if (strcasecmp("Plugin", child->key) == 0)
292       status = cf_util_get_string(child, &page->plugin_name);
293     else if (strcasecmp("Match", child->key) == 0)
294       /* Be liberal with failing matches => don't set `status'. */
295       cmc_config_add_match(page, child);
296     else {
297       WARNING("memcachec plugin: Option `%s' not allowed here.", child->key);
298       status = -1;
299     }
300
301     if (status != 0)
302       break;
303   } /* for (i = 0; i < ci->children_num; i++) */
304
305   /* Additionial sanity checks and libmemcached initialization. */
306   while (status == 0) {
307     if (page->server == NULL) {
308       WARNING("memcachec plugin: `Server' missing in `Page' block.");
309       status = -1;
310     }
311
312     if (page->key == NULL) {
313       WARNING("memcachec plugin: `Key' missing in `Page' block.");
314       status = -1;
315     }
316
317     if (page->matches == NULL) {
318       assert(page->instance != NULL);
319       WARNING("memcachec plugin: No (valid) `Match' block "
320               "within `Page' block `%s'.",
321               page->instance);
322       status = -1;
323     }
324
325     if (status == 0)
326       status = cmc_page_init_memc(page);
327
328     break;
329   } /* while (status == 0) */
330
331   if (status != 0) {
332     cmc_web_page_free(page);
333     return status;
334   }
335
336   /* Add the new page to the linked list */
337   if (pages_g == NULL)
338     pages_g = page;
339   else {
340     web_page_t *prev;
341
342     prev = pages_g;
343     while (prev->next != NULL)
344       prev = prev->next;
345     prev->next = page;
346   }
347
348   return 0;
349 } /* }}} int cmc_config_add_page */
350
351 static int cmc_config(oconfig_item_t *ci) /* {{{ */
352 {
353   int success;
354   int errors;
355   int status;
356
357   success = 0;
358   errors = 0;
359
360   for (int i = 0; i < ci->children_num; i++) {
361     oconfig_item_t *child = ci->children + i;
362
363     if (strcasecmp("Page", child->key) == 0) {
364       status = cmc_config_add_page(child);
365       if (status == 0)
366         success++;
367       else
368         errors++;
369     } else {
370       WARNING("memcachec plugin: Option `%s' not allowed here.", child->key);
371       errors++;
372     }
373   }
374
375   if ((success == 0) && (errors > 0)) {
376     ERROR("memcachec plugin: All statements failed.");
377     return -1;
378   }
379
380   return 0;
381 } /* }}} int cmc_config */
382
383 static int cmc_init(void) /* {{{ */
384 {
385   if (pages_g == NULL) {
386     INFO("memcachec plugin: No pages have been defined.");
387     return -1;
388   }
389   return 0;
390 } /* }}} int cmc_init */
391
392 static void cmc_submit(const web_page_t *wp, const web_match_t *wm, /* {{{ */
393                        value_t value) {
394   value_list_t vl = VALUE_LIST_INIT;
395
396   vl.values = &value;
397   vl.values_len = 1;
398   sstrncpy(vl.plugin, (wp->plugin_name != NULL) ? wp->plugin_name : "memcachec",
399            sizeof(vl.plugin));
400   sstrncpy(vl.plugin_instance, wp->instance, sizeof(vl.plugin_instance));
401   sstrncpy(vl.type, wm->type, sizeof(vl.type));
402   sstrncpy(vl.type_instance, wm->instance, sizeof(vl.type_instance));
403
404   plugin_dispatch_values(&vl);
405 } /* }}} void cmc_submit */
406
407 static int cmc_read_page(web_page_t *wp) /* {{{ */
408 {
409   memcached_return rc;
410   size_t string_length;
411   uint32_t flags;
412   int status;
413
414   if (wp->memc == NULL)
415     return -1;
416
417   wp->buffer = memcached_get(wp->memc, wp->key, strlen(wp->key), &string_length,
418                              &flags, &rc);
419   if (rc != MEMCACHED_SUCCESS) {
420     ERROR("memcachec plugin: memcached_get failed: %s",
421           memcached_strerror(wp->memc, rc));
422     return -1;
423   }
424
425   for (web_match_t *wm = wp->matches; wm != NULL; wm = wm->next) {
426     cu_match_value_t *mv;
427
428     status = match_apply(wm->match, wp->buffer);
429     if (status != 0) {
430       WARNING("memcachec plugin: match_apply failed.");
431       continue;
432     }
433
434     mv = match_get_user_data(wm->match);
435     if (mv == NULL) {
436       WARNING("memcachec plugin: match_get_user_data returned NULL.");
437       continue;
438     }
439
440     cmc_submit(wp, wm, mv->value);
441     match_value_reset(mv);
442   } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
443
444   sfree(wp->buffer);
445
446   return 0;
447 } /* }}} int cmc_read_page */
448
449 static int cmc_read(void) /* {{{ */
450 {
451   for (web_page_t *wp = pages_g; wp != NULL; wp = wp->next)
452     cmc_read_page(wp);
453
454   return 0;
455 } /* }}} int cmc_read */
456
457 static int cmc_shutdown(void) /* {{{ */
458 {
459   cmc_web_page_free(pages_g);
460   pages_g = NULL;
461
462   return 0;
463 } /* }}} int cmc_shutdown */
464
465 void module_register(void) {
466   plugin_register_complex_config("memcachec", cmc_config);
467   plugin_register_init("memcachec", cmc_init);
468   plugin_register_read("memcachec", cmc_read);
469   plugin_register_shutdown("memcachec", cmc_shutdown);
470 } /* void module_register */