filter_pcre: Added a plugin to filter value lists based on PCRE.
authorSebastian Harl <sh@tokkee.org>
Tue, 28 Oct 2008 21:02:38 +0000 (22:02 +0100)
committerSebastian Harl <sh@tokkee.org>
Tue, 28 Oct 2008 21:02:38 +0000 (22:02 +0100)
The user may specify a set of Perl-compatible regular expressions to match any
component of the (host, plugin, plugin instance, type, type instance) tuple.
Any of the filter flags may be used to handle a successful match.

Sample plugin configuration:

  <Plugin filter_pcre>
    <RegEx>
      Host "^mail\d+$"
      Plugin "^tcpconns$"
      TypeInstance "^SYN_"

      Action NoWrite
  </Plugin>

README
configure.in
src/Makefile.am
src/collectd.conf.in
src/collectd.conf.pod
src/filter_pcre.c [new file with mode: 0644]

diff --git a/README b/README
index b6bdb41..8ff8aee 100644 (file)
--- a/README
+++ b/README
@@ -250,6 +250,12 @@ Features
       needed. Please read collectd-unixsock(5) for a description on how that's
       done.
 
+  * Filtering and rewriting values dispatched to collectd can be done by the
+    following plugins:
+
+    - filter_pcre
+      Filter value lists based on Perl-compatible regular expressions.
+
   * Logging is, as everything in collectd, provided by plugins. The following
     plugins keep up informed about what's going on:
 
