8bcaf408aab1ce7dda6b880b201d8c51f4972a69
1 /**
2 * collectd - src/utils_vl_lookup.c
3 * Copyright (C) 2012 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 **/
27 #include "collectd.h"
29 #include <pthread.h>
30 #include <regex.h>
32 #include "common.h"
33 #include "utils_avltree.h"
34 #include "utils_vl_lookup.h"
36 #if HAVE_LIBKSTAT
37 kstat_ctl_t *kc;
38 #endif /* HAVE_LIBKSTAT */
40 #if BUILD_TEST
41 #define sstrncpy strncpy
42 #define plugin_log(s, ...) \
43 do { \
44 printf("[severity %i] ", s); \
45 printf(__VA_ARGS__); \
46 printf("\n"); \
47 } while (0)
48 #endif
50 /*
51 * Types
52 */
53 struct part_match_s {
54 char str[DATA_MAX_NAME_LEN];
55 regex_t regex;
56 _Bool is_regex;
57 };
58 typedef struct part_match_s part_match_t;
60 struct identifier_match_s {
61 part_match_t host;
62 part_match_t plugin;
63 part_match_t plugin_instance;
64 part_match_t type;
65 part_match_t type_instance;
67 unsigned int group_by;
68 };
69 typedef struct identifier_match_s identifier_match_t;
71 struct lookup_s {
72 c_avl_tree_t *by_type_tree;
74 lookup_class_callback_t cb_user_class;
75 lookup_obj_callback_t cb_user_obj;
76 lookup_free_class_callback_t cb_free_class;
77 lookup_free_obj_callback_t cb_free_obj;
78 };
80 struct user_obj_s;
81 typedef struct user_obj_s user_obj_t;
82 struct user_obj_s {
83 void *user_obj;
84 lookup_identifier_t ident;
86 user_obj_t *next;
87 };
89 struct user_class_s {
90 pthread_mutex_t lock;
91 void *user_class;
92 identifier_match_t match;
93 user_obj_t *user_obj_list; /* list of user_obj */
94 };
95 typedef struct user_class_s user_class_t;
97 struct user_class_list_s;
98 typedef struct user_class_list_s user_class_list_t;
99 struct user_class_list_s {
100 user_class_t entry;
101 user_class_list_t *next;
102 };
104 struct by_type_entry_s {
105 c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */
106 user_class_list_t *wildcard_plugin_list;
107 };
108 typedef struct by_type_entry_s by_type_entry_t;
110 /*
111 * Private functions
112 */
113 static _Bool lu_part_matches(part_match_t const *match, /* {{{ */
114 char const *str) {
115 if (match->is_regex) {
116 /* Short cut popular catch-all regex. */
117 if (strcmp(".*", match->str) == 0)
118 return (1);
120 int status = regexec(&match->regex, str,
121 /* nmatch = */ 0, /* pmatch = */ NULL,
122 /* flags = */ 0);
123 if (status == 0)
124 return (1);
125 else
126 return (0);
127 } else if (strcmp(match->str, str) == 0)
128 return (1);
129 else
130 return (0);
131 } /* }}} _Bool lu_part_matches */
133 static int lu_copy_ident_to_match_part(part_match_t *match_part, /* {{{ */
134 char const *ident_part) {
135 size_t len = strlen(ident_part);
136 int status;
138 if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/')) {
139 sstrncpy(match_part->str, ident_part, sizeof(match_part->str));
140 match_part->is_regex = 0;
141 return (0);
142 }
144 /* Copy string without the leading slash. */
145 sstrncpy(match_part->str, ident_part + 1, sizeof(match_part->str));
146 assert(sizeof(match_part->str) > len);
147 /* strip trailing slash */
148 match_part->str[len - 2] = 0;
150 status = regcomp(&match_part->regex, match_part->str,
151 /* flags = */ REG_EXTENDED);
152 if (status != 0) {
153 char errbuf[1024];
154 regerror(status, &match_part->regex, errbuf, sizeof(errbuf));
155 ERROR("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s",
156 match_part->str, errbuf);
157 return (EINVAL);
158 }
159 match_part->is_regex = 1;
161 return (0);
162 } /* }}} int lu_copy_ident_to_match_part */
164 static int lu_copy_ident_to_match(identifier_match_t *match, /* {{{ */
165 lookup_identifier_t const *ident,
166 unsigned int group_by) {
167 memset(match, 0, sizeof(*match));
169 match->group_by = group_by;
171 #define COPY_FIELD(field) \
172 do { \
173 int status = lu_copy_ident_to_match_part(&match->field, ident->field); \
174 if (status != 0) \
175 return (status); \
176 } while (0)
178 COPY_FIELD(host);
179 COPY_FIELD(plugin);
180 COPY_FIELD(plugin_instance);
181 COPY_FIELD(type);
182 COPY_FIELD(type_instance);
184 #undef COPY_FIELD
186 return (0);
187 } /* }}} int lu_copy_ident_to_match */
189 /* user_class->lock must be held when calling this function */
190 static void *lu_create_user_obj(lookup_t *obj, /* {{{ */
191 data_set_t const *ds, value_list_t const *vl,
192 user_class_t *user_class) {
193 user_obj_t *user_obj;
195 user_obj = calloc(1, sizeof(*user_obj));
196 if (user_obj == NULL) {
197 ERROR("utils_vl_lookup: calloc failed.");
198 return (NULL);
199 }
200 user_obj->next = NULL;
202 user_obj->user_obj = obj->cb_user_class(ds, vl, user_class->user_class);
203 if (user_obj->user_obj == NULL) {
204 sfree(user_obj);
205 WARNING("utils_vl_lookup: User-provided constructor failed.");
206 return (NULL);
207 }
209 #define COPY_FIELD(field, group_mask) \
210 do { \
211 if (user_class->match.field.is_regex && \
212 ((user_class->match.group_by & group_mask) == 0)) \
213 sstrncpy(user_obj->ident.field, "/.*/", sizeof(user_obj->ident.field)); \
214 else \
215 sstrncpy(user_obj->ident.field, vl->field, \
216 sizeof(user_obj->ident.field)); \
217 } while (0)
219 COPY_FIELD(host, LU_GROUP_BY_HOST);
220 COPY_FIELD(plugin, LU_GROUP_BY_PLUGIN);
221 COPY_FIELD(plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE);
222 COPY_FIELD(type, 0);
223 COPY_FIELD(type_instance, LU_GROUP_BY_TYPE_INSTANCE);
225 #undef COPY_FIELD
227 if (user_class->user_obj_list == NULL) {
228 user_class->user_obj_list = user_obj;
229 } else {
230 user_obj_t *last = user_class->user_obj_list;
231 while (last->next != NULL)
232 last = last->next;
233 last->next = user_obj;
234 }
236 return (user_obj);
237 } /* }}} void *lu_create_user_obj */
239 /* user_class->lock must be held when calling this function */
240 static user_obj_t *lu_find_user_obj(user_class_t *user_class, /* {{{ */
241 value_list_t const *vl) {
242 user_obj_t *ptr;
244 for (ptr = user_class->user_obj_list; ptr != NULL; ptr = ptr->next) {
245 if (user_class->match.host.is_regex &&
246 (user_class->match.group_by & LU_GROUP_BY_HOST) &&
247 (strcmp(vl->host, ptr->ident.host) != 0))
248 continue;
249 if (user_class->match.plugin.is_regex &&
250 (user_class->match.group_by & LU_GROUP_BY_PLUGIN) &&
251 (strcmp(vl->plugin, ptr->ident.plugin) != 0))
252 continue;
253 if (user_class->match.plugin_instance.is_regex &&
254 (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE) &&
255 (strcmp(vl->plugin_instance, ptr->ident.plugin_instance) != 0))
256 continue;
257 if (user_class->match.type_instance.is_regex &&
258 (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE) &&
259 (strcmp(vl->type_instance, ptr->ident.type_instance) != 0))
260 continue;
262 return (ptr);
263 }
265 return (NULL);
266 } /* }}} user_obj_t *lu_find_user_obj */
268 static int lu_handle_user_class(lookup_t *obj, /* {{{ */
269 data_set_t const *ds, value_list_t const *vl,
270 user_class_t *user_class) {
271 user_obj_t *user_obj;
272 int status;
274 assert(strcmp(vl->type, user_class->match.type.str) == 0);
275 assert(user_class->match.plugin.is_regex ||
276 (strcmp(vl->plugin, user_class->match.plugin.str)) == 0);
278 if (!lu_part_matches(&user_class->match.type_instance, vl->type_instance) ||
279 !lu_part_matches(&user_class->match.plugin_instance,
280 vl->plugin_instance) ||
281 !lu_part_matches(&user_class->match.plugin, vl->plugin) ||
282 !lu_part_matches(&user_class->match.host, vl->host))
283 return (1);
285 pthread_mutex_lock(&user_class->lock);
286 user_obj = lu_find_user_obj(user_class, vl);
287 if (user_obj == NULL) {
288 /* call lookup_class_callback_t() and insert into the list of user objects.
289 */
290 user_obj = lu_create_user_obj(obj, ds, vl, user_class);
291 if (user_obj == NULL) {
292 pthread_mutex_unlock(&user_class->lock);
293 return (-1);
294 }
295 }
296 pthread_mutex_unlock(&user_class->lock);
298 status = obj->cb_user_obj(ds, vl, user_class->user_class, user_obj->user_obj);
299 if (status != 0) {
300 ERROR("utils_vl_lookup: The user object callback failed with status %i.",
301 status);
302 /* Returning a negative value means: abort! */
303 if (status < 0)
304 return (status);
305 else
306 return (1);
307 }
309 return (0);
310 } /* }}} int lu_handle_user_class */
312 static int lu_handle_user_class_list(lookup_t *obj, /* {{{ */
313 data_set_t const *ds,
314 value_list_t const *vl,
315 user_class_list_t *user_class_list) {
316 user_class_list_t *ptr;
317 int retval = 0;
319 for (ptr = user_class_list; ptr != NULL; ptr = ptr->next) {
320 int status;
322 status = lu_handle_user_class(obj, ds, vl, &ptr->entry);
323 if (status < 0)
324 return (status);
325 else if (status == 0)
326 retval++;
327 }
329 return (retval);
330 } /* }}} int lu_handle_user_class_list */
332 static by_type_entry_t *lu_search_by_type(lookup_t *obj, /* {{{ */
333 char const *type,
334 _Bool allocate_if_missing) {
335 by_type_entry_t *by_type;
336 char *type_copy;
337 int status;
339 status = c_avl_get(obj->by_type_tree, type, (void *)&by_type);
340 if (status == 0)
341 return (by_type);
343 if (!allocate_if_missing)
344 return (NULL);
346 type_copy = strdup(type);
347 if (type_copy == NULL) {
348 ERROR("utils_vl_lookup: strdup failed.");
349 return (NULL);
350 }
352 by_type = calloc(1, sizeof(*by_type));
353 if (by_type == NULL) {
354 ERROR("utils_vl_lookup: calloc failed.");
355 sfree(type_copy);
356 return (NULL);
357 }
358 by_type->wildcard_plugin_list = NULL;
360 by_type->by_plugin_tree =
361 c_avl_create((int (*)(const void *, const void *))strcmp);
362 if (by_type->by_plugin_tree == NULL) {
363 ERROR("utils_vl_lookup: c_avl_create failed.");
364 sfree(by_type);
365 sfree(type_copy);
366 return (NULL);
367 }
369 status = c_avl_insert(obj->by_type_tree,
370 /* key = */ type_copy, /* value = */ by_type);
371 assert(status <= 0); /* >0 => entry exists => race condition. */
372 if (status != 0) {
373 ERROR("utils_vl_lookup: c_avl_insert failed.");
374 c_avl_destroy(by_type->by_plugin_tree);
375 sfree(by_type);
376 sfree(type_copy);
377 return (NULL);
378 }
380 return (by_type);
381 } /* }}} by_type_entry_t *lu_search_by_type */
383 static int lu_add_by_plugin(by_type_entry_t *by_type, /* {{{ */
384 user_class_list_t *user_class_list) {
385 user_class_list_t *ptr = NULL;
386 identifier_match_t const *match = &user_class_list->entry.match;
388 /* Lookup user_class_list from the per-plugin structure. If this is the first
389 * user_class to be added, the block returns immediately. Otherwise they will
390 * set "ptr" to non-NULL. */
391 if (match->plugin.is_regex) {
392 if (by_type->wildcard_plugin_list == NULL) {
393 by_type->wildcard_plugin_list = user_class_list;
394 return (0);
395 }
397 ptr = by_type->wildcard_plugin_list;
398 } /* if (plugin is wildcard) */
399 else /* (plugin is not wildcard) */
400 {
401 int status;
403 status =
404 c_avl_get(by_type->by_plugin_tree, match->plugin.str, (void *)&ptr);
406 if (status != 0) /* plugin not yet in tree */
407 {
408 char *plugin_copy = strdup(match->plugin.str);
410 if (plugin_copy == NULL) {
411 ERROR("utils_vl_lookup: strdup failed.");
412 sfree(user_class_list);
413 return (ENOMEM);
414 }
416 status =
417 c_avl_insert(by_type->by_plugin_tree, plugin_copy, user_class_list);
418 if (status != 0) {
419 ERROR("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.",
420 plugin_copy, status);
421 sfree(plugin_copy);
422 sfree(user_class_list);
423 return (status);
424 } else {
425 return (0);
426 }
427 } /* if (plugin not yet in tree) */
428 } /* if (plugin is not wildcard) */
430 assert(ptr != NULL);
432 while (ptr->next != NULL)
433 ptr = ptr->next;
434 ptr->next = user_class_list;
436 return (0);
437 } /* }}} int lu_add_by_plugin */
439 static void lu_destroy_user_obj(lookup_t *obj, /* {{{ */
440 user_obj_t *user_obj) {
441 while (user_obj != NULL) {
442 user_obj_t *next = user_obj->next;
444 if (obj->cb_free_obj != NULL)
445 obj->cb_free_obj(user_obj->user_obj);
446 user_obj->user_obj = NULL;
448 sfree(user_obj);
449 user_obj = next;
450 }
451 } /* }}} void lu_destroy_user_obj */
453 static void lu_destroy_user_class_list(lookup_t *obj, /* {{{ */
454 user_class_list_t *user_class_list) {
455 while (user_class_list != NULL) {
456 user_class_list_t *next = user_class_list->next;
458 if (obj->cb_free_class != NULL)
459 obj->cb_free_class(user_class_list->entry.user_class);
460 user_class_list->entry.user_class = NULL;
462 #define CLEAR_FIELD(field) \
463 do { \
464 if (user_class_list->entry.match.field.is_regex) { \
465 regfree(&user_class_list->entry.match.field.regex); \
466 user_class_list->entry.match.field.is_regex = 0; \
467 } \
468 } while (0)
470 CLEAR_FIELD(host);
471 CLEAR_FIELD(plugin);
472 CLEAR_FIELD(plugin_instance);
473 CLEAR_FIELD(type);
474 CLEAR_FIELD(type_instance);
476 #undef CLEAR_FIELD
478 lu_destroy_user_obj(obj, user_class_list->entry.user_obj_list);
479 user_class_list->entry.user_obj_list = NULL;
480 pthread_mutex_destroy(&user_class_list->entry.lock);
482 sfree(user_class_list);
483 user_class_list = next;
484 }
485 } /* }}} void lu_destroy_user_class_list */
487 static void lu_destroy_by_type(lookup_t *obj, /* {{{ */
488 by_type_entry_t *by_type) {
490 while (42) {
491 char *plugin = NULL;
492 user_class_list_t *user_class_list = NULL;
493 int status;
495 status = c_avl_pick(by_type->by_plugin_tree, (void *)&plugin,
496 (void *)&user_class_list);
497 if (status != 0)
498 break;
500 DEBUG("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".",
501 plugin);
502 sfree(plugin);
503 lu_destroy_user_class_list(obj, user_class_list);
504 }
506 c_avl_destroy(by_type->by_plugin_tree);
507 by_type->by_plugin_tree = NULL;
509 lu_destroy_user_class_list(obj, by_type->wildcard_plugin_list);
510 by_type->wildcard_plugin_list = NULL;
512 sfree(by_type);
513 } /* }}} int lu_destroy_by_type */
515 /*
516 * Public functions
517 */
518 lookup_t *lookup_create(lookup_class_callback_t cb_user_class, /* {{{ */
519 lookup_obj_callback_t cb_user_obj,
520 lookup_free_class_callback_t cb_free_class,
521 lookup_free_obj_callback_t cb_free_obj) {
522 lookup_t *obj = calloc(1, sizeof(*obj));
523 if (obj == NULL) {
524 ERROR("utils_vl_lookup: calloc failed.");
525 return (NULL);
526 }
528 obj->by_type_tree = c_avl_create((int (*)(const void *, const void *))strcmp);
529 if (obj->by_type_tree == NULL) {
530 ERROR("utils_vl_lookup: c_avl_create failed.");
531 sfree(obj);
532 return (NULL);
533 }
535 obj->cb_user_class = cb_user_class;
536 obj->cb_user_obj = cb_user_obj;
537 obj->cb_free_class = cb_free_class;
538 obj->cb_free_obj = cb_free_obj;
540 return (obj);
541 } /* }}} lookup_t *lookup_create */
543 void lookup_destroy(lookup_t *obj) /* {{{ */
544 {
545 int status;
547 if (obj == NULL)
548 return;
550 while (42) {
551 char *type = NULL;
552 by_type_entry_t *by_type = NULL;
554 status = c_avl_pick(obj->by_type_tree, (void *)&type, (void *)&by_type);
555 if (status != 0)
556 break;
558 DEBUG("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
559 sfree(type);
560 lu_destroy_by_type(obj, by_type);
561 }
563 c_avl_destroy(obj->by_type_tree);
564 obj->by_type_tree = NULL;
566 sfree(obj);
567 } /* }}} void lookup_destroy */
569 int lookup_add(lookup_t *obj, /* {{{ */
570 lookup_identifier_t const *ident, unsigned int group_by,
571 void *user_class) {
572 by_type_entry_t *by_type = NULL;
573 user_class_list_t *user_class_obj;
575 by_type = lu_search_by_type(obj, ident->type, /* allocate = */ 1);
576 if (by_type == NULL)
577 return (-1);
579 user_class_obj = calloc(1, sizeof(*user_class_obj));
580 if (user_class_obj == NULL) {
581 ERROR("utils_vl_lookup: calloc failed.");
582 return (ENOMEM);
583 }
584 pthread_mutex_init(&user_class_obj->entry.lock, /* attr = */ NULL);
585 user_class_obj->entry.user_class = user_class;
586 lu_copy_ident_to_match(&user_class_obj->entry.match, ident, group_by);
587 user_class_obj->entry.user_obj_list = NULL;
588 user_class_obj->next = NULL;
590 return (lu_add_by_plugin(by_type, user_class_obj));
591 } /* }}} int lookup_add */
593 /* returns the number of successful calls to the callback function */
594 int lookup_search(lookup_t *obj, /* {{{ */
595 data_set_t const *ds, value_list_t const *vl) {
596 by_type_entry_t *by_type = NULL;
597 user_class_list_t *user_class_list = NULL;
598 int retval = 0;
599 int status;
601 if ((obj == NULL) || (ds == NULL) || (vl == NULL))
602 return (-EINVAL);
604 by_type = lu_search_by_type(obj, vl->type, /* allocate = */ 0);
605 if (by_type == NULL)
606 return (0);
608 status =
609 c_avl_get(by_type->by_plugin_tree, vl->plugin, (void *)&user_class_list);
610 if (status == 0) {
611 status = lu_handle_user_class_list(obj, ds, vl, user_class_list);
612 if (status < 0)
613 return (status);
614 retval += status;
615 }
617 if (by_type->wildcard_plugin_list != NULL) {
618 status =
619 lu_handle_user_class_list(obj, ds, vl, by_type->wildcard_plugin_list);
620 if (status < 0)
621 return (status);
622 retval += status;
623 }
625 return (retval);
626 } /* }}} lookup_search */