Merge branch 'collectd-5.6' into collectd-5.7
[collectd.git] / src / utils_match.c
1 /**
2  * collectd - src/utils_match.c
3  * Copyright (C) 2008-2014  Florian octo 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 octo Forster <octo at collectd.org>
25  **/
26
27 #include "collectd.h"
28
29 #include "common.h"
30 #include "plugin.h"
31
32 #include "utils_match.h"
33
34 #include <regex.h>
35
36 #define UTILS_MATCH_FLAGS_EXCLUDE_REGEX 0x02
37
38 struct cu_match_s {
39   regex_t regex;
40   regex_t excluderegex;
41   int flags;
42
43   int (*callback)(const char *str, char *const *matches, size_t matches_num,
44                   void *user_data);
45   void *user_data;
46   void (*free)(void *user_data);
47 };
48
49 /*
50  * Private functions
51  */
52 static char *match_substr(const char *str, int begin, int end) {
53   char *ret;
54   size_t ret_len;
55
56   if ((begin < 0) || (end < 0) || (begin >= end))
57     return (NULL);
58   if ((size_t)end > (strlen(str) + 1)) {
59     ERROR("utils_match: match_substr: `end' points after end of string.");
60     return (NULL);
61   }
62
63   ret_len = end - begin;
64   ret = malloc(ret_len + 1);
65   if (ret == NULL) {
66     ERROR("utils_match: match_substr: malloc failed.");
67     return (NULL);
68   }
69
70   sstrncpy(ret, str + begin, ret_len + 1);
71   return (ret);
72 } /* char *match_substr */
73
74 static int default_callback(const char __attribute__((unused)) * str,
75                             char *const *matches, size_t matches_num,
76                             void *user_data) {
77   cu_match_value_t *data = (cu_match_value_t *)user_data;
78
79   if (data->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) {
80     gauge_t value;
81     char *endptr = NULL;
82
83     if (data->ds_type & UTILS_MATCH_CF_GAUGE_INC) {
84       data->value.gauge = isnan(data->value.gauge) ? 1 : data->value.gauge + 1;
85       data->values_num++;
86       return (0);
87     }
88
89     if (matches_num < 2)
90       return (-1);
91
92     value = (gauge_t)strtod(matches[1], &endptr);
93     if (matches[1] == endptr)
94       return (-1);
95
96     if (data->ds_type & UTILS_MATCH_CF_GAUGE_DIST) {
97       latency_counter_add(data->latency, DOUBLE_TO_CDTIME_T(value));
98       data->values_num++;
99       return (0);
100     }
101
102     if ((data->values_num == 0) ||
103         (data->ds_type & UTILS_MATCH_CF_GAUGE_LAST) ||
104         (data->ds_type & UTILS_MATCH_CF_GAUGE_PERSIST)) {
105       data->value.gauge = value;
106     } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_AVERAGE) {
107       double f = ((double)data->values_num) / ((double)(data->values_num + 1));
108       data->value.gauge = (data->value.gauge * f) + (value * (1.0 - f));
109     } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MIN) {
110       if (data->value.gauge > value)
111         data->value.gauge = value;
112     } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MAX) {
113       if (data->value.gauge < value)
114         data->value.gauge = value;
115     } else if (data->ds_type & UTILS_MATCH_CF_GAUGE_ADD) {
116       data->value.gauge += value;
117     } else {
118       ERROR("utils_match: default_callback: obj->ds_type is invalid!");
119       return (-1);
120     }
121
122     data->values_num++;
123   } else if (data->ds_type & UTILS_MATCH_DS_TYPE_COUNTER) {
124     counter_t value;
125     char *endptr = NULL;
126
127     if (data->ds_type & UTILS_MATCH_CF_COUNTER_INC) {
128       data->value.counter++;
129       data->values_num++;
130       return (0);
131     }
132
133     if (matches_num < 2)
134       return (-1);
135
136     value = (counter_t)strtoull(matches[1], &endptr, 0);
137     if (matches[1] == endptr)
138       return (-1);
139
140     if (data->ds_type & UTILS_MATCH_CF_COUNTER_SET)
141       data->value.counter = value;
142     else if (data->ds_type & UTILS_MATCH_CF_COUNTER_ADD)
143       data->value.counter += value;
144     else {
145       ERROR("utils_match: default_callback: obj->ds_type is invalid!");
146       return (-1);
147     }
148
149     data->values_num++;
150   } else if (data->ds_type & UTILS_MATCH_DS_TYPE_DERIVE) {
151     derive_t value;
152     char *endptr = NULL;
153
154     if (data->ds_type & UTILS_MATCH_CF_DERIVE_INC) {
155       data->value.derive++;
156       data->values_num++;
157       return (0);
158     }
159
160     if (matches_num < 2)
161       return (-1);
162
163     value = (derive_t)strtoll(matches[1], &endptr, 0);
164     if (matches[1] == endptr)
165       return (-1);
166
167     if (data->ds_type & UTILS_MATCH_CF_DERIVE_SET)
168       data->value.derive = value;
169     else if (data->ds_type & UTILS_MATCH_CF_DERIVE_ADD)
170       data->value.derive += value;
171     else {
172       ERROR("utils_match: default_callback: obj->ds_type is invalid!");
173       return (-1);
174     }
175
176     data->values_num++;
177   } else if (data->ds_type & UTILS_MATCH_DS_TYPE_ABSOLUTE) {
178     absolute_t value;
179     char *endptr = NULL;
180
181     if (matches_num < 2)
182       return (-1);
183
184     value = (absolute_t)strtoull(matches[1], &endptr, 0);
185     if (matches[1] == endptr)
186       return (-1);
187
188     if (data->ds_type & UTILS_MATCH_CF_ABSOLUTE_SET)
189       data->value.absolute = value;
190     else {
191       ERROR("utils_match: default_callback: obj->ds_type is invalid!");
192       return (-1);
193     }
194
195     data->values_num++;
196   } else {
197     ERROR("utils_match: default_callback: obj->ds_type is invalid!");
198     return (-1);
199   }
200
201   return (0);
202 } /* int default_callback */
203
204 static void match_simple_free(void *data) {
205   cu_match_value_t *user_data = (cu_match_value_t *)data;
206   if (user_data->latency)
207     latency_counter_destroy(user_data->latency);
208
209   free(data);
210 } /* void match_simple_free */
211
212 /*
213  * Public functions
214  */
215 cu_match_t *
216 match_create_callback(const char *regex, const char *excluderegex,
217                       int (*callback)(const char *str, char *const *matches,
218                                       size_t matches_num, void *user_data),
219                       void *user_data,
220                       void (*free_user_data)(void *user_data)) {
221   cu_match_t *obj;
222   int status;
223
224   DEBUG("utils_match: match_create_callback: regex = %s, excluderegex = %s",
225         regex, excluderegex);
226
227   obj = calloc(1, sizeof(*obj));
228   if (obj == NULL)
229     return (NULL);
230
231   status = regcomp(&obj->regex, regex, REG_EXTENDED | REG_NEWLINE);
232   if (status != 0) {
233     ERROR("Compiling the regular expression \"%s\" failed.", regex);
234     sfree(obj);
235     return (NULL);
236   }
237
238   if (excluderegex && strcmp(excluderegex, "") != 0) {
239     status = regcomp(&obj->excluderegex, excluderegex, REG_EXTENDED);
240     if (status != 0) {
241       ERROR("Compiling the excluding regular expression \"%s\" failed.",
242             excluderegex);
243       sfree(obj);
244       return (NULL);
245     }
246     obj->flags |= UTILS_MATCH_FLAGS_EXCLUDE_REGEX;
247   }
248
249   obj->callback = callback;
250   obj->user_data = user_data;
251   obj->free = free_user_data;
252
253   return (obj);
254 } /* cu_match_t *match_create_callback */
255
256 cu_match_t *match_create_simple(const char *regex, const char *excluderegex,
257                                 int match_ds_type) {
258   cu_match_value_t *user_data;
259   cu_match_t *obj;
260
261   user_data = calloc(1, sizeof(*user_data));
262   if (user_data == NULL)
263     return (NULL);
264   user_data->ds_type = match_ds_type;
265
266   if ((match_ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
267       (match_ds_type & UTILS_MATCH_CF_GAUGE_DIST)) {
268     user_data->latency = latency_counter_create();
269     if (user_data->latency == NULL) {
270       ERROR("match_create_simple(): latency_counter_create() failed.");
271       free(user_data);
272       return (NULL);
273     }
274   }
275
276   obj = match_create_callback(regex, excluderegex, default_callback, user_data,
277                               match_simple_free);
278   if (obj == NULL) {
279     if (user_data->latency)
280       latency_counter_destroy(user_data->latency);
281
282     sfree(user_data);
283     return (NULL);
284   }
285   return (obj);
286 } /* cu_match_t *match_create_simple */
287
288 void match_value_reset(cu_match_value_t *mv) {
289   if (mv == NULL)
290     return;
291
292   /* Reset GAUGE metrics only and except GAUGE_PERSIST. */
293   if ((mv->ds_type & UTILS_MATCH_DS_TYPE_GAUGE) &&
294       !(mv->ds_type & UTILS_MATCH_CF_GAUGE_PERSIST)) {
295     mv->value.gauge = NAN;
296     mv->values_num = 0;
297   }
298 } /* }}} void match_value_reset */
299
300 void match_destroy(cu_match_t *obj) {
301   if (obj == NULL)
302     return;
303
304   if ((obj->user_data != NULL) && (obj->free != NULL))
305     (*obj->free)(obj->user_data);
306
307   sfree(obj);
308 } /* void match_destroy */
309
310 int match_apply(cu_match_t *obj, const char *str) {
311   int status;
312   regmatch_t re_match[32];
313   char *matches[32] = {0};
314   size_t matches_num;
315
316   if ((obj == NULL) || (str == NULL))
317     return (-1);
318
319   if (obj->flags & UTILS_MATCH_FLAGS_EXCLUDE_REGEX) {
320     status =
321         regexec(&obj->excluderegex, str, STATIC_ARRAY_SIZE(re_match), re_match,
322                 /* eflags = */ 0);
323     /* Regex did match, so exclude this line */
324     if (status == 0) {
325       DEBUG("ExludeRegex matched, don't count that line\n");
326       return (0);
327     }
328   }
329
330   status = regexec(&obj->regex, str, STATIC_ARRAY_SIZE(re_match), re_match,
331                    /* eflags = */ 0);
332
333   /* Regex did not match */
334   if (status != 0)
335     return (0);
336
337   for (matches_num = 0; matches_num < STATIC_ARRAY_SIZE(matches);
338        matches_num++) {
339     if ((re_match[matches_num].rm_so < 0) || (re_match[matches_num].rm_eo < 0))
340       break;
341
342     matches[matches_num] = match_substr(str, re_match[matches_num].rm_so,
343                                         re_match[matches_num].rm_eo);
344     if (matches[matches_num] == NULL) {
345       status = -1;
346       break;
347     }
348   }
349
350   if (status != 0) {
351     ERROR("utils_match: match_apply: match_substr failed.");
352   } else {
353     status = obj->callback(str, matches, matches_num, obj->user_data);
354     if (status != 0) {
355       ERROR("utils_match: match_apply: callback failed.");
356     }
357   }
358
359   for (size_t i = 0; i < matches_num; i++) {
360     sfree(matches[i]);
361   }
362
363   return (status);
364 } /* int match_apply */
365
366 void *match_get_user_data(cu_match_t *obj) {
367   if (obj == NULL)
368     return (NULL);
369   return (obj->user_data);
370 } /* void *match_get_user_data */
371
372 /* vim: set sw=2 sts=2 ts=8 : */