@@ -416,6 +422,10 @@ Prerequisites
     Used to capture packets by the `dns' plugin.
     <http://www.tcpdump.org/>
 
+  * libpcre (optional)
+    Used by the `filter_pcre' plugin.
+    <http://www.pcre.org/>
+
   * libperl (optional)
     Obviously used by the `perl' plugin. The library has to be compiled with
     ithread support (introduced in Perl 5.6.0).
index 90ac462..9319d5f 100644 (file)
@@ -1641,6 +1641,78 @@ AC_DEFINE_UNQUOTED(COLLECT_LIBPCAP, [$collect_libpcap],
 AM_CONDITIONAL(BUILD_WITH_LIBPCAP, test "x$with_libpcap" = "xyes")
 # }}}
 
+# --with-libpcre {{{
+with_pcre_config="pcre-config"
+with_pcre_cflags=""
+with_pcre_libs=""
+AC_ARG_WITH(libpcre, [AS_HELP_STRING([--with-libpcre@<:@=PREFIX@:>@],
+       [Path to libpcre.])],
+       [
+               if test "x$withval" = "xno"
+               then
+                       with_libpcre="no"
+               else if test "x$withval" = "xyes"
+               then
+                       with_libpcre="yes"
+               else
+                       if test -f "$withval" && test -x "$withval"
+                       then
+                               with_pcre_config="$withval"
+                       else if test -x "$withval/bin/pcre-config"
+                       then
+                               with_pcre_config="$withval/bin/pcre-config"
+                       fi; fi
+                       with_libpcre="yes"
+               fi; fi
+       ],
+       [
+               with_libpcre="yes"
+       ])
+
+if test "x$with_libpcre" = "xyes"
+then
+       with_pcre_cflags=`$with_pcre_config --cflags 2>/dev/null`
+       pcre_config_status=$?
+
+       if test $pcre_config_status -ne 0
+       then
+               with_libpcre="no ($with_pcre_config failed)"
+       else
+               SAVE_CPPFLAGS="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $with_pcre_cflags"
+
+               AC_CHECK_HEADERS(pcre.h, [], [with_libpcre="no (pcre.h not found)"], [])
+
+               CPPFLAGS="$SAVE_CPPFLAGS"
+       fi
+fi
+
+if test "x$with_libpcre" = "xyes"
+then
+       with_pcre_libs=`$with_pcre_config --libs 2>/dev/null`
+       pcre_config_status=$?
+
+       if test $pcre_config_status -ne 0
+       then
+               with_libpcre="no ($with_pcre_config failed)"
+       else
+               AC_CHECK_LIB(pcre, pcre_compile,
+                       [with_libpcre="yes"],
+                       [with_libpcre="no (symbol 'pcre_compile' not found)"],
+                       [$with_pcre_libs])
+       fi
+fi
+
+if test "x$with_libpcre" = "xyes"
+then
+       BUILD_WITH_LIBPCRE_CFLAGS="$with_pcre_cflags"
+       BUILD_WITH_LIBPCRE_LIBS="$with_pcre_libs"
+       AC_SUBST(BUILD_WITH_LIBPCRE_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBPCRE_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBPCRE, test "x$with_libpcre" = "xyes")
+# }}}
+
 # --with-libperl {{{
 perl_interpreter="perl"
 AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to libperl.])],
@@ -2792,6 +2864,7 @@ AC_PLUGIN([entropy],     [$plugin_entropy],    [Entropy statistics])
 AC_PLUGIN([exec],        [yes],                [Execution of external programs])
 AC_PLUGIN([filecount],   [yes],                [Count files in directories])
 AC_PLUGIN([filter_ignore], [yes],                [Ignore specific values])
+AC_PLUGIN([filter_pcre], [$with_libpcre],      [Filter based on PCRE])
 AC_PLUGIN([hddtemp],     [yes],                [Query hddtempd])
 AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
 AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
@@ -2927,6 +3000,7 @@ Configuration:
     libopenipmi . . . . . $with_libopenipmipthread
     liboping  . . . . . . $with_liboping
     libpcap . . . . . . . $with_libpcap
+    libpcre . . . . . . . $with_libpcre
     libperl . . . . . . . $with_libperl
     libpthread  . . . . . $with_libpthread
     libpq . . . . . . . . $with_libpq
@@ -2963,6 +3037,7 @@ Configuration:
     exec  . . . . . . . . $enable_exec
     filecount . . . . . . $enable_filecount
     filter_ignore . . . . $enable_filter_ignore
+    filter_pcre . . . . . $enable_filter_pcre
     hddtemp . . . . . . . $enable_hddtemp
     interface . . . . . . $enable_interface
     iptables  . . . . . . $enable_iptables
index 2196d32..fb3587b 100644 (file)
@@ -282,6 +282,16 @@ collectd_LDADD += "-dlopen" filter_ignore.la
 collectd_DEPENDENCIES += filter_ignore.la
 endif
 
+if BUILD_PLUGIN_FILTER_PCRE
+pkglib_LTLIBRARIES += filter_pcre.la
+filter_pcre_la_SOURCES = filter_pcre.c
+filter_pcre_la_CPPFLAGS = $(BUILD_WITH_LIBPCRE_CFLAGS)
+filter_pcre_la_LDFLAGS = -module -avoid-version \
+               $(BUILD_WITH_LIBPCRE_LIBS)
+collectd_LDADD += "-dlopen" filter_pcre.la
+collectd_DEPENDENCIES += filter_pcre.la
+endif
+
 if BUILD_PLUGIN_HDDTEMP
 pkglib_LTLIBRARIES += hddtemp.la
 hddtemp_la_SOURCES = hddtemp.c
index 0597273..4963a06 100644 (file)
@@ -42,6 +42,7 @@ FQDNLookup   true
 @BUILD_PLUGIN_ENTROPY_TRUE@LoadPlugin entropy
 @BUILD_PLUGIN_EXEC_TRUE@LoadPlugin exec
 @BUILD_PLUGIN_FILECOUNT_TRUE@LoadPlugin filecount
+@BUILD_PLUGIN_FILTER_PCRE_TRUE@LoadPlugin filter_pcre
 @BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
 @BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
 @BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
@@ -170,6 +171,16 @@ FQDNLookup   true
 #      </Directory>
 #</Plugin>
 
+#<Plugin filter_pcre>
+#      <RegEx>
+#              Host "^mail\d+$"
+#              Plugin "^tcpconns$"
+#              TypeInstance "^SYN_"
+#
+#              Action NoWrite
+#      </RegEx>
+#</Plugin>
+
 @BUILD_PLUGIN_HDDTEMP_TRUE@<Plugin hddtemp>
 #      Host "127.0.0.1"
 #      Port "7634"
index 5d07ee5..bc494b8 100644 (file)
@@ -648,6 +648,70 @@ note that there are 1000 bytes in a kilobyte, not 1024.
 
 =back
 
+=head2 Plugin C<filter_pcre>
+
+This plugin allows you to filter value lists based on Perl-compatible regular
+expressions whose syntax and semantics are as close as possible to those of
+the Perl 5 language. See L<pcre(3)> for details.
+
+  <Plugin filter_pcre>
+    <RegEx>
+      Host "^mail\d+$"
+      Plugin "^tcpconns$"
+      TypeInstance "^SYN_"
+
+      Action NoWrite
+  </Plugin>
+
+The configuration consists of one or more C<RegEx> blocks, each of which
+specifies a regular expression identifying a set of value lists and how to
+handle successful matches. A value list keeps the values of a single data-set
+and is identified by the tuple (host, plugin, plugin instance, type, type
+instance). The plugin and type instances are optional components. If they are
+missing they are treated as empty strings. Within those blocks, the following
+options are recognized:
+
+=over 4
+
+=item B<Host> I<regex>
+
+=item B<Plugin> I<regex>
+
+=item B<PluginInstance> I<regex>
+
+=item B<Type> I<regex>
+
+=item B<TypeInstance> I<regex>
+
+Specifies the regular expression for each component of the identifier. If any
+of these options is missing it is interpreted as a pattern which matches any
+string. All five components of a value list have to match the appropriate
+regular expression to trigger the specified action.
+
+=item B<Action> I<NoWrite>|I<NoThresholdCheck>|I<Ignore>
+
+Specify how to handle successful matches:
+
+=over 4
+
+=item B<NoWrite>
+
+Do not send the value list to any output (a.k.a. write) plugins.
+
+=item B<NoThresholdCheck>
+
+Skip threshold checking for this value list.
+
+=item B<Ignore>
+
+Completely ignore this value list.
+
+=back
+
+Two or more actions may be combined by specifying multiple B<Action> options.
+
+=back
+
 =head2 Plugin C<hddtemp>
 
 To get values from B<hddtemp> collectd connects to B<localhost> (127.0.0.1),
@@ -2290,6 +2354,7 @@ L<types.db(5)>,
 L<hddtemp(8)>,
 L<kstat(3KSTAT)>,
 L<mbmon(1)>,
+L<pcre(3)>,
 L<psql(1)>,
 L<rrdtool(1)>,
 L<sensors(1)>
diff --git a/src/filter_pcre.c b/src/filter_pcre.c
new file mode 100644 (file)
index 0000000..a92dee0
--- /dev/null
@@ -0,0 +1,329 @@
+/**
+ * collectd - src/filter_pcre.c
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * 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
+ *
+ * Author:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module allows to filter value lists based on Perl-compatible regular
+ * expressions.
+ */
+
+#include "collectd.h"
+#include "configfile.h"
+#include "plugin.h"
+#include "common.h"
+
+#include <pcre.h>
+
+#define log_err(...) ERROR ("filter_pcre: " __VA_ARGS__)
+#define log_warn(...) WARNING ("filter_pcre: " __VA_ARGS__)
+
+/*
+ * private data types
+ */
+
+typedef struct {
+       pcre       *re;
+       pcre_extra *extra;
+} c_pcre_t;
+
+#define C_PCRE_INIT(regex) do { \
+               (regex).re    = NULL; \
+               (regex).extra = NULL; \
+       } while (0)
+
+#define C_PCRE_FREE(regex) do { \
+               pcre_free ((regex).re); \
+               pcre_free ((regex).extra); \
+               C_PCRE_INIT (regex); \
+       } while (0)
+
+typedef struct {
+       c_pcre_t host;
+       c_pcre_t plugin;
+       c_pcre_t plugin_instance;
+       c_pcre_t type;
+       c_pcre_t type_instance;
+
+       int action;
+} regex_t;
+
+/*
+ * private variables
+ */
+
+static regex_t *regexes     = NULL;
+static int      regexes_num = 0;
+
+/*
+ * internal helper functions
+ */
+
+/* returns true if string matches the regular expression */
+static int c_pcre_match (c_pcre_t *re, const char *string)
+{
+       int status;
+       int ovector[30];
+
+       if (NULL == re)
+               return 1;
+
+       if (NULL == string)
+               string = "";
+
+       status = pcre_exec (re->re,
+                       /* extra       = */ re->extra,
+                       /* subject     = */ string,
+                       /* length      = */ strlen (string),
+                       /* startoffset = */ 0,
+                       /* options     = */ 0,
+                       /* ovector     = */ ovector,
+                       /* ovecsize    = */ STATIC_ARRAY_SIZE (ovector));
+
+       if (0 <= status)
+               return 1;
+
+       if (PCRE_ERROR_NOMATCH != status)
+               log_err ("PCRE matching of string \"%s\" failed with status %d",
+                               string, status);
+       return 0;
+} /* c_pcre_match */
+
+static regex_t *regex_new (void)
+{
+       regex_t *re;
+
+       ++regexes_num;
+       regexes = (regex_t *)realloc (regexes, regexes_num * sizeof (*regexes));
+       if (NULL == regexes) {
+               log_err ("Out of memory.");
+               exit (5);
+       }
+
+       re = regexes + (regexes_num - 1);
+
+       C_PCRE_INIT (re->host);
+       C_PCRE_INIT (re->plugin);
+       C_PCRE_INIT (re->plugin_instance);
+       C_PCRE_INIT (re->type);
+       C_PCRE_INIT (re->type_instance);
+
+       re->action = 0;
+       return re;
+} /* regex_new */
+
+static void regex_delete (regex_t *re)
+{
+       if (NULL == re)
+               return;
+
+       C_PCRE_FREE (re->host);
+       C_PCRE_FREE (re->plugin);
+       C_PCRE_FREE (re->plugin_instance);
+       C_PCRE_FREE (re->type);
+       C_PCRE_FREE (re->type_instance);
+
+       re->action = 0;
+} /* regex_delete */
+
+/* returns true if the value list matches the regular expression */
+static int regex_match (regex_t *re, value_list_t *vl)
+{
+       int matches = 0;
+
+       if (NULL == re)
+               return 1;
+
+       if ((NULL == re->host.re) || c_pcre_match (&re->host, vl->host))
+               ++matches;
+
+       if ((NULL == re->plugin.re) || c_pcre_match (&re->plugin, vl->plugin))
+               ++matches;
+
+       if ((NULL == re->plugin_instance.re)
+                       || c_pcre_match (&re->plugin_instance, vl->plugin_instance))
+               ++matches;
+
+       if ((NULL == re->type.re) || c_pcre_match (&re->type, vl->type))
+               ++matches;
+
+       if ((NULL == re->type_instance.re)
+                       || c_pcre_match (&re->type_instance, vl->type_instance))
+               ++matches;
+
+       if (5 == matches)
+               return 1;
+       return 0;
+} /* regex_match */
+
+/*
+ * interface to collectd
+ */
+
+static int c_pcre_filter (const data_set_t *ds, value_list_t *vl)
+{
+       int i;
+
+       for (i = 0; i < regexes_num; ++i)
+               if (regex_match (regexes + i, vl))
+                       return regexes[i].action;
+       return 0;
+} /* c_pcre_filter */
+
+static int c_pcre_shutdown (void)
+{
+       int i;
+
+       plugin_unregister_filter ("filter_pcre");
+       plugin_unregister_shutdown ("filter_pcre");
+
+       for (i = 0; i < regexes_num; ++i)
+               regex_delete (regexes + i);
+
+       sfree (regexes);
+       regexes_num = 0;
+       return 0;
+} /* c_pcre_shutdown */
+
+static int config_set_regex (c_pcre_t *re, oconfig_item_t *ci)
+{
+       const char *pattern;
+       const char *errptr;
+       int erroffset;
+
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("<RegEx>: %s expects a single string argument.", ci->key);
+               return 1;
+       }
+
+       pattern = ci->values[0].value.string;
+
+       re->re = pcre_compile (pattern,
+                       /* options   = */ 0,
+                       /* errptr    = */ &errptr,
+                       /* erroffset = */ &erroffset,
+                       /* tableptr  = */ NULL);
+
+       if (NULL == re->re) {
+               log_err ("<RegEx>: PCRE compilation of pattern \"%s\" failed "
+                               "at offset %d: %s", pattern, erroffset, errptr);
+               return 1;
+       }
+
+       re->extra = pcre_study (re->re,
+                       /* options = */ 0,
+                       /* errptr  = */ &errptr);
+
+       if (NULL != errptr) {
+               log_err ("<RegEx>: PCRE studying of pattern \"%s\" failed: %s",
+                               pattern, errptr);
+               return 1;
+       }
+       return 0;
+} /* config_set_regex */
+
+static int config_set_action (int *action, oconfig_item_t *ci)
+{
+       const char *action_str;
+
+       if ((0 != ci->children_num) || (1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("<RegEx>: Action expects a single string argument.");
+               return 1;
+       }
+
+       action_str = ci->values[0].value.string;
+
+       if (0 == strcasecmp (action_str, "NoWrite"))
+               *action |= FILTER_NOWRITE;
+       else if (0 == strcasecmp (action_str, "NoThresholdCheck"))
+               *action |= FILTER_NOTHRESHOLD_CHECK;
+       else if (0 == strcasecmp (action_str, "Ignore"))
+               *action |= FILTER_IGNORE;
+       else
+               log_warn ("<Regex>: Ignoring unknown action \"%s\".", action_str);
+       return 0;
+} /* config_set_action */
+
+static int c_pcre_config_regex (oconfig_item_t *ci)
+{
+       regex_t *re;
+       int i;
+
+       if (0 != ci->values_num) {
+               log_err ("<RegEx> expects no arguments.");
+               return 1;
+       }
+
+       re = regex_new ();
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+               int status = 0;
+
+               if (0 == strcasecmp (c->key, "Host"))
+                       status = config_set_regex (&re->host, c);
+               else if (0 == strcasecmp (c->key, "Plugin"))
+                       status = config_set_regex (&re->plugin, c);
+               else if (0 == strcasecmp (c->key, "PluginInstance"))
+                       status = config_set_regex (&re->plugin_instance, c);
+               else if (0 == strcasecmp (c->key, "Type"))
+                       status = config_set_regex (&re->type, c);
+               else if (0 == strcasecmp (c->key, "TypeInstance"))
+                       status = config_set_regex (&re->type_instance, c);
+               else if (0 == strcasecmp (c->key, "Action"))
+                       status = config_set_action (&re->action, c);
+               else
+                       log_warn ("<RegEx>: Ignoring unknown config key \"%s\".", c->key);
+
+               if (0 != status) {
+                       log_err ("Ignoring regular expression definition.");
+                       regex_delete (re);
+                       --regexes_num;
+               }
+       }
+       return 0;
+} /* c_pcre_config_regex */
+
+static int c_pcre_config (oconfig_item_t *ci)
+{
+       int i;
+
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *c = ci->children + i;
+
+               if (0 == strcasecmp (c->key, "RegEx"))
+                       c_pcre_config_regex (c);
+               else
+                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
+       }
+
+       plugin_register_filter ("filter_pcre", c_pcre_filter);
+       plugin_register_shutdown ("filter_pcre", c_pcre_shutdown);
+       return 0;
+} /* c_pcre_config */
+
+void module_register (void)
+{
+       plugin_register_complex_config ("filter_pcre", c_pcre_config);
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+