#include <pthread.h>
+#define AGG_MATCHES_ALL(str) (strcmp ("/.*/", str) == 0)
+
struct aggregation_s /* {{{ */
{
identifier_t ident;
+ unsigned int group_by;
_Bool calc_num;
_Bool calc_sum;
inst->ds_type = ds->ds[0].type;
-#define COPY_FIELD(fld) do { \
- sstrncpy (inst->ident.fld, \
- LU_IS_ANY (agg->ident.fld) ? vl->fld : agg->ident.fld, \
- sizeof (inst->ident.fld)); \
+#define COPY_FIELD(field, group_mask) do { \
+ sstrncpy (inst->ident.field, \
+ (agg->group_by & group_mask) ? vl->field : agg->ident.field, \
+ sizeof (inst->ident.field)); \
} while (0)
- COPY_FIELD (host);
- COPY_FIELD (plugin);
- COPY_FIELD (plugin_instance);
- COPY_FIELD (type);
- COPY_FIELD (type_instance);
+ 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, /* group_mask = */ 0);
+ COPY_FIELD (type_instance, LU_GROUP_BY_TYPE_INSTANCE);
#undef COPY_FIELD
}
meta_data_add_boolean (vl.meta, "aggregation:created", 1);
- if (LU_IS_ALL (inst->ident.host))
+ if (AGG_MATCHES_ALL (inst->ident.host))
sstrncpy (vl.host, "global", sizeof (vl.host));
else
sstrncpy (vl.host, inst->ident.host, sizeof (vl.host));
sstrncpy (vl.plugin, "aggregation", sizeof (vl.plugin));
- if (LU_IS_ALL (inst->ident.plugin))
+ if (AGG_MATCHES_ALL (inst->ident.plugin))
{
- if (LU_IS_ALL (inst->ident.plugin_instance))
+ if (AGG_MATCHES_ALL (inst->ident.plugin_instance))
sstrncpy (pi_prefix, "", sizeof (pi_prefix));
else
sstrncpy (pi_prefix, inst->ident.plugin_instance, sizeof (pi_prefix));
}
else
{
- if (LU_IS_ALL (inst->ident.plugin_instance))
+ if (AGG_MATCHES_ALL (inst->ident.plugin_instance))
sstrncpy (pi_prefix, inst->ident.plugin, sizeof (pi_prefix));
else
ssnprintf (pi_prefix, sizeof (pi_prefix),
sstrncpy (vl.type, inst->ident.type, sizeof (vl.type));
- if (!LU_IS_ALL (inst->ident.type_instance))
+ if (!AGG_MATCHES_ALL (inst->ident.type_instance))
sstrncpy (vl.type_instance, inst->ident.type_instance,
sizeof (vl.type_instance));
value = ci->values[i].value.string;
if (strcasecmp ("Host", value) == 0)
- sstrncpy (agg->ident.host, LU_ANY, sizeof (agg->ident.host));
+ agg->group_by |= LU_GROUP_BY_HOST;
else if (strcasecmp ("Plugin", value) == 0)
- sstrncpy (agg->ident.plugin, LU_ANY, sizeof (agg->ident.plugin));
+ agg->group_by |= LU_GROUP_BY_PLUGIN;
else if (strcasecmp ("PluginInstance", value) == 0)
- sstrncpy (agg->ident.plugin_instance, LU_ANY,
- sizeof (agg->ident.plugin_instance));
+ agg->group_by |= LU_GROUP_BY_PLUGIN_INSTANCE;
else if (strcasecmp ("TypeInstance", value) == 0)
- sstrncpy (agg->ident.type_instance, LU_ANY, sizeof (agg->ident.type_instance));
+ agg->group_by |= LU_GROUP_BY_TYPE_INSTANCE;
else if (strcasecmp ("Type", value) == 0)
ERROR ("aggregation plugin: Grouping by type is not supported.");
else
}
memset (agg, 0, sizeof (*agg));
- sstrncpy (agg->ident.host, LU_ALL, sizeof (agg->ident.host));
- sstrncpy (agg->ident.plugin, LU_ALL, sizeof (agg->ident.plugin));
- sstrncpy (agg->ident.plugin_instance, LU_ALL,
+ sstrncpy (agg->ident.host, "/.*/", sizeof (agg->ident.host));
+ sstrncpy (agg->ident.plugin, "/.*/", sizeof (agg->ident.plugin));
+ sstrncpy (agg->ident.plugin_instance, "/.*/",
sizeof (agg->ident.plugin_instance));
- sstrncpy (agg->ident.type, LU_ALL, sizeof (agg->ident.type));
- sstrncpy (agg->ident.type_instance, LU_ALL,
+ sstrncpy (agg->ident.type, "/.*/", sizeof (agg->ident.type));
+ sstrncpy (agg->ident.type_instance, "/.*/",
sizeof (agg->ident.type_instance));
for (i = 0; i < ci->children_num; i++)
/* Sanity checking */
is_valid = 1;
- if (LU_IS_ALL (agg->ident.type)) /* {{{ */
+ if (strcmp ("/.*/", agg->ident.type) == 0) /* {{{ */
{
ERROR ("aggregation plugin: It appears you did not specify the required "
"\"Type\" option in this aggregation. "
else if (strchr (agg->ident.type, '/') != NULL)
{
ERROR ("aggregation plugin: The \"Type\" may not contain the '/' "
- "character. Especially, it may not be a wildcard. The current "
+ "character. Especially, it may not be a regex. The current "
"value is \"%s\".", agg->ident.type);
is_valid = 0;
} /* }}} */
- if (!LU_IS_ALL (agg->ident.host) /* {{{ */
- && !LU_IS_ALL (agg->ident.plugin)
- && !LU_IS_ALL (agg->ident.plugin_instance)
- && !LU_IS_ALL (agg->ident.type_instance))
+ if (!AGG_MATCHES_ALL (agg->ident.host) /* {{{ */
+ && !AGG_MATCHES_ALL (agg->ident.plugin)
+ && !AGG_MATCHES_ALL (agg->ident.plugin_instance)
+ && !AGG_MATCHES_ALL (agg->ident.type_instance))
{
ERROR ("aggregation plugin: An aggregation must contain at least one "
"wildcard. This is achieved by leaving at least one of the \"Host\", "
return (-1);
} /* }}} */
- status = lookup_add (lookup, &agg->ident, agg);
+ status = lookup_add (lookup, &agg->ident, agg->group_by, agg);
if (status != 0)
{
ERROR ("aggregation plugin: lookup_add failed with status %i.", status);
Selects the value lists to be added to this aggregation. B<Type> must be a
valid data set name, see L<types.db(5)> for details.
+If the string starts with and ends with a slash (C</>), the string is
+interpreted as a I<regular expression>. The regex flavor used are POSIX
+extended regular expressions as described in L<regex(7)>. Example usage:
+
+ Host "/^db[0-9]\\.example\\.com$/"
+
=item B<GroupBy> B<Host>|B<Plugin>|B<PluginInstance>|B<TypeInstance>
Group valued by the specified field. The B<GroupBy> option may be repeated to
**/
#include "collectd.h"
+
+#include <regex.h>
+
#include "common.h"
#include "utils_vl_lookup.h"
#include "utils_avltree.h"
/*
* 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;
struct user_class_s
{
void *user_class;
- identifier_t ident;
+ identifier_match_t match;
user_obj_t *user_obj_list; /* list of user_obj */
};
typedef struct user_class_s user_class_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 (1);
+
+ int status = regexec (&match->regex, str,
+ /* nmatch = */ 0, /* pmatch = */ NULL,
+ /* flags = */ 0);
+ if (status == 0)
+ return (1);
+ else
+ return (0);
+ }
+ else if (strcmp (match->str, str) == 0)
+ return (1);
+ else
+ return (0);
+} /* }}} _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 = 0;
+ 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 = 1;
+
+ return (0);
+} /* }}} int lu_copy_ident_to_match_part */
+
+static int lu_copy_ident_to_match (identifier_match_t *match, /* {{{ */
+ 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 */
+
static void *lu_create_user_obj (lookup_t *obj, /* {{{ */
data_set_t const *ds, value_list_t const *vl,
user_class_t *user_class)
return (NULL);
}
- sstrncpy (user_obj->ident.host,
- LU_IS_ALL (user_class->ident.host) ? "/all/" : vl->host,
- sizeof (user_obj->ident.host));
- sstrncpy (user_obj->ident.plugin,
- LU_IS_ALL (user_class->ident.plugin) ? "/all/" : vl->plugin,
- sizeof (user_obj->ident.plugin));
- sstrncpy (user_obj->ident.plugin_instance,
- LU_IS_ALL (user_class->ident.plugin_instance) ? "/all/" : vl->plugin_instance,
- sizeof (user_obj->ident.plugin_instance));
- sstrncpy (user_obj->ident.type,
- LU_IS_ALL (user_class->ident.type) ? "/all/" : vl->type,
- sizeof (user_obj->ident.type));
- sstrncpy (user_obj->ident.type_instance,
- LU_IS_ALL (user_class->ident.type_instance) ? "/all/" : vl->type_instance,
- sizeof (user_obj->ident.type_instance));
+#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)
{
ptr != NULL;
ptr = ptr->next)
{
- if (!LU_IS_ALL (ptr->ident.host)
- && (strcmp (ptr->ident.host, vl->host) != 0))
+ 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 (!LU_IS_ALL (ptr->ident.plugin_instance)
- && (strcmp (ptr->ident.plugin_instance, vl->plugin_instance) != 0))
+ 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 (!LU_IS_ALL (ptr->ident.type_instance)
- && (strcmp (ptr->ident.type_instance, vl->type_instance) != 0))
+ 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);
user_obj_t *user_obj;
int status;
- assert (strcmp (vl->type, user_class->ident.type) == 0);
- assert (LU_IS_WILDCARD (user_class->ident.plugin)
- || (strcmp (vl->plugin, user_class->ident.plugin) == 0));
+ 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);
- /* When we get here, type and plugin already match the user class. Now check
- * the rest of the fields. */
- if (!LU_IS_WILDCARD (user_class->ident.type_instance)
- && (strcmp (vl->type_instance, user_class->ident.type_instance) != 0))
- return (1);
- if (!LU_IS_WILDCARD (user_class->ident.plugin_instance)
- && (strcmp (vl->plugin_instance,
- user_class->ident.plugin_instance) != 0))
- return (1);
- if (!LU_IS_WILDCARD (user_class->ident.host)
- && (strcmp (vl->host, user_class->ident.host) != 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);
user_obj = lu_find_user_obj (user_class, vl);
} /* }}} by_type_entry_t *lu_search_by_type */
static int lu_add_by_plugin (by_type_entry_t *by_type, /* {{{ */
- identifier_t const *ident, user_class_list_t *user_class_list)
+ 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 blocks return immediately. Otherwise they will
* set "ptr" to non-NULL. */
- if (LU_IS_WILDCARD (ident->plugin))
+ if (match->plugin.is_regex)
{
if (by_type->wildcard_plugin_list == NULL)
{
int status;
status = c_avl_get (by_type->by_plugin_tree,
- ident->plugin, (void *) &ptr);
+ match->plugin.str, (void *) &ptr);
if (status != 0) /* plugin not yet in tree */
{
- char *plugin_copy = strdup (ident->plugin);
+ char *plugin_copy = strdup (match->plugin.str);
if (plugin_copy == NULL)
{
} /* }}} void lookup_destroy */
int lookup_add (lookup_t *obj, /* {{{ */
- identifier_t const *ident, void *user_class)
+ 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;
}
memset (user_class_obj, 0, sizeof (*user_class_obj));
user_class_obj->entry.user_class = user_class;
- memmove (&user_class_obj->entry.ident, ident, sizeof (*ident));
+ 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, ident, user_class_obj));
+ return (lu_add_by_plugin (by_type, user_class_obj));
} /* }}} int lookup_add */
/* returns the number of successful calls to the callback function */
};
typedef struct identifier_s identifier_t;
-#define LU_ANY "/any/"
-#define LU_ALL "/all/"
-
-#define LU_IS_ANY(str) (strcmp (str, LU_ANY) == 0)
-#define LU_IS_ALL(str) (strcmp (str, LU_ALL) == 0)
-#define LU_IS_WILDCARD(str) (LU_IS_ANY(str) || LU_IS_ALL(str))
+#define LU_GROUP_BY_HOST 0x01
+#define LU_GROUP_BY_PLUGIN 0x02
+#define LU_GROUP_BY_PLUGIN_INSTANCE 0x04
+/* #define LU_GROUP_BY_TYPE 0x00 */
+#define LU_GROUP_BY_TYPE_INSTANCE 0x10
/*
* Functions
void lookup_destroy (lookup_t *obj);
int lookup_add (lookup_t *obj,
- identifier_t const *ident, void *user_class);
+ identifier_t const *ident, unsigned int group_by, void *user_class);
/* TODO(octo): Pass lookup_obj_callback_t to lookup_search()? */
int lookup_search (lookup_t *obj,
static void checked_lookup_add (lookup_t *obj, /* {{{ */
char const *host,
char const *plugin, char const *plugin_instance,
- char const *type, char const *type_instance)
+ char const *type, char const *type_instance,
+ unsigned int group_by)
{
identifier_t ident;
void *user_class;
user_class = malloc (sizeof (ident));
memmove (user_class, &ident, sizeof (ident));
- status = lookup_add (obj, &ident, user_class);
+ status = lookup_add (obj, &ident, group_by, user_class);
assert (status == 0);
} /* }}} void test_add */
{
lookup_t *obj = checked_lookup_create ();
- checked_lookup_add (obj, "/any/", "test", "", "test", "/all/");
+ checked_lookup_add (obj, "/.*/", "test", "", "test", "/.*/", LU_GROUP_BY_HOST);
checked_lookup_search (obj, "host0", "test", "", "test", "0",
/* expect new = */ 1);
checked_lookup_search (obj, "host0", "test", "", "test", "1",
{
lookup_t *obj = checked_lookup_create ();
- checked_lookup_add (obj, "/any/", "/all/", "/all/", "test", "/all/");
+ checked_lookup_add (obj, "/.*/", "/.*/", "/.*/", "test", "/.*/", LU_GROUP_BY_HOST);
checked_lookup_search (obj, "host0", "plugin0", "", "test", "0",
/* expect new = */ 1);
checked_lookup_search (obj, "host0", "plugin0", "", "test", "1",
lookup_t *obj = checked_lookup_create ();
int status;
- checked_lookup_add (obj, "/any/", "plugin0", "", "test", "/all/");
- checked_lookup_add (obj, "/any/", "/all/", "", "test", "ti0");
+ checked_lookup_add (obj, "/.*/", "plugin0", "", "test", "/.*/", LU_GROUP_BY_HOST);
+ checked_lookup_add (obj, "/.*/", "/.*/", "", "test", "ti0", LU_GROUP_BY_HOST);
status = checked_lookup_search (obj, "host0", "plugin1", "", "test", "",
/* expect new = */ 0);
lookup_destroy (obj);
}
+static void testcase3 (void)
+{
+ lookup_t *obj = checked_lookup_create ();
+
+ checked_lookup_add (obj, "/^db[0-9]\\./", "cpu", "/.*/", "cpu", "/.*/",
+ LU_GROUP_BY_TYPE_INSTANCE);
+ checked_lookup_search (obj, "db0.example.com", "cpu", "0", "cpu", "user",
+ /* expect new = */ 1);
+ checked_lookup_search (obj, "db0.example.com", "cpu", "0", "cpu", "idle",
+ /* expect new = */ 1);
+ checked_lookup_search (obj, "db0.example.com", "cpu", "1", "cpu", "user",
+ /* expect new = */ 0);
+ checked_lookup_search (obj, "db0.example.com", "cpu", "1", "cpu", "idle",
+ /* expect new = */ 0);
+ checked_lookup_search (obj, "app0.example.com", "cpu", "0", "cpu", "user",
+ /* expect new = */ 0);
+ checked_lookup_search (obj, "app0.example.com", "cpu", "0", "cpu", "idle",
+ /* expect new = */ 0);
+ checked_lookup_search (obj, "db1.example.com", "cpu", "0", "cpu", "user",
+ /* expect new = */ 0);
+ checked_lookup_search (obj, "db1.example.com", "cpu", "0", "cpu", "idle",
+ /* expect new = */ 0);
+ checked_lookup_search (obj, "db1.example.com", "cpu", "0", "cpu", "system",
+ /* expect new = */ 1);
+
+ lookup_destroy (obj);
+}
+
int main (int argc, char **argv) /* {{{ */
{
testcase0 ();
testcase1 ();
testcase2 ();
+ testcase3 ();
return (EXIT_SUCCESS);
} /* }}} int main */