value match: Add the `DataSource' and `Satisfy' configuration options.
[collectd.git] / src / match_value.c
1 /**
2  * collectd - src/match_value.c
3  * Copyright (C) 2008  Florian Forster
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; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Authors:
19  *   Florian Forster <octo at verplant.org>
20  **/
21
22 /*
23  * This module allows to filter and rewrite value lists based on
24  * Perl-compatible regular expressions.
25  */
26
27 #include "collectd.h"
28 #include "common.h"
29 #include "utils_cache.h"
30 #include "filter_chain.h"
31
32 #define SATISFY_ALL 0
33 #define SATISFY_ANY 1
34
35 /*
36  * private data types
37  */
38 struct mv_match_s;
39 typedef struct mv_match_s mv_match_t;
40 struct mv_match_s
41 {
42   gauge_t min;
43   gauge_t max;
44   int invert;
45   int satisfy;
46
47   char **data_sources;
48   size_t data_sources_num;
49 };
50
51 /*
52  * internal helper functions
53  */
54 static void mv_free_match (mv_match_t *m) /* {{{ */
55 {
56   if (m == NULL)
57     return;
58
59   free (m);
60 } /* }}} void mv_free_match */
61
62 static int mv_config_add_satisfy (mv_match_t *m, /* {{{ */
63     oconfig_item_t *ci)
64 {
65   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
66   {
67     ERROR ("`value' match: `%s' needs exactly one string argument.",
68         ci->key);
69     return (-1);
70   }
71
72   if (strcasecmp ("All", ci->values[0].value.string) == 0)
73     m->satisfy = SATISFY_ALL;
74   else if (strcasecmp ("Any", ci->values[0].value.string) == 0)
75     m->satisfy = SATISFY_ANY;
76   else
77   {
78     ERROR ("`value' match: Passing `%s' to the `%s' option is invalid. "
79         "The argument must either be `All' or `Any'.",
80         ci->values[0].value.string, ci->key);
81     return (-1);
82   }
83
84   return (0);
85 } /* }}} int mv_config_add_satisfy */
86
87 static int mv_config_add_data_source (mv_match_t *m, /* {{{ */
88     oconfig_item_t *ci)
89 {
90   size_t new_data_sources_num;
91   char **temp;
92   int i;
93
94   /* Check number of arbuments. */
95   if (ci->values_num < 1)
96   {
97     ERROR ("`value' match: `%s' needs at least one argument.",
98         ci->key);
99     return (-1);
100   }
101
102   /* Check type of arguments */
103   for (i = 0; i < ci->values_num; i++)
104   {
105     if (ci->values[i].type == OCONFIG_TYPE_STRING)
106       continue;
107
108     ERROR ("`value' match: `%s' accepts only string arguments "
109         "(argument %i is a %s).",
110         ci->key, i + 1,
111         (ci->values[i].type == OCONFIG_TYPE_BOOLEAN)
112         ? "truth value" : "number");
113     return (-1);
114   }
115
116   /* Allocate space for the char pointers */
117   new_data_sources_num = m->data_sources_num + ((size_t) ci->values_num);
118   temp = (char **) realloc (m->data_sources,
119       new_data_sources_num * sizeof (char *));
120   if (temp == NULL)
121   {
122     ERROR ("`value' match: realloc failed.");
123     return (-1);
124   }
125   m->data_sources = temp;
126
127   /* Copy the strings, allocating memory as needed. */
128   for (i = 0; i < ci->values_num; i++)
129   {
130     size_t j;
131
132     /* If we get here, there better be memory for us to write to. */
133     assert (m->data_sources_num < new_data_sources_num);
134
135     j = m->data_sources_num;
136     m->data_sources[j] = sstrdup (ci->values[i].value.string);
137     if (m->data_sources[j] == NULL)
138     {
139       ERROR ("`value' match: sstrdup failed.");
140       continue;
141     }
142     m->data_sources_num++;
143   }
144
145   return (0);
146 } /* }}} int mv_config_add_data_source */
147
148 static int mv_config_add_gauge (gauge_t *ret_value, /* {{{ */
149     oconfig_item_t *ci)
150 {
151
152   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
153   {
154     ERROR ("`value' match: `%s' needs exactly one numeric argument.",
155         ci->key);
156     return (-1);
157   }
158
159   *ret_value = ci->values[0].value.number;
160
161   return (0);
162 } /* }}} int mv_config_add_gauge */
163
164 static int mv_config_add_boolean (int *ret_value, /* {{{ */
165     oconfig_item_t *ci)
166 {
167
168   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
169   {
170     ERROR ("`value' match: `%s' needs exactly one boolean argument.",
171         ci->key);
172     return (-1);
173   }
174
175   if (ci->values[0].value.boolean)
176     *ret_value = 1;
177   else
178     *ret_value = 0;
179
180   return (0);
181 } /* }}} int mv_config_add_boolean */
182
183 static int mv_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
184 {
185   mv_match_t *m;
186   int status;
187   int i;
188
189   m = (mv_match_t *) malloc (sizeof (*m));
190   if (m == NULL)
191   {
192     ERROR ("mv_create: malloc failed.");
193     return (-ENOMEM);
194   }
195   memset (m, 0, sizeof (*m));
196
197   m->min = NAN;
198   m->max = NAN;
199   m->invert = 0;
200   m->satisfy = SATISFY_ALL;
201   m->data_sources = NULL;
202   m->data_sources_num = 0;
203
204   status = 0;
205   for (i = 0; i < ci->children_num; i++)
206   {
207     oconfig_item_t *child = ci->children + i;
208
209     if (strcasecmp ("Min", child->key) == 0)
210       status = mv_config_add_gauge (&m->min, child);
211     else if (strcasecmp ("Max", child->key) == 0)
212       status = mv_config_add_gauge (&m->max, child);
213     else if (strcasecmp ("Invert", child->key) == 0)
214       status = mv_config_add_boolean (&m->invert, child);
215     else if (strcasecmp ("Satisfy", child->key) == 0)
216       status = mv_config_add_satisfy (m, child);
217     else if (strcasecmp ("DataSource", child->key) == 0)
218       status = mv_config_add_data_source (m, child);
219     else
220     {
221       ERROR ("`value' match: The `%s' configuration option is not "
222           "understood and will be ignored.", child->key);
223       status = 0;
224     }
225
226     if (status != 0)
227       break;
228   }
229
230   /* Additional sanity-checking */
231   while (status == 0)
232   {
233     if (isnan (m->min) && isnan (m->max))
234     {
235       ERROR ("`value' match: Neither minimum nor maximum are defined. "
236           "This match will be ignored.");
237       status = -1;
238     }
239
240     break;
241   }
242
243   if (status != 0)
244   {
245     mv_free_match (m);
246     return (status);
247   }
248
249   *user_data = m;
250   return (0);
251 } /* }}} int mv_create */
252
253 static int mv_destroy (void **user_data) /* {{{ */
254 {
255   if ((user_data != NULL) && (*user_data != NULL))
256     mv_free_match (*user_data);
257   return (0);
258 } /* }}} int mv_destroy */
259
260 static int mv_match (const data_set_t *ds, const value_list_t *vl, /* {{{ */
261     notification_meta_t **meta, void **user_data)
262 {
263   mv_match_t *m;
264   gauge_t *values;
265   int status;
266   int i;
267
268   if ((user_data == NULL) || (*user_data == NULL))
269     return (-1);
270
271   m = *user_data;
272
273   values = uc_get_rate (ds, vl);
274   if (values == NULL)
275   {
276     ERROR ("`value' match: Retrieving the current rate from the cache "
277         "failed.");
278     return (-1);
279   }
280
281   status = FC_MATCH_NO_MATCH;
282
283   for (i = 0; i < ds->ds_num; i++)
284   {
285     int value_matches = 0;
286
287     /* Check if this data source is relevant. */
288     if (m->data_sources != NULL)
289     {
290       size_t j;
291
292       for (j = 0; j < m->data_sources_num; j++)
293         if (strcasecmp (ds->ds[i].name, m->data_sources[j]) == 0)
294           break;
295
296       /* No match, ignore this data source. */
297       if (j >=  m->data_sources_num)
298         continue;
299     }
300
301     DEBUG ("`value' match: current = %g; min = %g; max = %g; invert = %s;",
302         values[i], m->min, m->max,
303         m->invert ? "true" : "false");
304
305     if ((!isnan (m->min) && (values[i] < m->min))
306         || (!isnan (m->max) && (values[i] > m->max)))
307       value_matches = 0;
308     else
309       value_matches = 1;
310
311     if (m->invert)
312     {
313       if (value_matches)
314         value_matches = 0;
315       else
316         value_matches = 1;
317     }
318
319     if (value_matches != 0)
320     {
321       status = FC_MATCH_MATCHES;
322       if (m->satisfy == SATISFY_ANY)
323         break;
324     }
325     else if (value_matches == 0)
326     {
327       status = FC_MATCH_NO_MATCH;
328       if (m->satisfy == SATISFY_ALL)
329         break;
330     }
331   } /* for (i = 0; i < ds->ds_num; i++) */
332
333   free (values);
334   return (status);
335 } /* }}} int mv_match */
336
337 void module_register (void)
338 {
339   match_proc_t mproc;
340
341   memset (&mproc, 0, sizeof (mproc));
342   mproc.create  = mv_create;
343   mproc.destroy = mv_destroy;
344   mproc.match   = mv_match;
345   fc_register_match ("value", mproc);
346 } /* module_register */
347
348 /* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
349