Merge pull request #3329 from efuss/fix-3311
[collectd.git] / src / match_value.c
1 /**
2  * collectd - src/match_value.c
3  * Copyright (C) 2008       Florian Forster
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a
6  * copy of this software and associated documentation files (the "Software"),
7  * to deal in the Software without restriction, including without limitation
8  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9  * and/or sell copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21  * DEALINGS IN THE SOFTWARE.
22  *
23  * Authors:
24  *   Florian Forster <octo at collectd.org>
25  **/
26
27 /*
28  * This module allows to filter and rewrite value lists based on
29  * Perl-compatible regular expressions.
30  */
31
32 #include "collectd.h"
33
34 #include "filter_chain.h"
35 #include "utils/common/common.h"
36 #include "utils_cache.h"
37
38 #define SATISFY_ALL 0
39 #define SATISFY_ANY 1
40
41 /*
42  * private data types
43  */
44 struct mv_match_s;
45 typedef struct mv_match_s mv_match_t;
46 struct mv_match_s {
47   gauge_t min;
48   gauge_t max;
49   int invert;
50   int satisfy;
51
52   char **data_sources;
53   size_t data_sources_num;
54 };
55
56 /*
57  * internal helper functions
58  */
59 static void mv_free_match(mv_match_t *m) /* {{{ */
60 {
61   if (m == NULL)
62     return;
63
64   if (m->data_sources != NULL) {
65     for (size_t i = 0; i < m->data_sources_num; ++i)
66       free(m->data_sources[i]);
67     free(m->data_sources);
68   }
69
70   free(m);
71 } /* }}} void mv_free_match */
72
73 static int mv_config_add_satisfy(mv_match_t *m, /* {{{ */
74                                  oconfig_item_t *ci) {
75   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING)) {
76     ERROR("`value' match: `%s' needs exactly one string argument.", ci->key);
77     return -1;
78   }
79
80   if (strcasecmp("All", ci->values[0].value.string) == 0)
81     m->satisfy = SATISFY_ALL;
82   else if (strcasecmp("Any", ci->values[0].value.string) == 0)
83     m->satisfy = SATISFY_ANY;
84   else {
85     ERROR("`value' match: Passing `%s' to the `%s' option is invalid. "
86           "The argument must either be `All' or `Any'.",
87           ci->values[0].value.string, ci->key);
88     return -1;
89   }
90
91   return 0;
92 } /* }}} int mv_config_add_satisfy */
93
94 static int mv_config_add_data_source(mv_match_t *m, /* {{{ */
95                                      oconfig_item_t *ci) {
96   size_t new_data_sources_num;
97   char **temp;
98
99   /* Check number of arbuments. */
100   if (ci->values_num < 1) {
101     ERROR("`value' match: `%s' needs at least one argument.", ci->key);
102     return -1;
103   }
104
105   /* Check type of arguments */
106   for (int i = 0; i < ci->values_num; i++) {
107     if (ci->values[i].type == OCONFIG_TYPE_STRING)
108       continue;
109
110     ERROR("`value' match: `%s' accepts only string arguments "
111           "(argument %i is a %s).",
112           ci->key, i + 1,
113           (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) ? "truth value"
114                                                        : "number");
115     return -1;
116   }
117
118   /* Allocate space for the char pointers */
119   new_data_sources_num = m->data_sources_num + ((size_t)ci->values_num);
120   temp = realloc(m->data_sources, new_data_sources_num * sizeof(char *));
121   if (temp == NULL) {
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 (int i = 0; i < ci->values_num; i++) {
129     /* If we get here, there better be memory for us to write to. */
130     assert(m->data_sources_num < new_data_sources_num);
131
132     size_t j = m->data_sources_num;
133     m->data_sources[j] = sstrdup(ci->values[i].value.string);
134     if (m->data_sources[j] == NULL) {
135       ERROR("`value' match: sstrdup failed.");
136       continue;
137     }
138     m->data_sources_num++;
139   }
140
141   return 0;
142 } /* }}} int mv_config_add_data_source */
143
144 static int mv_config_add_gauge(gauge_t *ret_value, /* {{{ */
145                                oconfig_item_t *ci) {
146
147   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER)) {
148     ERROR("`value' match: `%s' needs exactly one numeric argument.", ci->key);
149     return -1;
150   }
151
152   *ret_value = ci->values[0].value.number;
153
154   return 0;
155 } /* }}} int mv_config_add_gauge */
156
157 static int mv_config_add_boolean(int *ret_value, /* {{{ */
158                                  oconfig_item_t *ci) {
159
160   if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN)) {
161     ERROR("`value' match: `%s' needs exactly one boolean argument.", ci->key);
162     return -1;
163   }
164
165   if (ci->values[0].value.boolean)
166     *ret_value = 1;
167   else
168     *ret_value = 0;
169
170   return 0;
171 } /* }}} int mv_config_add_boolean */
172
173 static int mv_create(const oconfig_item_t *ci, void **user_data) /* {{{ */
174 {
175   mv_match_t *m;
176   int status;
177
178   m = calloc(1, sizeof(*m));
179   if (m == NULL) {
180     ERROR("mv_create: calloc failed.");
181     return -ENOMEM;
182   }
183
184   m->min = NAN;
185   m->max = NAN;
186   m->invert = 0;
187   m->satisfy = SATISFY_ALL;
188   m->data_sources = NULL;
189   m->data_sources_num = 0;
190
191   status = 0;
192   for (int i = 0; i < ci->children_num; i++) {
193     oconfig_item_t *child = ci->children + i;
194
195     if (strcasecmp("Min", child->key) == 0)
196       status = mv_config_add_gauge(&m->min, child);
197     else if (strcasecmp("Max", child->key) == 0)
198       status = mv_config_add_gauge(&m->max, child);
199     else if (strcasecmp("Invert", child->key) == 0)
200       status = mv_config_add_boolean(&m->invert, child);
201     else if (strcasecmp("Satisfy", child->key) == 0)
202       status = mv_config_add_satisfy(m, child);
203     else if (strcasecmp("DataSource", child->key) == 0)
204       status = mv_config_add_data_source(m, child);
205     else {
206       ERROR("`value' match: The `%s' configuration option is not "
207             "understood and will be ignored.",
208             child->key);
209       status = 0;
210     }
211
212     if (status != 0)
213       break;
214   }
215
216   /* Additional sanity-checking */
217   while (status == 0) {
218     if (isnan(m->min) && isnan(m->max)) {
219       ERROR("`value' match: Neither minimum nor maximum are defined. "
220             "This match will be ignored.");
221       status = -1;
222     }
223
224     break;
225   }
226
227   if (status != 0) {
228     mv_free_match(m);
229     return status;
230   }
231
232   *user_data = m;
233   return 0;
234 } /* }}} int mv_create */
235
236 static int mv_destroy(void **user_data) /* {{{ */
237 {
238   if ((user_data != NULL) && (*user_data != NULL))
239     mv_free_match(*user_data);
240   return 0;
241 } /* }}} int mv_destroy */
242
243 static int mv_match(const data_set_t *ds, const value_list_t *vl, /* {{{ */
244                     notification_meta_t __attribute__((unused)) * *meta,
245                     void **user_data) {
246   mv_match_t *m;
247   gauge_t *values;
248   int status;
249
250   if ((user_data == NULL) || (*user_data == NULL))
251     return -1;
252
253   m = *user_data;
254
255   values = uc_get_rate(ds, vl);
256   if (values == NULL) {
257     ERROR("`value' match: Retrieving the current rate from the cache "
258           "failed.");
259     return -1;
260   }
261
262   status = FC_MATCH_NO_MATCH;
263
264   for (size_t i = 0; i < ds->ds_num; i++) {
265     int value_matches = 0;
266
267     /* Check if this data source is relevant. */
268     if (m->data_sources != NULL) {
269       size_t j;
270
271       for (j = 0; j < m->data_sources_num; j++)
272         if (strcasecmp(ds->ds[i].name, m->data_sources[j]) == 0)
273           break;
274
275       /* No match, ignore this data source. */
276       if (j >= m->data_sources_num)
277         continue;
278     }
279
280     DEBUG("`value' match: current = %g; min = %g; max = %g; invert = %s;",
281           values[i], m->min, m->max, m->invert ? "true" : "false");
282
283     if ((!isnan(m->min) && (values[i] < m->min)) ||
284         (!isnan(m->max) && (values[i] > m->max)))
285       value_matches = 0;
286     else
287       value_matches = 1;
288
289     if (m->invert) {
290       if (value_matches)
291         value_matches = 0;
292       else
293         value_matches = 1;
294     }
295
296     if (value_matches != 0) {
297       status = FC_MATCH_MATCHES;
298       if (m->satisfy == SATISFY_ANY)
299         break;
300     } else {
301       status = FC_MATCH_NO_MATCH;
302       if (m->satisfy == SATISFY_ALL)
303         break;
304     }
305   } /* for (i = 0; i < ds->ds_num; i++) */
306
307   free(values);
308   return status;
309 } /* }}} int mv_match */
310
311 void module_register(void) {
312   match_proc_t mproc = {0};
313
314   mproc.create = mv_create;
315   mproc.destroy = mv_destroy;
316   mproc.match = mv_match;
317   fc_register_match("value", mproc);
318 } /* module_register */