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_vl_lookup.h"
34 #include "utils_avltree.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, ...) do { \
43 printf ("[severity %i] ", s); \
44 printf (__VA_ARGS__); \
45 printf ("\n"); \
46 } while (0)
47 #endif
49 /*
50 * Types
51 */
52 struct part_match_s
53 {
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 {
62 part_match_t host;
63 part_match_t plugin;
64 part_match_t plugin_instance;
65 part_match_t type;
66 part_match_t type_instance;
68 unsigned int group_by;
69 };
70 typedef struct identifier_match_s identifier_match_t;
72 struct lookup_s
73 {
74 c_avl_tree_t *by_type_tree;
76 lookup_class_callback_t cb_user_class;
77 lookup_obj_callback_t cb_user_obj;
78 lookup_free_class_callback_t cb_free_class;
79 lookup_free_obj_callback_t cb_free_obj;
80 };
82 struct user_obj_s;
83 typedef struct user_obj_s user_obj_t;
84 struct user_obj_s
85 {
86 void *user_obj;
87 identifier_t ident;
89 user_obj_t *next;
90 };
92 struct user_class_s
93 {
94 pthread_mutex_t lock;
95 void *user_class;
96 identifier_match_t match;
97 user_obj_t *user_obj_list; /* list of user_obj */
98 };
99 typedef struct user_class_s user_class_t;
101 struct user_class_list_s;
102 typedef struct user_class_list_s user_class_list_t;
103 struct user_class_list_s
104 {
105 user_class_t entry;
106 user_class_list_t *next;
107 };
109 struct by_type_entry_s
110 {
111 c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */
112 user_class_list_t *wildcard_plugin_list;
113 };
114 typedef struct by_type_entry_s by_type_entry_t;
116 /*
117 * Private functions
118 */
119 static _Bool lu_part_matches (part_match_t const *match, /* {{{ */
120 char const *str)
121 {
122 if (match->is_regex)
123 {
124 /* Short cut popular catch-all regex. */
125 if (strcmp (".*", match->str) == 0)
126 return (1);
128 int status = regexec (&match->regex, str,
129 /* nmatch = */ 0, /* pmatch = */ NULL,
130 /* flags = */ 0);
131 if (status == 0)
132 return (1);
133 else
134 return (0);
135 }
136 else if (strcmp (match->str, str) == 0)
137 return (1);
138 else
139 return (0);
140 } /* }}} _Bool lu_part_matches */
142 static int lu_copy_ident_to_match_part (part_match_t *match_part, /* {{{ */
143 char const *ident_part)
144 {
145 size_t len = strlen (ident_part);
146 int status;
148 if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/'))
149 {
150 sstrncpy (match_part->str, ident_part, sizeof (match_part->str));
151 match_part->is_regex = 0;
152 return (0);
153 }
155 /* Copy string without the leading slash. */
156 sstrncpy (match_part->str, ident_part + 1, sizeof (match_part->str));
157 assert (sizeof (match_part->str) > len);
158 /* strip trailing slash */
159 match_part->str[len - 2] = 0;
161 status = regcomp (&match_part->regex, match_part->str,
162 /* flags = */ REG_EXTENDED);
163 if (status != 0)
164 {
165 char errbuf[1024];
166 regerror (status, &match_part->regex, errbuf, sizeof (errbuf));
167 ERROR ("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s",
168 match_part->str, errbuf);
169 return (EINVAL);
170 }
171 match_part->is_regex = 1;
173 return (0);
174 } /* }}} int lu_copy_ident_to_match_part */
176 static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
177 identifier_t const *ident, unsigned int group_by)
178 {
179 memset (match, 0, sizeof (*match));
181 match->group_by = group_by;
183 #define COPY_FIELD(field) do { \
184 int status = lu_copy_ident_to_match_part (&match->field, ident->field); \
185 if (status != 0) \
186 return (status); \
187 } while (0)
189 COPY_FIELD (host);
190 COPY_FIELD (plugin);
191 COPY_FIELD (plugin_instance);
192 COPY_FIELD (type);
193 COPY_FIELD (type_instance);
195 #undef COPY_FIELD
197 return (0);
198 } /* }}} int lu_copy_ident_to_match */
200 /* user_class->lock must be held when calling this function */
201 static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
202 data_set_t const *ds, value_list_t const *vl,
203 user_class_t *user_class)
204 {
205 user_obj_t *user_obj;
207 user_obj = calloc (1, sizeof (*user_obj));
208 if (user_obj == NULL)
209 {
210 ERROR ("utils_vl_lookup: calloc failed.");
211 return (NULL);
212 }
213 user_obj->next = NULL;
215 user_obj->user_obj = obj->cb_user_class (ds, vl, user_class->user_class);
216 if (user_obj->user_obj == NULL)
217 {
218 sfree (user_obj);
219 WARNING("utils_vl_lookup: User-provided constructor failed.");
220 return (NULL);
221 }
223 #define COPY_FIELD(field, group_mask) do { \
224 if (user_class->match.field.is_regex \
225 && ((user_class->match.group_by & group_mask) == 0)) \
226 sstrncpy (user_obj->ident.field, "/.*/", sizeof (user_obj->ident.field)); \
227 else \
228 sstrncpy (user_obj->ident.field, vl->field, sizeof (user_obj->ident.field)); \
229 } while (0)
231 COPY_FIELD (host, LU_GROUP_BY_HOST);
232 COPY_FIELD (plugin, LU_GROUP_BY_PLUGIN);
233 COPY_FIELD (plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE);
234 COPY_FIELD (type, 0);
235 COPY_FIELD (type_instance, LU_GROUP_BY_TYPE_INSTANCE);
237 #undef COPY_FIELD
239 if (user_class->user_obj_list == NULL)
240 {
241 user_class->user_obj_list = user_obj;
242 }
243 else
244 {
245 user_obj_t *last = user_class->user_obj_list;
246 while (last->next != NULL)
247 last = last->next;
248 last->next = user_obj;
249 }
251 return (user_obj);
252 } /* }}} void *lu_create_user_obj */
254 /* user_class->lock must be held when calling this function */
255 static user_obj_t *lu_find_user_obj (user_class_t *user_class, /* {{{ */
256 value_list_t const *vl)
257 {
258 user_obj_t *ptr;
260 for (ptr = user_class->user_obj_list;
261 ptr != NULL;
262 ptr = ptr->next)
263 {
264 if (user_class->match.host.is_regex
265 && (user_class->match.group_by & LU_GROUP_BY_HOST)
266 && (strcmp (vl->host, ptr->ident.host) != 0))
267 continue;
268 if (user_class->match.plugin.is_regex
269 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN)
270 && (strcmp (vl->plugin, ptr->ident.plugin) != 0))
271 continue;
272 if (user_class->match.plugin_instance.is_regex
273 && (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE)
274 && (strcmp (vl->plugin_instance, ptr->ident.plugin_instance) != 0))
275 continue;
276 if (user_class->match.type_instance.is_regex
277 && (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE)
278 && (strcmp (vl->type_instance, ptr->ident.type_instance) != 0))
279 continue;
281 return (ptr);
282 }
284 return (NULL);
285 } /* }}} user_obj_t *lu_find_user_obj */
287 static int lu_handle_user_class (lookup_t *obj, /* {{{ */
288 data_set_t const *ds, value_list_t const *vl,
289 user_class_t *user_class)
290 {
291 user_obj_t *user_obj;
292 int status;
294 assert (strcmp (vl->type, user_class->match.type.str) == 0);
295 assert (user_class->match.plugin.is_regex
296 || (strcmp (vl->plugin, user_class->match.plugin.str)) == 0);
298 if (!lu_part_matches (&user_class->match.type_instance, vl->type_instance)
299 || !lu_part_matches (&user_class->match.plugin_instance, vl->plugin_instance)
300 || !lu_part_matches (&user_class->match.plugin, vl->plugin)
301 || !lu_part_matches (&user_class->match.host, vl->host))
302 return (1);
304 pthread_mutex_lock (&user_class->lock);
305 user_obj = lu_find_user_obj (user_class, vl);
306 if (user_obj == NULL)
307 {
308 /* call lookup_class_callback_t() and insert into the list of user objects. */
309 user_obj = lu_create_user_obj (obj, ds, vl, user_class);
310 if (user_obj == NULL) {
311 pthread_mutex_unlock (&user_class->lock);
312 return (-1);
313 }
314 }
315 pthread_mutex_unlock (&user_class->lock);
317 status = obj->cb_user_obj (ds, vl,
318 user_class->user_class, user_obj->user_obj);
319 if (status != 0)
320 {
321 ERROR ("utils_vl_lookup: The user object callback failed with status %i.",
322 status);
323 /* Returning a negative value means: abort! */
324 if (status < 0)
325 return (status);
326 else
327 return (1);
328 }
330 return (0);
331 } /* }}} int lu_handle_user_class */
333 static int lu_handle_user_class_list (lookup_t *obj, /* {{{ */
334 data_set_t const *ds, value_list_t const *vl,
335 user_class_list_t *user_class_list)
336 {
337 user_class_list_t *ptr;
338 int retval = 0;
340 for (ptr = user_class_list; ptr != NULL; ptr = ptr->next)
341 {
342 int status;
344 status = lu_handle_user_class (obj, ds, vl, &ptr->entry);
345 if (status < 0)
346 return (status);
347 else if (status == 0)
348 retval++;
349 }
351 return (retval);
352 } /* }}} int lu_handle_user_class_list */
354 static by_type_entry_t *lu_search_by_type (lookup_t *obj, /* {{{ */
355 char const *type, _Bool allocate_if_missing)
356 {
357 by_type_entry_t *by_type;
358 char *type_copy;
359 int status;
361 status = c_avl_get (obj->by_type_tree, type, (void *) &by_type);
362 if (status == 0)
363 return (by_type);
365 if (!allocate_if_missing)
366 return (NULL);
368 type_copy = strdup (type);
369 if (type_copy == NULL)
370 {
371 ERROR ("utils_vl_lookup: strdup failed.");
372 return (NULL);
373 }
375 by_type = calloc (1, sizeof (*by_type));
376 if (by_type == NULL)
377 {
378 ERROR ("utils_vl_lookup: calloc failed.");
379 sfree (type_copy);
380 return (NULL);
381 }
382 by_type->wildcard_plugin_list = NULL;
384 by_type->by_plugin_tree = c_avl_create ((void *) strcmp);
385 if (by_type->by_plugin_tree == NULL)
386 {
387 ERROR ("utils_vl_lookup: c_avl_create failed.");
388 sfree (by_type);
389 sfree (type_copy);
390 return (NULL);
391 }
393 status = c_avl_insert (obj->by_type_tree,
394 /* key = */ type_copy, /* value = */ by_type);
395 assert (status <= 0); /* >0 => entry exists => race condition. */
396 if (status != 0)
397 {
398 ERROR ("utils_vl_lookup: c_avl_insert failed.");
399 c_avl_destroy (by_type->by_plugin_tree);
400 sfree (by_type);
401 sfree (type_copy);
402 return (NULL);
403 }
405 return (by_type);
406 } /* }}} by_type_entry_t *lu_search_by_type */
408 static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
409 user_class_list_t *user_class_list)
410 {
411 user_class_list_t *ptr = NULL;
412 identifier_match_t const *match = &user_class_list->entry.match;
414 /* Lookup user_class_list from the per-plugin structure. If this is the first
415 * user_class to be added, the block returns immediately. Otherwise they will
416 * set "ptr" to non-NULL. */
417 if (match->plugin.is_regex)
418 {
419 if (by_type->wildcard_plugin_list == NULL)
420 {
421 by_type->wildcard_plugin_list = user_class_list;
422 return (0);
423 }
425 ptr = by_type->wildcard_plugin_list;
426 } /* if (plugin is wildcard) */
427 else /* (plugin is not wildcard) */
428 {
429 int status;
431 status = c_avl_get (by_type->by_plugin_tree,
432 match->plugin.str, (void *) &ptr);
434 if (status != 0) /* plugin not yet in tree */
435 {
436 char *plugin_copy = strdup (match->plugin.str);
438 if (plugin_copy == NULL)
439 {
440 ERROR ("utils_vl_lookup: strdup failed.");
441 sfree (user_class_list);
442 return (ENOMEM);
443 }
445 status = c_avl_insert (by_type->by_plugin_tree,
446 plugin_copy, user_class_list);
447 if (status != 0)
448 {
449 ERROR ("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.",
450 plugin_copy, status);
451 sfree (plugin_copy);
452 sfree (user_class_list);
453 return (status);
454 }
455 else
456 {
457 return (0);
458 }
459 } /* if (plugin not yet in tree) */
460 } /* if (plugin is not wildcard) */
462 assert (ptr != NULL);
464 while (ptr->next != NULL)
465 ptr = ptr->next;
466 ptr->next = user_class_list;
468 return (0);
469 } /* }}} int lu_add_by_plugin */
471 static void lu_destroy_user_obj (lookup_t *obj, /* {{{ */
472 user_obj_t *user_obj)
473 {
474 while (user_obj != NULL)
475 {
476 user_obj_t *next = user_obj->next;
478 if (obj->cb_free_obj != NULL)
479 obj->cb_free_obj (user_obj->user_obj);
480 user_obj->user_obj = NULL;
482 sfree (user_obj);
483 user_obj = next;
484 }
485 } /* }}} void lu_destroy_user_obj */
487 static void lu_destroy_user_class_list (lookup_t *obj, /* {{{ */
488 user_class_list_t *user_class_list)
489 {
490 while (user_class_list != NULL)
491 {
492 user_class_list_t *next = user_class_list->next;
494 if (obj->cb_free_class != NULL)
495 obj->cb_free_class (user_class_list->entry.user_class);
496 user_class_list->entry.user_class = NULL;
498 lu_destroy_user_obj (obj, user_class_list->entry.user_obj_list);
499 user_class_list->entry.user_obj_list = NULL;
500 pthread_mutex_destroy (&user_class_list->entry.lock);
502 sfree (user_class_list);
503 user_class_list = next;
504 }
505 } /* }}} void lu_destroy_user_class_list */
507 static void lu_destroy_by_type (lookup_t *obj, /* {{{ */
508 by_type_entry_t *by_type)
509 {
511 while (42)
512 {
513 char *plugin = NULL;
514 user_class_list_t *user_class_list = NULL;
515 int status;
517 status = c_avl_pick (by_type->by_plugin_tree,
518 (void *) &plugin, (void *) &user_class_list);
519 if (status != 0)
520 break;
522 DEBUG ("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".",
523 plugin);
524 sfree (plugin);
525 lu_destroy_user_class_list (obj, user_class_list);
526 }
528 c_avl_destroy (by_type->by_plugin_tree);
529 by_type->by_plugin_tree = NULL;
531 lu_destroy_user_class_list (obj, by_type->wildcard_plugin_list);
532 by_type->wildcard_plugin_list = NULL;
534 sfree (by_type);
535 } /* }}} int lu_destroy_by_type */
537 /*
538 * Public functions
539 */
540 lookup_t *lookup_create (lookup_class_callback_t cb_user_class, /* {{{ */
541 lookup_obj_callback_t cb_user_obj,
542 lookup_free_class_callback_t cb_free_class,
543 lookup_free_obj_callback_t cb_free_obj)
544 {
545 lookup_t *obj = calloc (1, sizeof (*obj));
546 if (obj == NULL)
547 {
548 ERROR ("utils_vl_lookup: calloc failed.");
549 return (NULL);
550 }
552 obj->by_type_tree = c_avl_create ((void *) strcmp);
553 if (obj->by_type_tree == NULL)
554 {
555 ERROR ("utils_vl_lookup: c_avl_create failed.");
556 sfree (obj);
557 return (NULL);
558 }
560 obj->cb_user_class = cb_user_class;
561 obj->cb_user_obj = cb_user_obj;
562 obj->cb_free_class = cb_free_class;
563 obj->cb_free_obj = cb_free_obj;
565 return (obj);
566 } /* }}} lookup_t *lookup_create */
568 void lookup_destroy (lookup_t *obj) /* {{{ */
569 {
570 int status;
572 if (obj == NULL)
573 return;
575 while (42)
576 {
577 char *type = NULL;
578 by_type_entry_t *by_type = NULL;
580 status = c_avl_pick (obj->by_type_tree, (void *) &type, (void *) &by_type);
581 if (status != 0)
582 break;
584 DEBUG ("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type);
585 sfree (type);
586 lu_destroy_by_type (obj, by_type);
587 }
589 c_avl_destroy (obj->by_type_tree);
590 obj->by_type_tree = NULL;
592 sfree (obj);
593 } /* }}} void lookup_destroy */
595 int lookup_add (lookup_t *obj, /* {{{ */
596 identifier_t const *ident, unsigned int group_by, void *user_class)
597 {
598 by_type_entry_t *by_type = NULL;
599 user_class_list_t *user_class_obj;
601 by_type = lu_search_by_type (obj, ident->type, /* allocate = */ 1);
602 if (by_type == NULL)
603 return (-1);
605 user_class_obj = calloc (1, sizeof (*user_class_obj));
606 if (user_class_obj == NULL)
607 {
608 ERROR ("utils_vl_lookup: calloc failed.");
609 return (ENOMEM);
610 }
611 pthread_mutex_init (&user_class_obj->entry.lock, /* attr = */ NULL);
612 user_class_obj->entry.user_class = user_class;
613 lu_copy_ident_to_match (&user_class_obj->entry.match, ident, group_by);
614 user_class_obj->entry.user_obj_list = NULL;
615 user_class_obj->next = NULL;
617 return (lu_add_by_plugin (by_type, user_class_obj));
618 } /* }}} int lookup_add */
620 /* returns the number of successful calls to the callback function */
621 int lookup_search (lookup_t *obj, /* {{{ */
622 data_set_t const *ds, value_list_t const *vl)
623 {
624 by_type_entry_t *by_type = NULL;
625 user_class_list_t *user_class_list = NULL;
626 int retval = 0;
627 int status;
629 if ((obj == NULL) || (ds == NULL) || (vl == NULL))
630 return (-EINVAL);
632 by_type = lu_search_by_type (obj, vl->type, /* allocate = */ 0);
633 if (by_type == NULL)
634 return (0);
636 status = c_avl_get (by_type->by_plugin_tree,
637 vl->plugin, (void *) &user_class_list);
638 if (status == 0)
639 {
640 status = lu_handle_user_class_list (obj, ds, vl, user_class_list);
641 if (status < 0)
642 return (status);
643 retval += status;
644 }
646 if (by_type->wildcard_plugin_list != NULL)
647 {
648 status = lu_handle_user_class_list (obj, ds, vl,
649 by_type->wildcard_plugin_list);
650 if (status < 0)
651 return (status);
652 retval += status;
653 }
655 return (retval);
656 } /* }}} lookup_search */