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 */
mv_free_match(mv_match_t * m)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
mv_config_add_satisfy(mv_match_t * m,oconfig_item_t * ci)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
mv_config_add_data_source(mv_match_t * m,oconfig_item_t * ci)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
mv_config_add_gauge(gauge_t * ret_value,oconfig_item_t * ci)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
mv_config_add_boolean(int * ret_value,oconfig_item_t * ci)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
mv_create(const oconfig_item_t * ci,void ** user_data)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
mv_destroy(void ** user_data)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
mv_match(const data_set_t * ds,const value_list_t * vl,notification_meta_t ** meta,void ** user_data)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
module_register(void)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 */
319