/** * collectd - src/utils_vl_lookup.c * Copyright (C) 2012 Florian Forster * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Authors: * Florian Forster **/ #include "collectd.h" #include #include #include "utils/avltree/avltree.h" #include "utils/common/common.h" #include "utils/lookup/vl_lookup.h" #if HAVE_KSTAT_H #include #endif #if HAVE_LIBKSTAT kstat_ctl_t *kc; #endif /* HAVE_LIBKSTAT */ #if BUILD_TEST #define sstrncpy strncpy #define plugin_log(s, ...) \ do { \ printf("[severity %i] ", s); \ printf(__VA_ARGS__); \ printf("\n"); \ } while (0) #endif /* * Types */ struct part_match_s { char str[DATA_MAX_NAME_LEN]; regex_t regex; bool is_regex; }; typedef struct part_match_s part_match_t; struct identifier_match_s { part_match_t host; part_match_t plugin; part_match_t plugin_instance; part_match_t type; part_match_t type_instance; unsigned int group_by; }; typedef struct identifier_match_s identifier_match_t; struct lookup_s { c_avl_tree_t *by_type_tree; lookup_class_callback_t cb_user_class; lookup_obj_callback_t cb_user_obj; lookup_free_class_callback_t cb_free_class; lookup_free_obj_callback_t cb_free_obj; }; struct user_obj_s; typedef struct user_obj_s user_obj_t; struct user_obj_s { void *user_obj; lookup_identifier_t ident; user_obj_t *next; }; struct user_class_s { pthread_mutex_t lock; void *user_class; identifier_match_t match; user_obj_t *user_obj_list; /* list of user_obj */ }; typedef struct user_class_s user_class_t; struct user_class_list_s; typedef struct user_class_list_s user_class_list_t; struct user_class_list_s { user_class_t entry; user_class_list_t *next; }; struct by_type_entry_s { c_avl_tree_t *by_plugin_tree; /* plugin -> user_class_list_t */ user_class_list_t *wildcard_plugin_list; }; typedef struct by_type_entry_s by_type_entry_t; /* * Private functions */ static bool lu_part_matches(part_match_t const *match, /* {{{ */ char const *str) { if (match->is_regex) { /* Short cut popular catch-all regex. */ if (strcmp(".*", match->str) == 0) return true; int status = regexec(&match->regex, str, /* nmatch = */ 0, /* pmatch = */ NULL, /* flags = */ 0); if (status == 0) return true; else return false; } else if (strcmp(match->str, str) == 0) return true; else return false; } /* }}} bool lu_part_matches */ static int lu_copy_ident_to_match_part(part_match_t *match_part, /* {{{ */ char const *ident_part) { size_t len = strlen(ident_part); int status; if ((len < 3) || (ident_part[0] != '/') || (ident_part[len - 1] != '/')) { sstrncpy(match_part->str, ident_part, sizeof(match_part->str)); match_part->is_regex = false; return 0; } /* Copy string without the leading slash. */ sstrncpy(match_part->str, ident_part + 1, sizeof(match_part->str)); assert(sizeof(match_part->str) > len); /* strip trailing slash */ match_part->str[len - 2] = 0; status = regcomp(&match_part->regex, match_part->str, /* flags = */ REG_EXTENDED); if (status != 0) { char errbuf[1024]; regerror(status, &match_part->regex, errbuf, sizeof(errbuf)); ERROR("utils_vl_lookup: Compiling regular expression \"%s\" failed: %s", match_part->str, errbuf); return EINVAL; } match_part->is_regex = true; return 0; } /* }}} int lu_copy_ident_to_match_part */ static int lu_copy_ident_to_match(identifier_match_t *match, /* {{{ */ lookup_identifier_t const *ident, unsigned int group_by) { memset(match, 0, sizeof(*match)); match->group_by = group_by; #define COPY_FIELD(field) \ do { \ int status = lu_copy_ident_to_match_part(&match->field, ident->field); \ if (status != 0) \ return status; \ } while (0) COPY_FIELD(host); COPY_FIELD(plugin); COPY_FIELD(plugin_instance); COPY_FIELD(type); COPY_FIELD(type_instance); #undef COPY_FIELD return 0; } /* }}} int lu_copy_ident_to_match */ /* user_class->lock must be held when calling this function */ static void *lu_create_user_obj(lookup_t *obj, /* {{{ */ data_set_t const *ds, value_list_t const *vl, user_class_t *user_class) { user_obj_t *user_obj; user_obj = calloc(1, sizeof(*user_obj)); if (user_obj == NULL) { ERROR("utils_vl_lookup: calloc failed."); return NULL; } user_obj->next = NULL; user_obj->user_obj = obj->cb_user_class(ds, vl, user_class->user_class); if (user_obj->user_obj == NULL) { sfree(user_obj); WARNING("utils_vl_lookup: User-provided constructor failed."); return NULL; } #define COPY_FIELD(field, group_mask) \ do { \ if (user_class->match.field.is_regex && \ ((user_class->match.group_by & group_mask) == 0)) \ sstrncpy(user_obj->ident.field, "/.*/", sizeof(user_obj->ident.field)); \ else \ sstrncpy(user_obj->ident.field, vl->field, \ sizeof(user_obj->ident.field)); \ } while (0) COPY_FIELD(host, LU_GROUP_BY_HOST); COPY_FIELD(plugin, LU_GROUP_BY_PLUGIN); COPY_FIELD(plugin_instance, LU_GROUP_BY_PLUGIN_INSTANCE); COPY_FIELD(type, 0); COPY_FIELD(type_instance, LU_GROUP_BY_TYPE_INSTANCE); #undef COPY_FIELD if (user_class->user_obj_list == NULL) { user_class->user_obj_list = user_obj; } else { user_obj_t *last = user_class->user_obj_list; while (last->next != NULL) last = last->next; last->next = user_obj; } return user_obj; } /* }}} void *lu_create_user_obj */ /* user_class->lock must be held when calling this function */ static user_obj_t *lu_find_user_obj(user_class_t *user_class, /* {{{ */ value_list_t const *vl) { user_obj_t *ptr; for (ptr = user_class->user_obj_list; ptr != NULL; ptr = ptr->next) { if (user_class->match.host.is_regex && (user_class->match.group_by & LU_GROUP_BY_HOST) && (strcmp(vl->host, ptr->ident.host) != 0)) continue; if (user_class->match.plugin.is_regex && (user_class->match.group_by & LU_GROUP_BY_PLUGIN) && (strcmp(vl->plugin, ptr->ident.plugin) != 0)) continue; if (user_class->match.plugin_instance.is_regex && (user_class->match.group_by & LU_GROUP_BY_PLUGIN_INSTANCE) && (strcmp(vl->plugin_instance, ptr->ident.plugin_instance) != 0)) continue; if (user_class->match.type_instance.is_regex && (user_class->match.group_by & LU_GROUP_BY_TYPE_INSTANCE) && (strcmp(vl->type_instance, ptr->ident.type_instance) != 0)) continue; return ptr; } return NULL; } /* }}} user_obj_t *lu_find_user_obj */ static int lu_handle_user_class(lookup_t *obj, /* {{{ */ data_set_t const *ds, value_list_t const *vl, user_class_t *user_class) { user_obj_t *user_obj; int status; assert(strcmp(vl->type, user_class->match.type.str) == 0); assert(user_class->match.plugin.is_regex || (strcmp(vl->plugin, user_class->match.plugin.str)) == 0); if (!lu_part_matches(&user_class->match.type_instance, vl->type_instance) || !lu_part_matches(&user_class->match.plugin_instance, vl->plugin_instance) || !lu_part_matches(&user_class->match.plugin, vl->plugin) || !lu_part_matches(&user_class->match.host, vl->host)) return 1; pthread_mutex_lock(&user_class->lock); user_obj = lu_find_user_obj(user_class, vl); if (user_obj == NULL) { /* call lookup_class_callback_t() and insert into the list of user objects. */ user_obj = lu_create_user_obj(obj, ds, vl, user_class); if (user_obj == NULL) { pthread_mutex_unlock(&user_class->lock); return -1; } } pthread_mutex_unlock(&user_class->lock); status = obj->cb_user_obj(ds, vl, user_class->user_class, user_obj->user_obj); if (status != 0) { ERROR("utils_vl_lookup: The user object callback failed with status %i.", status); /* Returning a negative value means: abort! */ if (status < 0) return status; else return 1; } return 0; } /* }}} int lu_handle_user_class */ static int lu_handle_user_class_list(lookup_t *obj, /* {{{ */ data_set_t const *ds, value_list_t const *vl, user_class_list_t *user_class_list) { user_class_list_t *ptr; int retval = 0; for (ptr = user_class_list; ptr != NULL; ptr = ptr->next) { int status; status = lu_handle_user_class(obj, ds, vl, &ptr->entry); if (status < 0) return status; else if (status == 0) retval++; } return retval; } /* }}} int lu_handle_user_class_list */ static by_type_entry_t *lu_search_by_type(lookup_t *obj, /* {{{ */ char const *type, bool allocate_if_missing) { by_type_entry_t *by_type; char *type_copy; int status; status = c_avl_get(obj->by_type_tree, type, (void *)&by_type); if (status == 0) return by_type; if (!allocate_if_missing) return NULL; type_copy = strdup(type); if (type_copy == NULL) { ERROR("utils_vl_lookup: strdup failed."); return NULL; } by_type = calloc(1, sizeof(*by_type)); if (by_type == NULL) { ERROR("utils_vl_lookup: calloc failed."); sfree(type_copy); return NULL; } by_type->wildcard_plugin_list = NULL; by_type->by_plugin_tree = c_avl_create((int (*)(const void *, const void *))strcmp); if (by_type->by_plugin_tree == NULL) { ERROR("utils_vl_lookup: c_avl_create failed."); sfree(by_type); sfree(type_copy); return NULL; } status = c_avl_insert(obj->by_type_tree, /* key = */ type_copy, /* value = */ by_type); assert(status <= 0); /* >0 => entry exists => race condition. */ if (status != 0) { ERROR("utils_vl_lookup: c_avl_insert failed."); c_avl_destroy(by_type->by_plugin_tree); sfree(by_type); sfree(type_copy); return NULL; } return by_type; } /* }}} by_type_entry_t *lu_search_by_type */ static int lu_add_by_plugin(by_type_entry_t *by_type, /* {{{ */ user_class_list_t *user_class_list) { user_class_list_t *ptr = NULL; identifier_match_t const *match = &user_class_list->entry.match; /* Lookup user_class_list from the per-plugin structure. If this is the first * user_class to be added, the block returns immediately. Otherwise they will * set "ptr" to non-NULL. */ if (match->plugin.is_regex) { if (by_type->wildcard_plugin_list == NULL) { by_type->wildcard_plugin_list = user_class_list; return 0; } ptr = by_type->wildcard_plugin_list; } /* if (plugin is wildcard) */ else /* (plugin is not wildcard) */ { int status; status = c_avl_get(by_type->by_plugin_tree, match->plugin.str, (void *)&ptr); if (status != 0) /* plugin not yet in tree */ { char *plugin_copy = strdup(match->plugin.str); if (plugin_copy == NULL) { ERROR("utils_vl_lookup: strdup failed."); sfree(user_class_list); return ENOMEM; } status = c_avl_insert(by_type->by_plugin_tree, plugin_copy, user_class_list); if (status != 0) { ERROR("utils_vl_lookup: c_avl_insert(\"%s\") failed with status %i.", plugin_copy, status); sfree(plugin_copy); sfree(user_class_list); return status; } else { return 0; } } /* if (plugin not yet in tree) */ } /* if (plugin is not wildcard) */ assert(ptr != NULL); while (ptr->next != NULL) ptr = ptr->next; ptr->next = user_class_list; return 0; } /* }}} int lu_add_by_plugin */ static void lu_destroy_user_obj(lookup_t *obj, /* {{{ */ user_obj_t *user_obj) { while (user_obj != NULL) { user_obj_t *next = user_obj->next; if (obj->cb_free_obj != NULL) obj->cb_free_obj(user_obj->user_obj); user_obj->user_obj = NULL; sfree(user_obj); user_obj = next; } } /* }}} void lu_destroy_user_obj */ static void lu_destroy_user_class_list(lookup_t *obj, /* {{{ */ user_class_list_t *user_class_list) { while (user_class_list != NULL) { user_class_list_t *next = user_class_list->next; if (obj->cb_free_class != NULL) obj->cb_free_class(user_class_list->entry.user_class); user_class_list->entry.user_class = NULL; #define CLEAR_FIELD(field) \ do { \ if (user_class_list->entry.match.field.is_regex) { \ regfree(&user_class_list->entry.match.field.regex); \ user_class_list->entry.match.field.is_regex = false; \ } \ } while (0) CLEAR_FIELD(host); CLEAR_FIELD(plugin); CLEAR_FIELD(plugin_instance); CLEAR_FIELD(type); CLEAR_FIELD(type_instance); #undef CLEAR_FIELD lu_destroy_user_obj(obj, user_class_list->entry.user_obj_list); user_class_list->entry.user_obj_list = NULL; pthread_mutex_destroy(&user_class_list->entry.lock); sfree(user_class_list); user_class_list = next; } } /* }}} void lu_destroy_user_class_list */ static void lu_destroy_by_type(lookup_t *obj, /* {{{ */ by_type_entry_t *by_type) { while (42) { char *plugin = NULL; user_class_list_t *user_class_list = NULL; int status; status = c_avl_pick(by_type->by_plugin_tree, (void *)&plugin, (void *)&user_class_list); if (status != 0) break; DEBUG("utils_vl_lookup: lu_destroy_by_type: Destroying plugin \"%s\".", plugin); sfree(plugin); lu_destroy_user_class_list(obj, user_class_list); } c_avl_destroy(by_type->by_plugin_tree); by_type->by_plugin_tree = NULL; lu_destroy_user_class_list(obj, by_type->wildcard_plugin_list); by_type->wildcard_plugin_list = NULL; sfree(by_type); } /* }}} int lu_destroy_by_type */ /* * Public functions */ lookup_t *lookup_create(lookup_class_callback_t cb_user_class, /* {{{ */ lookup_obj_callback_t cb_user_obj, lookup_free_class_callback_t cb_free_class, lookup_free_obj_callback_t cb_free_obj) { lookup_t *obj = calloc(1, sizeof(*obj)); if (obj == NULL) { ERROR("utils_vl_lookup: calloc failed."); return NULL; } obj->by_type_tree = c_avl_create((int (*)(const void *, const void *))strcmp); if (obj->by_type_tree == NULL) { ERROR("utils_vl_lookup: c_avl_create failed."); sfree(obj); return NULL; } obj->cb_user_class = cb_user_class; obj->cb_user_obj = cb_user_obj; obj->cb_free_class = cb_free_class; obj->cb_free_obj = cb_free_obj; return obj; } /* }}} lookup_t *lookup_create */ void lookup_destroy(lookup_t *obj) /* {{{ */ { int status; if (obj == NULL) return; while (42) { char *type = NULL; by_type_entry_t *by_type = NULL; status = c_avl_pick(obj->by_type_tree, (void *)&type, (void *)&by_type); if (status != 0) break; DEBUG("utils_vl_lookup: lookup_destroy: Destroying type \"%s\".", type); sfree(type); lu_destroy_by_type(obj, by_type); } c_avl_destroy(obj->by_type_tree); obj->by_type_tree = NULL; sfree(obj); } /* }}} void lookup_destroy */ int lookup_add(lookup_t *obj, /* {{{ */ lookup_identifier_t const *ident, unsigned int group_by, void *user_class) { by_type_entry_t *by_type = NULL; user_class_list_t *user_class_obj; by_type = lu_search_by_type(obj, ident->type, /* allocate = */ true); if (by_type == NULL) return -1; user_class_obj = calloc(1, sizeof(*user_class_obj)); if (user_class_obj == NULL) { ERROR("utils_vl_lookup: calloc failed."); return ENOMEM; } pthread_mutex_init(&user_class_obj->entry.lock, /* attr = */ NULL); user_class_obj->entry.user_class = user_class; lu_copy_ident_to_match(&user_class_obj->entry.match, ident, group_by); user_class_obj->entry.user_obj_list = NULL; user_class_obj->next = NULL; return lu_add_by_plugin(by_type, user_class_obj); } /* }}} int lookup_add */ /* returns the number of successful calls to the callback function */ int lookup_search(lookup_t *obj, /* {{{ */ data_set_t const *ds, value_list_t const *vl) { by_type_entry_t *by_type = NULL; user_class_list_t *user_class_list = NULL; int retval = 0; int status; if ((obj == NULL) || (ds == NULL) || (vl == NULL)) return -EINVAL; by_type = lu_search_by_type(obj, vl->type, /* allocate = */ false); if (by_type == NULL) return 0; status = c_avl_get(by_type->by_plugin_tree, vl->plugin, (void *)&user_class_list); if (status == 0) { status = lu_handle_user_class_list(obj, ds, vl, user_class_list); if (status < 0) return status; retval += status; } if (by_type->wildcard_plugin_list != NULL) { status = lu_handle_user_class_list(obj, ds, vl, by_type->wildcard_plugin_list); if (status < 0) return status; retval += status; } return retval; } /* }}} lookup_search */