From: Florian Forster Date: Fri, 21 Nov 2008 21:09:53 +0000 (+0100) Subject: src/filter_chain.[ch]: Implement an advanced filtering framework. X-Git-Tag: collectd-4.6.0~156^2~8 X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=55a46b483726014b91bc7e741d95a84a611b28c2;p=collectd.git src/filter_chain.[ch]: Implement an advanced filtering framework. The concept separates `filter' plugins into `match' and `target' plugins. Those two parts can be combined in wild variation, allowing *very* flexible mechanisms. The concept is explained in more detail in the `FILTER CONFIGURATION' section of collectd.conf(5), also added with this commit. --- diff --git a/src/Makefile.am b/src/Makefile.am index b6d1a085..960dfe4d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -29,6 +29,7 @@ bin_PROGRAMS = collectd-nagios collectd_SOURCES = collectd.c collectd.h \ common.c common.h \ configfile.c configfile.h \ + filter_chain.c filter_chain.h \ plugin.c plugin.h \ utils_avltree.c utils_avltree.h \ utils_cache.c utils_cache.h \ diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 9889b616..9ce2ae92 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -2451,6 +2451,251 @@ only one such notification is generated until the value appears again. =back +=head1 FILTER CONFIGURATION + +TODO: Update this entire section once development is done. + +Starting with collectd 4.6 there is a powerful filtering infrastructure +implemented in the daemon. The concept has mostly been copied from I, +the packet filter infrastructure for Linux. We'll use a similar terminology, so +that users that are familiar with iptables feel right at home. + +=head2 Terminology + +The most important terms are: + +=over 4 + +=item B + +A I is a criteria to select specific values. Examples are, of course, the +name of the value or it's current value. + +=item B + +A I is some action that is to be performed with data. Such actions +could, for example, be to change part of the value's identifier or to ignore +the value completely. Built-in functions are B and B, see below. + +Some targets, for example the built-in B target, signal that processing +of a value should be stopped. In that case processing of the current chain will +be aborted. + +=item B + +The combination of any number of matches and at least one target is called a +I. The target actions will be performed for all values for which B +matches apply. If the rule does not have any matches associated with it, the +target action will be performed for all values. + +If any target returns the stop condition, the processing will stop right away. +This means that any targets following the current one will not be called after +the stop condition has been returned. + +=item B + +A I is a list of rules and possibly default targets. The rules are tried +in order and if one matches, the associated target will be called. If a value +is handled by a rule, it depends on the target whether or not any subsequent +rules are considered or if traversal of the chain is aborted. After all rules +have been checked and no target returned the stop condition, the default +targets will be executed. + +=back + +=head2 General structure + +The following shows the resulting structure: + + +---------+ + ! Chain ! + +---------+ + ! + V + +---------+ +---------+ +---------+ +---------+ + ! Rule !->! Match !->! Match !->! Target ! + +---------+ +---------+ +---------+ +---------+ + ! + V + +---------+ +---------+ +---------+ + ! Rule !->! Target !->! Target ! + +---------+ +---------+ +---------+ + ! + V + : + : + ! + V + +---------+ +---------+ +---------+ + ! Rule !->! Match !->! Target ! + +---------+ +---------+ +---------+ + ! + V + +---------+ + ! Default ! + ! Target ! + +---------+ + +=head2 Synopsis + +The configuration reflects this structure directly: + + + + + Plugin "^mysql$" + Type "^mysql_command$" + TypeInstance "^show_" + + + + + + Plugin "rrdtool" + + + +The above configuration example will ignore all values where the plugin field +is "mysql", the type is "mysql_command" and the type instance begins with +"show_". All other values will be sent to the "rrdtool" write plugin via the +default target of the chain. + +=head2 List of configuration options + +=over 4 + +=item B I + +Adds a new chain with a certain name. This name can be used to refer to a +specific chain, for example to jump to it. + +Within the B block, there can be B blocks and B blocks. + +=item B [I] + +Adds a new rule to the current chain. The name of the rule is optional and +currently has no meaning for the daemon. + +Within the B block, there may be any number of B blocks and there +must be at least one B block. + +=item B I + +Adds a match to a B block. The name specifies what kind of match should +be performed. Available matches depend on the plugins that have been loaded. + +The arguments inside the B block are passed to the plugin implementing +the match, so which arguments are valid here depends on the plugin being used. +If you do not need any to pass any arguments to a match, you can use the +shorter syntax: + + Match "foobar" + +Which is equivalent to: + + + + +=item B I + +Add a target to a rule or a default target to a chain. The name specifies what +kind of target is to be added. Which targets are available depends on the +plugins being loaded. + +The arguments inside the B block are passed to the plugin implementing +the target, so which arguments are valid here depends on the plugin being used. +If you do not need any to pass any arguments to a target, you can use the +shorter syntax: + + Target "stop" + +This is the same as writing: + + + + +=back + +=head2 Built-in targets + +The following targets are built into the core daemon and therefore need no +plugins to be loaded: + +=over 4 + +=item B + +Does nothing except returning with the stop condition, causing processing of +the current chain to be aborted. + +=item B + +Sends the value to write plugins. + +Available options: + +=over 4 + +=item B I + +Name of the write plugin to which the data should be sent. This option may be +given multiple times to send the data to more than one write plugin. + +Example: + + Target "stop" + +=back + +If no plugin is explicitly specified, the values will be sent to all available +write plugins. + +Example: + + + Plugin "rrdtool" + + +=item B + +Starts processing the rules of another chain. If the end of that chain is +reached, or a stop condition is encountered, processing will continue right +after the B target, i.Ee. with the next target or the next rule. + +Available options: + +=over 4 + +=item B I + +Jumps to the chain I. This argument is required and may appear only once. + +=back + +Example: + + + Chain "foobar" + + +=back + +=head2 Backwards compatibility + +If you use collectd with an old configuration, i.Ee. one without a +B block, it will behave as it used to. This is equivalent to the +following configuration: + + + Target "write" + + +If you specify a B block anywhere, the B target will not be added +anywhere and you will have to make sure that it is called where appropriate. We +suggest to add the above snippet as default target to your main chain. + +TODO: Notifications will be implemented using chains, too. Describe that here! + =head1 SEE ALSO L, diff --git a/src/filter_chain.c b/src/filter_chain.c new file mode 100644 index 00000000..9a15661f --- /dev/null +++ b/src/filter_chain.c @@ -0,0 +1,958 @@ +/** + * collectd - src/filter_chain.h + * Copyright (C) 2008 Florian octo Forster + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Florian octo Forster + **/ + +/* + * First tell the compiler to stick to the C99 and POSIX standards as close as + * possible. + */ +#ifndef __STRICT_ANSI__ /* {{{ */ +# define __STRICT_ANSI__ +#endif + +#ifndef _ISOC99_SOURCE +# define _ISOC99_SOURCE +#endif + +#ifdef _POSIX_C_SOURCE +# undef _POSIX_C_SOURCE +#endif +#define _POSIX_C_SOURCE 200112L + +#if 0 +/* Single UNIX needed for strdup. */ +#ifdef _XOPEN_SOURCE +# undef _XOPEN_SOURCE +#endif +#define _XOPEN_SOURCE 500 +#endif + +#ifndef _REENTRANT +# define _REENTRANT +#endif + +#ifndef _THREAD_SAFE +# define _THREAD_SAFE +#endif + +#ifdef _GNU_SOURCE +# undef _GNU_SOURCE +#endif +/* }}} */ + +#include "collectd.h" +#include "configfile.h" +#include "plugin.h" +#include "common.h" +#include "filter_chain.h" + +/* + * Data types + */ +/* List of matches, used in fc_rule_t and for the global `match_list_head' + * variable. */ +struct fc_match_s; +typedef struct fc_match_s fc_match_t; /* {{{ */ +struct fc_match_s +{ + char name[DATA_MAX_NAME_LEN]; + match_proc_t proc; + void *user_data; + fc_match_t *next; +}; /* }}} */ + +/* List of targets, used in fc_rule_t and for the global `target_list_head' + * variable. */ +struct fc_target_s; +typedef struct fc_target_s fc_target_t; /* {{{ */ +struct fc_target_s +{ + char name[DATA_MAX_NAME_LEN]; + void *user_data; + target_proc_t proc; + fc_target_t *next; +}; /* }}} */ + +/* List of rules, used in fc_chain_t */ +struct fc_fule_s; +typedef struct fc_fule_s fc_rule_t; /* {{{ */ +struct fc_fule_s +{ + char name[DATA_MAX_NAME_LEN]; + fc_match_t *matches; + fc_target_t *targets; + fc_rule_t *next; +}; /* }}} */ + +/* List of chains, used for `chain_list_head' */ +struct fc_chain_s; +typedef struct fc_chain_s fc_chain_t; /* {{{ */ +struct fc_chain_s +{ + char name[DATA_MAX_NAME_LEN]; + fc_rule_t *rules; + fc_target_t *targets; + fc_chain_t *next; +}; /* }}} */ + +/* + * Global variables + */ +static fc_match_t *match_list_head; +static fc_target_t *target_list_head; +static fc_chain_t *chain_list_head; + +/* + * Private functions + */ +static void fc_free_matches (fc_match_t *m) /* {{{ */ +{ + if (m == NULL) + return; + + if (m->proc.destroy != NULL) + (*m->proc.destroy) (&m->user_data); + else if (m->user_data != NULL) + { + ERROR ("Filter sybsystem: fc_free_matches: There is user data, but no " + "destroy functions has been specified. " + "Memory will probably be lost!"); + } + + if (m->next != NULL) + fc_free_matches (m->next); + + free (m); +} /* }}} void fc_free_matches */ + +static void fc_free_targets (fc_target_t *t) /* {{{ */ +{ + if (t == NULL) + return; + + if (t->proc.destroy != NULL) + (*t->proc.destroy) (&t->user_data); + else if (t->user_data != NULL) + { + ERROR ("Filter sybsystem: fc_free_targets: There is user data, but no " + "destroy functions has been specified. " + "Memory will probably be lost!"); + } + + if (t->next != NULL) + fc_free_targets (t->next); + + free (t); +} /* }}} void fc_free_targets */ + +static void fc_free_rules (fc_rule_t *r) /* {{{ */ +{ + if (r == NULL) + return; + + fc_free_matches (r->matches); + fc_free_targets (r->targets); + + if (r->next != NULL) + fc_free_rules (r->next); + + free (r); +} /* }}} void fc_free_rules */ + +static void fc_free_chains (fc_chain_t *c) /* {{{ */ +{ + if (c == NULL) + return; + + fc_free_rules (c->rules); + fc_free_targets (c->targets); + + if (c->next != NULL) + fc_free_chains (c->next); + + free (c); +} /* }}} void fc_free_chains */ + +static char *fc_strdup (const char *orig) /* {{{ */ +{ + size_t sz; + char *dest; + + if (orig == NULL) + return (NULL); + + sz = strlen (orig) + 1; + dest = (char *) malloc (sz); + if (dest == NULL) + return (NULL); + + memcpy (dest, orig, sz); + + return (dest); +} /* }}} char *fc_strdup */ + +/* + * Configuration. + * + * The configuration looks somewhat like this: + * + * + * + * + * Plugin "^mysql$" + * Type "^mysql_command$" + * TypeInstance "^show_" + * + * + * + * + * + * + * Plugin "rrdtool" + * + * + */ +static int fc_config_add_match (fc_match_t **matches_head, /* {{{ */ + oconfig_item_t *ci) +{ + fc_match_t *m; + fc_match_t *ptr; + int status; + + if ((ci->values_num != 1) + || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + WARNING ("Filter subsystem: `Match' blocks require " + "exactly one string argument."); + return (-1); + } + + ptr = match_list_head; + while (ptr != NULL) + { + if (strcasecmp (ptr->name, ci->values[0].value.string) == 0) + break; + ptr = ptr->next; + } + + if (ptr == NULL) + { + WARNING ("Filter subsystem: Cannot find a \"%s\" match. " + "Did you load the appropriate plugin?", + ci->values[0].value.string); + return (-1); + } + + m = (fc_match_t *) malloc (sizeof (*m)); + if (m == NULL) + { + ERROR ("fc_config_add_match: malloc failed."); + return (-1); + } + memset (m, 0, sizeof (*m)); + + sstrncpy (m->name, ptr->name, sizeof (m->name)); + memcpy (&m->proc, &ptr->proc, sizeof (m->proc)); + assert (m->proc.create != NULL); + m->user_data = NULL; + m->next = NULL; + + status = (*m->proc.create) (ci, &m->user_data); + if (status != 0) + { + WARNING ("Filter subsystem: Failed to create a %s match.", + m->name); + fc_free_matches (m); + return (-1); + } + + if (*matches_head != NULL) + { + ptr = *matches_head; + while (ptr->next != NULL) + ptr = ptr->next; + + ptr->next = m; + } + else + { + *matches_head = m; + } + + return (0); +} /* }}} int fc_config_add_match */ + +static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */ + oconfig_item_t *ci) +{ + fc_target_t *t; + fc_target_t *ptr; + int status; + + if ((ci->values_num != 1) + || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + WARNING ("Filter subsystem: `Target' blocks require " + "exactly one string argument."); + return (-1); + } + + ptr = target_list_head; + while (ptr != NULL) + { + if (strcasecmp (ptr->name, ci->values[0].value.string) == 0) + break; + ptr = ptr->next; + } + + if (ptr == NULL) + { + WARNING ("Filter subsystem: Cannot find a \"%s\" target. " + "Did you load the appropriate plugin?", + ci->values[0].value.string); + return (-1); + } + + t = (fc_target_t *) malloc (sizeof (*t)); + if (t == NULL) + { + ERROR ("fc_config_add_match: malloc failed."); + return (-1); + } + memset (t, 0, sizeof (*t)); + + sstrncpy (t->name, ptr->name, sizeof (t->name)); + memcpy (&t->proc, &ptr->proc, sizeof (t->proc)); + assert (t->proc.create != NULL); + t->user_data = NULL; + t->next = NULL; + + status = (*t->proc.create) (ci, &t->user_data); + if (status != 0) + { + WARNING ("Filter subsystem: Failed to create a %s match.", + t->name); + fc_free_targets (t); + return (-1); + } + + if (*targets_head != NULL) + { + ptr = *targets_head; + while (ptr->next != NULL) + ptr = ptr->next; + + ptr->next = t; + } + else + { + *targets_head = t; + } + + return (0); +} /* }}} int fc_config_add_target */ + +static int fc_config_add_rule (fc_chain_t *chain, /* {{{ */ + oconfig_item_t *ci) +{ + fc_rule_t *rule; + char rule_name[2*DATA_MAX_NAME_LEN] = "Unnamed rule"; + int status = 0; + int i; + + if (ci->values_num > 1) + { + WARNING ("Filter subsystem: `Rule' blocks have at most one argument."); + return (-1); + } + else if ((ci->values_num == 1) + && (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + WARNING ("Filter subsystem: `Rule' blocks expect one string argument " + "or no argument at all."); + return (-1); + } + + rule = (fc_rule_t *) malloc (sizeof (*rule)); + if (rule == NULL) + { + ERROR ("fc_config_add_rule: malloc failed."); + return (-1); + } + memset (rule, 0, sizeof (*rule)); + rule->next = NULL; + + if (ci->values_num == 1) + { + sstrncpy (rule->name, ci->values[0].value.string, sizeof (rule->name)); + ssnprintf (rule_name, sizeof (rule_name), "Rule \"%s\"", + ci->values[0].value.string); + } + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *option = ci->children + i; + status = 0; + + if (strcasecmp ("Match", option->key) == 0) + status = fc_config_add_match (&rule->matches, option); + else if (strcasecmp ("Target", option->key) == 0) + status = fc_config_add_target (&rule->targets, option); + else + { + WARNING ("Filter subsystem: %s: Option `%s' not allowed " + "inside a block.", rule_name, option->key); + status = -1; + } + + if (status != 0) + break; + } /* for (ci->children) */ + + /* Additional sanity checking. */ + while (status == 0) + { + if (rule->targets == NULL) + { + WARNING ("Filter subsystem: %s: No target has been specified.", + rule_name); + status = -1; + break; + } + + break; + } /* while (status == 0) */ + + if (status != 0) + { + fc_free_rules (rule); + return (-1); + } + + if (chain->rules != NULL) + { + fc_rule_t *ptr; + + ptr = chain->rules; + while (ptr->next != NULL) + ptr = ptr->next; + + ptr->next = rule; + } + else + { + chain->rules = rule; + } + + return (0); +} /* }}} int fc_config_add_rule */ + +static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */ +{ + fc_chain_t *chain; + int status = 0; + int i; + + if ((ci->values_num != 1) + || (ci->values[0].type != OCONFIG_TYPE_STRING)) + { + WARNING ("Filter subsystem: blocks require exactly one " + "string argument."); + return (-1); + } + + chain = (fc_chain_t *) malloc (sizeof (*chain)); + if (chain == NULL) + { + ERROR ("fc_config_add_chain: malloc failed."); + return (-1); + } + memset (chain, 0, sizeof (*chain)); + sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name)); + chain->rules = NULL; + chain->targets = NULL; + chain->next = NULL; + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *option = ci->children + i; + status = 0; + + if (strcasecmp ("Rule", option->key) == 0) + status = fc_config_add_rule (chain, option); + else + { + WARNING ("Filter subsystem: Chain %s: Option `%s' not allowed " + "inside a block.", chain->name, option->key); + status = -1; + } + + if (status != 0) + break; + } /* for (ci->children) */ + + /* Additional sanity checking. */ + while (status == 0) + { + if (chain->targets == NULL) + { + WARNING ("Filter subsystem: Chain %s: No default target has been " + "specified. Please make sure that there is a block within " + "the block!", chain->name); + status = -1; + break; + } + + break; + } /* while (status == 0) */ + + if (status != 0) + { + fc_free_chains (chain); + return (-1); + } + + if (chain_list_head != NULL) + { + fc_chain_t *ptr; + + ptr = chain_list_head; + while (ptr->next != NULL) + ptr = ptr->next; + + ptr->next = chain; + } + else + { + chain_list_head = chain; + } + + return (0); +} /* }}} int fc_config_add_chain */ + +int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */ + fc_chain_t *chain) +{ + fc_rule_t *rule; + fc_target_t *target; + int status; + + if (chain == NULL) + return (-1); + + status = FC_ACTION_CONTINUE; + + for (rule = chain->rules; rule != NULL; rule = rule->next) + { + fc_match_t *match; + + /* N. B.: rule->matches may be NULL. */ + for (match = rule->matches; match != NULL; match = match->next) + { + status = (*match->proc.match) (ds, vl, /* meta = */ NULL, + &match->user_data); + if (status < 0) + { + WARNING ("fc_process: A match failed."); + break; + } + else if (status != FC_MATCH_MATCHES) + break; + } + + /* for-loop has been aborted: Either error or no match. */ + if (match != NULL) + continue; + + for (target = rule->targets; target != NULL; target = target->next) + { + /* If we get here, all matches have matched the value. Execute the target. */ + status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL, + &target->user_data); + if (status < 0) + { + WARNING ("fc_process: A target failed."); + continue; + } + else if (status == FC_ACTION_CONTINUE) + continue; + else if (status == FC_ACTION_STOP) + break; + else + { + WARNING ("fc_process: Unknown target return value: %i", status); + } + } + + if (status == FC_ACTION_STOP) + break; + } /* for (rule) */ + + /* for-loop has been aborted: A target returned `FC_ACTION_STOP' */ + if (rule != NULL) + return (0); + + for (target = chain->targets; target != NULL; target = target->next) + { + /* If we get here, all matches have matched the value. Execute the target. */ + status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL, + &target->user_data); + if (status < 0) + { + WARNING ("fc_process: The default target failed."); + } + } + + return (0); +} /* }}} int fc_process_chain */ + +/* + * Built-in target "jump" + * + * Prefix `bit' like `_b_uilt-_i_n _t_arget' + */ +static int fc_bit_jump_create (const oconfig_item_t *ci, /* {{{ */ + void **user_data) +{ + oconfig_item_t *ci_chain; + + if (ci->children_num != 1) + { + ERROR ("Filter subsystem: The built-in target `jump' needs exactly " + "one `Chain' argument!"); + return (-1); + } + + ci_chain = ci->children; + if (strcasecmp ("Chain", ci_chain->key) != 0) + { + ERROR ("Filter subsystem: The built-in target `jump' does not " + "support the configuration option `%s'.", + ci_chain->key); + return (-1); + } + + if ((ci_chain->values_num != 1) + || (ci_chain->values[0].type != OCONFIG_TYPE_STRING)) + { + ERROR ("Filter subsystem: Built-in target `jump': The `Chain' option " + "needs exactly one string argument."); + return (-1); + } + + *user_data = fc_strdup (ci_chain->values[0].value.string); + if (*user_data == NULL) + { + ERROR ("fc_bit_jump_create: fc_strdup failed."); + return (-1); + } + + return (0); +} /* }}} int fc_bit_jump_create */ + +static int fc_bit_jump_destroy (void **user_data) /* {{{ */ +{ + if (user_data != NULL) + { + free (*user_data); + *user_data = NULL; + } + + return (0); +} /* }}} int fc_bit_jump_destroy */ + +static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */ + value_list_t *vl, notification_meta_t **meta, void **user_data) +{ + char *chain_name; + fc_chain_t *chain; + int status; + + chain_name = *user_data; + + for (chain = chain_list_head; chain != NULL; chain = chain->next) + if (strcasecmp (chain_name, chain->name) == 0) + break; + + if (chain == NULL) + { + ERROR ("Filter subsystem: Built-in target `jump': There is no chain " + "named `%s'.", chain_name); + return (-1); + } + + status = fc_process_chain (ds, vl, chain); + if (status < 0) + return (status); + + return (FC_ACTION_CONTINUE); +} /* }}} int fc_bit_jump_invoke */ + +static int fc_bit_stop_invoke (const data_set_t *ds, /* {{{ */ + value_list_t *vl, notification_meta_t **meta, void **user_data) +{ + return (FC_ACTION_STOP); +} /* }}} int fc_bit_stop_invoke */ + +static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */ + void **user_data) +{ + int i; + + char **plugin_list; + size_t plugin_list_len; + + plugin_list = NULL; + plugin_list_len = 0; + + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *child = ci->children + i; + char **temp; + int j; + + if (strcasecmp ("Plugin", child->key) != 0) + { + ERROR ("Filter subsystem: The built-in target `write' does not " + "support the configuration option `%s'.", + child->key); + continue; + } + + for (j = 0; j < child->values_num; j++) + { + if (child->values[j].type != OCONFIG_TYPE_STRING) + { + ERROR ("Filter subsystem: Built-in target `write': " + "The `Plugin' option accepts only string arguments."); + continue; + } + + temp = (char **) realloc (plugin_list, (plugin_list_len + 2) + * (sizeof (*plugin_list))); + if (temp == NULL) + { + ERROR ("fc_bit_write_create: realloc failed."); + continue; + } + plugin_list = temp; + + plugin_list[plugin_list_len] = fc_strdup (child->values[j].value.string); + if (plugin_list[plugin_list_len] == NULL) + { + ERROR ("fc_bit_write_create: fc_strdup failed."); + continue; + } + plugin_list_len++; + plugin_list[plugin_list_len] = NULL; + } /* for (j = 0; j < child->values_num; j++) */ + } /* for (i = 0; i < ci->children_num; i++) */ + + *user_data = plugin_list; + + return (0); +} /* }}} int fc_bit_write_create */ + +static int fc_bit_write_destroy (void **user_data) /* {{{ */ +{ + char **plugin_list; + size_t i; + + if ((user_data == NULL) || (*user_data == NULL)) + return (0); + + plugin_list = *user_data; + + for (i = 0; plugin_list[i] != NULL; i++) + free (plugin_list[i]); + free (plugin_list); + + return (0); +} /* }}} int fc_bit_write_destroy */ + +static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */ + value_list_t *vl, notification_meta_t **meta, void **user_data) +{ + char **plugin_list; + int status; + + plugin_list = NULL; + if (user_data != NULL) + plugin_list = *user_data; + + if ((plugin_list == NULL) || (plugin_list[0] == NULL)) + { + status = plugin_write (/* plugin = */ NULL, ds, vl); + if (status != 0) + { + INFO ("Filter subsystem: Built-in target `write': Dispatching value to " + "all write plugins failed with status %i.", status); + } + } + else + { + size_t i; + + for (i = 0; plugin_list[i] != NULL; i++) + { + status = plugin_write (plugin_list[i], ds, vl); + if (status != 0) + { + INFO ("Filter subsystem: Built-in target `write': Dispatching value to " + "the `%s' plugin failed with status %i.", plugin_list[i], status); + } + } /* for (i = 0; plugin_list[i] != NULL; i++) */ + } + + return (FC_ACTION_CONTINUE); +} /* }}} int fc_bit_write_invoke */ + +static int fc_init_once (void) /* {{{ */ +{ + static int done = 0; + target_proc_t tproc; + + if (done != 0) + return (0); + + memset (&tproc, 0, sizeof (tproc)); + tproc.create = fc_bit_jump_create; + tproc.destroy = fc_bit_jump_destroy; + tproc.invoke = fc_bit_jump_invoke; + fc_register_target ("jump", tproc); + + memset (&tproc, 0, sizeof (tproc)); + tproc.create = NULL; + tproc.destroy = NULL; + tproc.invoke = fc_bit_stop_invoke; + fc_register_target ("stop", tproc); + + memset (&tproc, 0, sizeof (tproc)); + tproc.create = fc_bit_write_create; + tproc.destroy = fc_bit_write_destroy; + tproc.invoke = fc_bit_write_invoke; + fc_register_target ("write", tproc); + + done++; + return (0); +} /* }}} int fc_init_once */ + +/* + * Public functions + */ +/* Add a match to list of available matches. */ +int fc_register_match (const char *name, match_proc_t proc) /* {{{ */ +{ + fc_match_t *m; + + m = (fc_match_t *) malloc (sizeof (*m)); + if (m == NULL) + return (-ENOMEM); + memset (m, 0, sizeof (*m)); + + sstrncpy (m->name, name, sizeof (m->name)); + memcpy (&m->proc, &proc, sizeof (m->proc)); + m->next = NULL; + + if (match_list_head == NULL) + { + match_list_head = m; + } + else + { + fc_match_t *ptr; + + ptr = match_list_head; + while (ptr->next != NULL) + ptr = ptr->next; + + ptr->next = m; + } + + return (0); +} /* }}} int fc_register_match */ + +/* Add a target to list of available targets. */ +int fc_register_target (const char *name, target_proc_t proc) /* {{{ */ +{ + fc_target_t *t; + + t = (fc_target_t *) malloc (sizeof (*t)); + if (t == NULL) + return (-ENOMEM); + memset (t, 0, sizeof (*t)); + + sstrncpy (t->name, name, sizeof (t->name)); + memcpy (&t->proc, &proc, sizeof (t->proc)); + t->next = NULL; + + if (target_list_head == NULL) + { + target_list_head = t; + } + else + { + fc_target_t *ptr; + + ptr = target_list_head; + while (ptr->next != NULL) + ptr = ptr->next; + + ptr->next = t; + } + + return (0); +} /* }}} int fc_register_target */ + +/* Iterate over all rules in the chain and execute all targets for which all + * matches match. */ +int fc_process (const data_set_t *ds, value_list_t *vl) /* {{{ */ +{ + fc_chain_t *chain; + + for (chain = chain_list_head; chain != NULL; chain = chain->next) + if (strcasecmp ("Main", chain->name) == 0) + break; + + if (chain != NULL) + return (fc_process_chain (ds, vl, chain)); + + ERROR ("fc_process: TODO: Implement default behavior!"); + + return (0); +} /* }}} int fc_process */ + +int fc_configure (const oconfig_item_t *ci) /* {{{ */ +{ + fc_init_once (); + + if (ci == NULL) + return (-EINVAL); + + if (strcasecmp ("Chain", ci->key) == 0) + return (fc_config_add_chain (ci)); + + WARNING ("Filter subsystem: Unknown top level config option `%s'.", + ci->key); + + return (-1); +} /* }}} int fc_configure */ + +/* vim: set sw=2 sts=2 et fdm=marker : */ diff --git a/src/filter_chain.h b/src/filter_chain.h new file mode 100644 index 00000000..f2e22af2 --- /dev/null +++ b/src/filter_chain.h @@ -0,0 +1,92 @@ +/** + * collectd - src/filter_chain.h + * Copyright (C) 2008 Florian octo Forster + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: + * Florian octo Forster + **/ + +#ifndef FILTER_CHAIN_H +#define FILTER_CHAIN_H 1 + +#include "collectd.h" +#include "plugin.h" + +#define FC_MATCH_NO_MATCH 0 +#define FC_MATCH_MATCHES 1 + +#define FC_ACTION_CONTINUE 0 +#define FC_ACTION_STOP 1 + +/* + * Match functions + */ +struct match_proc_s +{ + int (*create) (const oconfig_item_t *ci, void **user_data); + int (*destroy) (void **user_data); + int (*match) (const data_set_t *ds, const value_list_t *vl, + notification_meta_t **meta, void **user_data); +}; +typedef struct match_proc_s match_proc_t; + +int fc_register_match (const char *name, match_proc_t proc); + +/* + * Target functions + */ +struct target_proc_s +{ + int (*create) (const oconfig_item_t *ci, void **user_data); + int (*destroy) (void **user_data); + int (*invoke) (const data_set_t *ds, value_list_t *vl, + notification_meta_t **meta, void **user_data); +}; +typedef struct target_proc_s target_proc_t; + +int fc_register_target (const char *name, target_proc_t proc); + +/* + * TODO: Chain management + */ +#if 0 +int fc_chain_add (const char *chain_name, + const char *target_name, int target_argc, char **target_argv); +int fc_chain_delete (const char *chain_name); +#endif + +/* + * TODO: Rule management + */ +#if 0 +int fc_rule_add (const char *chain_name, int position, + int match_num, const char **match_name, int *match_argc, char ***match_argv, + const char *target_name, int target_argc, char **target_argv); +int fc_rule_delete (const char *chain_name, int position); +#endif + +/* + * Processing function + */ +int fc_process (const data_set_t *ds, value_list_t *vl); + +/* + * Shortcut for global configuration + */ +int fc_configure (const oconfig_item_t *ci); + +#endif /* FILTER_CHAIN_H */ +/* vim: set sw=2 sts=2 et : */