Merge branch 'collectd-4.9' into collectd-4.10
authorFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sat, 6 Nov 2010 11:08:20 +0000 (12:08 +0100)
committerFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sat, 6 Nov 2010 11:08:20 +0000 (12:08 +0100)
Conflicts:
src/collectd.conf.in
src/python.c

1  2 
README
configure.in
src/collectd-python.pod
src/collectd.h
src/curl_json.c
src/netapp.c
src/network.c
src/python.c
src/utils_match.c

diff --combined README
--- 1/README
--- 2/README
+++ b/README
@@@ -57,10 -57,6 +57,10 @@@ Feature
        Retrieves JSON data via cURL and parses it according to user
        configuration.
  
 +    - curl_xml
 +      Retrieves XML data via cURL and parses it according to user
 +      configuration.
 +
      - dbi
        Executes SQL statements on various databases and interprets the returned
        data.
        Memory utilization: Memory occupied by running processes, page cache,
        buffer cache and free.
  
 +    - modbus
 +      Reads values from Modbus/TCP enabled devices. Supports reading values
 +      from multiple "slaves" so gateway devices can be used.
 +
      - multimeter
        Information provided by serial multimeters, such as the `Metex
        M-4650CR'.
        write your own plugins in Perl and return arbitrary values using this
        API. See collectd-perl(5).
  
 +    - pinba
 +      Receive and dispatch timing values from Pinba, a profiling extension for
 +      PHP.
 +
      - ping
        Network latency: Time to reach the default gateway or another given
        host.
@@@ -549,10 -537,6 +549,10 @@@ Prerequisite
      Used by the `memcachec' plugin to connect to a memcache daemon.
      <http://tangent.org/552/libmemcached.html>
  
 +  * libmodbus (optional)
 +    Used by the `modbus' plugin to communicate with Modbus/TCP devices.
 +    <https://launchpad.net/libmodbus>
 +
    * libmysqlclient (optional)
      Unsurprisingly used by the `mysql' plugin.
      <http://dev.mysql.com/>
      Used to capture packets by the `dns' plugin.
      <http://www.tcpdump.org/>
  
+   * libperfstat (optional)
+     Used by various plugins to gather statistics under AIX.
    * libperl (optional)
      Obviously used by the `perl' plugin. The library has to be compiled with
      ithread support (introduced in Perl 5.6.0).
      The PostgreSQL C client library used by the `postgresql' plugin.
      <http://www.postgresql.org/>
  
 +  * libprotobuf-c, protoc-c (optional)
 +    Used by the `pinba' plugin to generate a parser for the network packets
 +    sent by the Pinba PHP extension.
 +    <http://code.google.com/p/protobuf-c/>
 +
    * libpython (optional)
      Used by the `python' plugin. Currently, only 2.3 ≦ Python < 3 is supported.
      <http://www.python.org/>
diff --combined configure.in
@@@ -47,9 -47,6 +47,9 @@@ AC_PROG_LE
  AC_PROG_YACC
  PKG_PROG_PKG_CONFIG
  
 +AC_CHECK_PROG([have_protoc_c], [protoc-c], [yes], [no])
 +AM_CONDITIONAL(HAVE_PROTOC_C, test "x$have_protoc_c" = "xyes")
 +
  AC_MSG_CHECKING([for kernel type ($host_os)])
  case $host_os in
        *linux*)
@@@ -91,6 -88,10 +91,10 @@@ if test "x$ac_system" = "xSolaris
  then
        AC_DEFINE(_POSIX_PTHREAD_SEMANTICS, 1, [Define to enforce POSIX thread semantics under Solaris.])
  fi
+ if test "x$ac_system" = "xAIX"
+ then
+       AC_DEFINE(_THREAD_SAFE_ERRNO, 1, [Define to use the thread-safe version of errno under AIX.])
+ fi
  
  # Where to install .pc files.
  pkgconfigdir="${libdir}/pkgconfig"
@@@ -480,7 -481,7 +484,7 @@@ AC_HEADER_TIM
  # Checks for library functions.
  #
  AC_PROG_GCC_TRADITIONAL
 -AC_CHECK_FUNCS(gettimeofday select strdup strtol getaddrinfo getnameinfo strchr memcpy strstr strcmp strncmp strncpy strlen strncasecmp strcasecmp openlog closelog sysconf setenv)
 +AC_CHECK_FUNCS(gettimeofday select strdup strtol getaddrinfo getnameinfo strchr memcpy strstr strcmp strncmp strncpy strlen strncasecmp strcasecmp openlog closelog sysconf setenv if_indextoname)
  
  AC_FUNC_STRERROR_R
  
@@@ -1003,13 -1004,6 +1007,13 @@@ AC_CHECK_MEMBERS([struct net_device_sta
        #include <linux/netdevice.h>
        ])
  
 +AC_CHECK_MEMBERS([struct ip_mreqn.imr_ifindex], [],
 +      [],
 +      [
 +      #include <netinet/in.h>
 +      #include <net/if.h>
 +      ])
 +
  AC_CHECK_MEMBERS([struct kinfo_proc.ki_pid, struct kinfo_proc.ki_rssize, struct kinfo_proc.ki_rusage],
        [
                AC_DEFINE(HAVE_STRUCT_KINFO_PROC_FREEBSD, 1,
  AM_CONDITIONAL(BUILD_WITH_LIBMEMCACHED, test "x$with_libmemcached" = "xyes")
  # }}}
  
 +# --with-libmodbus {{{
 +with_libmodbus_config=""
 +with_libmodbus_cflags=""
 +with_libmodbus_libs=""
 +AC_ARG_WITH(libmodbus, [AS_HELP_STRING([--with-libmodbus@<:@=PREFIX@:>@], [Path to the modbus library.])],
 +[
 +      if test "x$withval" = "xno"
 +      then
 +              with_libmodbus="no"
 +      else if test "x$withval" = "xyes"
 +      then
 +              with_libmodbus="use_pkgconfig"
 +      else if test -d "$with_libmodbus/lib"
 +      then
 +              AC_MSG_NOTICE([Not checking for libmodbus: Manually configured])
 +              with_libmodbus_cflags="-I$withval/include"
 +              with_libmodbus_libs="-L$withval/lib -lmodbus"
 +              with_libmodbus="yes"
 +      fi; fi; fi
 +],
 +[with_libmodbus="use_pkgconfig"])
 +
 +# configure using pkg-config
 +if test "x$with_libmodbus" = "xuse_pkgconfig"
 +then
 +      if test "x$PKG_CONFIG" = "x"
 +      then
 +              with_libmodbus="no (Don't have pkg-config)"
 +      fi
 +fi
 +if test "x$with_libmodbus" = "xuse_pkgconfig"
 +then
 +      AC_MSG_NOTICE([Checking for modbus using $PKG_CONFIG])
 +      $PKG_CONFIG --exists 'modbus' 2>/dev/null
 +      if test $? -ne 0
 +      then
 +              with_libmodbus="no (pkg-config doesn't know library)"
 +      fi
 +fi
 +if test "x$with_libmodbus" = "xuse_pkgconfig"
 +then
 +      with_libmodbus_cflags="`$PKG_CONFIG --cflags 'modbus'`"
 +      if test $? -ne 0
 +      then
 +              with_libmodbus="no ($PKG_CONFIG failed)"
 +      fi
 +      with_libmodbus_libs="`$PKG_CONFIG --libs 'modbus'`"
 +      if test $? -ne 0
 +      then
 +              with_libmodbus="no ($PKG_CONFIG failed)"
 +      fi
 +fi
 +if test "x$with_libmodbus" = "xuse_pkgconfig"
 +then
 +      with_libmodbus="yes"
 +fi
 +
 +# with_libmodbus_cflags and with_libmodbus_libs are set up now, let's do
 +# the actual checks.
 +if test "x$with_libmodbus" = "xyes"
 +then
 +      SAVE_CPPFLAGS="$CPPFLAGS"
 +      CPPFLAGS="$CPPFLAGS $with_libmodbus_cflags"
 +
 +      AC_CHECK_HEADERS(modbus/modbus.h, [], [with_libmodbus="no (modbus/modbus.h not found)"])
 +
 +      CPPFLAGS="$SAVE_CPPFLAGS"
 +fi
 +if test "x$with_libmodbus" = "xyes"
 +then
 +      SAVE_CPPFLAGS="$CPPFLAGS"
 +      SAVE_LDFLAGS="$LDFLAGS"
 +
 +      CPPFLAGS="$CPPFLAGS $with_libmodbus_cflags"
 +      LDFLAGS="$LDFLAGS $with_libmodbus_libs"
 +
 +      AC_CHECK_LIB(modbus, modbus_init_tcp,
 +                   [with_libmodbus="yes"],
 +                   [with_libmodbus="no (symbol modbus_init_tcp not found)"])
 +
 +      CPPFLAGS="$SAVE_CPPFLAGS"
 +      LDFLAGS="$SAVE_LDFLAGS"
 +fi
 +if test "x$with_libmodbus" = "xyes"
 +then
 +      BUILD_WITH_LIBMODBUS_CFLAGS="$with_libmodbus_cflags"
 +      BUILD_WITH_LIBMODBUS_LIBS="$with_libmodbus_libs"
 +      AC_SUBST(BUILD_WITH_LIBMODBUS_CFLAGS)
 +      AC_SUBST(BUILD_WITH_LIBMODBUS_LIBS)
 +fi
 +# }}}
 +
  # --with-libmysql {{{
  with_mysql_config="mysql_config"
  with_mysql_cflags=""
  if test "x$with_python" = "xyes"
  then
        AC_MSG_CHECKING([for Python CPPFLAGS])
 -      python_include_path=`echo "import distutils.sysconfig;print distutils.sysconfig.get_python_inc()" | "$with_python_prog" 2>&1`
 +      python_include_path=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_python_inc())" | "$with_python_prog" 2>&1`
        python_config_status=$?
  
        if test "$python_config_status" -ne 0 || test "x$python_include_path" = "x"
  if test "x$with_python" = "xyes"
  then
        AC_MSG_CHECKING([for Python LDFLAGS])
 -      python_library_path=`echo "import distutils.sysconfig;print distutils.sysconfig.get_config_vars(\"LIBDIR\").__getitem__(0)" | "$with_python_prog" 2>&1`
 +      python_library_path=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_config_vars(\"LIBDIR\").__getitem__(0))" | "$with_python_prog" 2>&1`
        python_config_status=$?
  
        if test "$python_config_status" -ne 0 || test "x$python_library_path" = "x"
  if test "x$with_python" = "xyes"
  then
        AC_MSG_CHECKING([for Python LIBS])
 -      python_library_flags=`echo "import distutils.sysconfig;print distutils.sysconfig.get_config_vars(\"BLDLIBRARY\").__getitem__(0)" | "$with_python_prog" 2>&1`
 +      python_library_flags=`echo "import distutils.sysconfig;import sys;sys.stdout.write(distutils.sysconfig.get_config_vars(\"BLDLIBRARY\").__getitem__(0))" | "$with_python_prog" 2>&1`
        python_config_status=$?
  
        if test "$python_config_status" -ne 0 || test "x$python_library_flags" = "x"
@@@ -3920,7 -3822,7 +3924,7 @@@ AC_DEFUN
             enable_plugin="yes"
             force="yes"
       else
 -           enable_plugin="no"
 +           enable_plugin="no (disabled on command line)"
       fi; fi
      ],
      [
@@@ -3973,7 -3875,6 +3977,7 @@@ plugin_contextswitch="no
  plugin_cpu="no"
  plugin_cpufreq="no"
  plugin_curl_json="no"
 +plugin_curl_xml="no"
  plugin_df="no"
  plugin_disk="no"
  plugin_entropy="no"
        plugin_curl_json="yes"
  fi
  
 +if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"
 +then
 +      plugin_curl_xml="yes"
 +fi
 +
  if test "x$have_processor_info" = "xyes"
  then
        plugin_cpu="yes"
@@@ -4271,7 -4167,6 +4275,7 @@@ AC_PLUGIN([cpu],         [$plugin_cpu]
  AC_PLUGIN([csv],         [yes],                [CSV output plugin])
  AC_PLUGIN([curl],        [$with_libcurl],      [CURL generic web statistics])
  AC_PLUGIN([curl_json],   [$plugin_curl_json],    [CouchDB statistics])
 +AC_PLUGIN([curl_xml],   [$plugin_curl_xml],    [CURL generic xml statistics])
  AC_PLUGIN([dbi],         [$with_libdbi],       [General database statistics])
  AC_PLUGIN([df],          [$plugin_df],         [Filesystem usage statistics])
  AC_PLUGIN([disk],        [$plugin_disk],       [Disk usage statistics])
@@@ -4302,7 -4197,6 +4306,7 @@@ AC_PLUGIN([mbmon],       [yes]
  AC_PLUGIN([memcachec],   [$with_libmemcached], [memcachec statistics])
  AC_PLUGIN([memcached],   [yes],                [memcached statistics])
  AC_PLUGIN([memory],      [$plugin_memory],     [Memory usage])
 +AC_PLUGIN([modbus],      [$with_libmodbus],    [Modbus plugin])
  AC_PLUGIN([multimeter],  [$plugin_multimeter], [Read multimeter values])
  AC_PLUGIN([mysql],       [$with_libmysql],     [MySQL statistics])
  AC_PLUGIN([netapp],      [$with_libnetapp],    [NetApp plugin])
@@@ -4319,8 -4213,6 +4323,8 @@@ AC_PLUGIN([onewire],     [$with_libowca
  AC_PLUGIN([openvpn],     [yes],                [OpenVPN client statistics])
  AC_PLUGIN([oracle],      [$with_oracle],       [Oracle plugin])
  AC_PLUGIN([perl],        [$plugin_perl],       [Embed a Perl interpreter])
 +# FIXME: Check for libevent, too.
 +AC_PLUGIN([pinba],       [$have_protoc_c],     [Pinba statistics])
  AC_PLUGIN([ping],        [$with_liboping],     [Network latency statistics])
  AC_PLUGIN([postgresql],  [$with_libpq],        [PostgreSQL database statistics])
  AC_PLUGIN([powerdns],    [yes],                [PowerDNS statistics])
@@@ -4538,7 -4430,6 +4542,7 @@@ Configuration
      libkstat  . . . . . . $with_kstat
      libkvm  . . . . . . . $with_libkvm
      libmemcached  . . . . $with_libmemcached
 +    libmodbus . . . . . . $with_libmodbus
      libmysql  . . . . . . $with_libmysql
      libnetapp . . . . . . $with_libnetapp
      libnetlink  . . . . . $with_libnetlink
      libxml2 . . . . . . . $with_libxml2
      libxmms . . . . . . . $with_libxmms
      libyajl . . . . . . . $with_libyajl
 +    libevent  . . . . . . $with_libevent
 +    protobuf-c  . . . . . $have_protoc_c
      oracle  . . . . . . . $with_oracle
      python  . . . . . . . $with_python
  
      csv . . . . . . . . . $enable_csv
      curl  . . . . . . . . $enable_curl
      curl_json . . . . . . $enable_curl_json
 +    curl_xml  . . . . . . $enable_curl_xml
      dbi . . . . . . . . . $enable_dbi
      df  . . . . . . . . . $enable_df
      disk  . . . . . . . . $enable_disk
      memcachec . . . . . . $enable_memcachec
      memcached . . . . . . $enable_memcached
      memory  . . . . . . . $enable_memory
 +    modbus  . . . . . . . $enable_modbus
      multimeter  . . . . . $enable_multimeter
      mysql . . . . . . . . $enable_mysql
      netapp  . . . . . . . $enable_netapp
      openvpn . . . . . . . $enable_openvpn
      oracle  . . . . . . . $enable_oracle
      perl  . . . . . . . . $enable_perl
 +    pinba . . . . . . . . $enable_pinba
      ping  . . . . . . . . $enable_ping
      postgresql  . . . . . $enable_postgresql
      powerdns  . . . . . . $enable_powerdns
diff --combined src/collectd-python.pod
@@@ -1,3 -1,13 +1,13 @@@
+ # 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.
  =head1 NAME
  
  collectd-python - Documentation of collectd's C<python plugin>
@@@ -27,7 -37,8 +37,7 @@@ for collectd in Python. This is a lot m
  Python-script every time you want to read a value with the C<exec plugin> (see
  L<collectd-exec(5)>) and provides a lot more functionality, too.
  
 -Currently only I<Python 2> is supported and at least I<version 2.3> is
 -required.
 +At least python I<version 2.3> is required.
  
  =head1 CONFIGURATION
  
@@@ -105,15 -116,6 +115,15 @@@ environment variable, e.g. I<export PAG
  Depending on your version of python this might or might not result in an
  B<OSError> exception which can be ignored.
  
 +If you really need to spawn new processes from python you can register an init
 +callback and reset the action for SIGCHLD to the default behavior. Please note
 +that this I<will> break the exec plugin. Do not even load the exec plugin if
 +you intend to do this!
 +
 +There is an example script located in B<contrib/python/getsigchld.py>  to do
 +this. If you import this from I<collectd.conf> SIGCHLD will be handled
 +normally and spawning processes from python will work as intended.
 +
  =back
  
  =item E<lt>B<Module> I<Name>E<gt> block
@@@ -127,29 -129,6 +137,29 @@@ The I<name> identifies the callback
  
  =back
  
 +=head1 STRINGS
 +
 +There are a lot of places where strings are send from collectd to python and
 +from python to collectd. How exactly this works depends on wheather byte or
 +unicode strings or python2 or python3 are used.
 +
 +Python2 has I<str>, which is just bytes, and I<unicode>. Python3 has I<str>,
 +which is a unicode object, and I<bytes>.
 +
 +When passing strings from python to collectd all of these object are supported
 +in all places, however I<str> should be used if possible. These strings must
 +not contain a NUL byte. Ignoring this will result in a I<TypeError> exception.
 +If a byte string was used it will be used as is by collectd. If a unicode
 +object was used it will be encoded using the default encoding (see above). If
 +this is not possible python will raise a I<UnicodeEncodeError> exception.
 +
 +Wenn passing strings from collectd to python the behavior depends on the
 +python version used. Python2 will always receive a I<str> object. Python3 will
 +usually receive a I<str> object as well, however the original string will be
 +decoded to unicode using the default encoding. If this fails because the
 +string is not a valid sequence for this encoding a I<bytes> object will be
 +returned instead.
 +
  =head1 WRITING YOUR OWN PLUGINS
  
  Writing your own plugins is quite simple. collectd manages plugins by means of
@@@ -240,28 -219,6 +250,28 @@@ collectd you're done
  The following complex types are used to pass values between the Python plugin
  and collectd:
  
 +=head2 Signed
 +
 +The Signed class is just a long. It has all its methods and behaves exactly
 +like any other long object. It is used to indicate if an integer was or should
 +be stored as a signed or unsigned integer object.
 +
 + class Signed(long)
 +
 +This is a long by another name. Use it in meta data dicts
 +to choose the way it is stored in the meta data.
 +
 +=head2 Unsigned
 +
 +The Unsigned class is just a long. It has all its methods and behaves exactly
 +like any other long object. It is used to indicate if an integer was or should
 +be stored as a signed or unsigned integer object.
 +
 + class Unsigned(long)
 +
 +This is a long by another name. Use it in meta data dicts
 +to choose the way it is stored in the meta data.
 +
  =head2 Config
  
  The Config class is an object which keeps the information provided in the
@@@ -425,16 -382,6 +435,16 @@@ If the sequence does not have the corre
  exception will be raised. If the content of the sequence is not a number, a
  I<TypeError> exception will be raised.
  
 +=item meta
 +
 +These are the meta data for this Value object.
 +It has to be a dictionary of numbers, strings or bools. All keys must be
 +strings. I<int> and <long> objects will be dispatched as signed integers unless
 +they are between 2**63 and 2**64-1, which will result in a unsigned integer.
 +You can force one of these storage classes by using the classes
 +B<collectd.Signed> and B<collectd.Unsigned>. A meta object received by a write
 +callback will always contain B<Signed> or B<Unsigned> objects.
 +
  =back
  
  =head2 Notification
@@@ -693,8 -640,14 +703,8 @@@ dispatched by the python plugin after u
  
  =item
  
 -This plugin is not compatible with python3. Trying to compile it with python3
 -will fail because of the ways string, unicode and bytearray behavior was
 -changed.
 -
 -=item
 -
  Not all aspects of the collectd API are accessible from python. This includes
 -but is not limited to meta-data, filters and data sets.
 +but is not limited to filters and data sets.
  
  =back
  
diff --combined src/collectd.h
@@@ -227,10 -227,6 +227,6 @@@ typedef bool _Bool
  # include <kstat.h>
  #endif
  
- #if HAVE_SENSORS_SENSORS_H
- # include <sensors/sensors.h>
- #endif
  #ifndef PACKAGE_NAME
  #define PACKAGE_NAME "collectd"
  #endif
  
  extern char hostname_g[];
  extern int  interval_g;
 +extern int  timeout_g;
  
  #endif /* COLLECTD_H */
diff --combined src/curl_json.c
@@@ -671,7 -671,7 +671,7 @@@ static int cj_config_add_url (oconfig_i
      ssnprintf (cb_name, sizeof (cb_name), "curl_json-%s-%s",
                 db->instance, db->url);
  
 -    plugin_register_complex_read (cb_name, cj_read,
 +    plugin_register_complex_read (/* group = */ NULL, cb_name, cj_read,
                                    /* interval = */ NULL, &ud);
    }
    else
@@@ -764,6 -764,7 +764,7 @@@ static int cj_curl_perform (cj_t *db, C
    if (db->yajl == NULL)
    {
      ERROR ("curl_json plugin: yajl_alloc failed.");
+     db->yajl = yprev;
      return (-1);
    }
  
diff --combined src/netapp.c
@@@ -566,7 -566,7 +566,7 @@@ static int submit_values (const char *h
                const char *plugin_inst,
                const char *type, const char *type_inst,
                value_t *values, int values_len,
-               time_t timestamp)
+               time_t timestamp, int interval)
  {
        value_list_t vl = VALUE_LIST_INIT;
  
        if (timestamp > 0)
                vl.time = timestamp;
  
+       if (interval > 0)
+               vl.interval = interval;
        if (host != NULL)
                sstrncpy (vl.host, host, sizeof (vl.host));
        else
  
  static int submit_two_counters (const char *host, const char *plugin_inst, /* {{{ */
                const char *type, const char *type_inst, counter_t val0, counter_t val1,
-               time_t timestamp)
+               time_t timestamp, int interval)
  {
        value_t values[2];
  
        values[1].counter = val1;
  
        return (submit_values (host, plugin_inst, type, type_inst,
-                               values, 2, timestamp));
+                               values, 2, timestamp, interval));
  } /* }}} int submit_two_counters */
  
  static int submit_counter (const char *host, const char *plugin_inst, /* {{{ */
-               const char *type, const char *type_inst, counter_t counter, time_t timestamp)
+               const char *type, const char *type_inst, counter_t counter, time_t timestamp, int interval)
  {
        value_t v;
  
        v.counter = counter;
  
        return (submit_values (host, plugin_inst, type, type_inst,
-                               &v, 1, timestamp));
+                               &v, 1, timestamp, interval));
  } /* }}} int submit_counter */
  
  static int submit_two_gauge (const char *host, const char *plugin_inst, /* {{{ */
                const char *type, const char *type_inst, gauge_t val0, gauge_t val1,
-               time_t timestamp)
+               time_t timestamp, int interval)
  {
        value_t values[2];
  
        values[1].gauge = val1;
  
        return (submit_values (host, plugin_inst, type, type_inst,
-                               values, 2, timestamp));
+                               values, 2, timestamp, interval));
  } /* }}} int submit_two_gauge */
  
  static int submit_double (const char *host, const char *plugin_inst, /* {{{ */
-               const char *type, const char *type_inst, double d, time_t timestamp)
+               const char *type, const char *type_inst, double d, time_t timestamp, int interval)
  {
        value_t v;
  
        v.gauge = (gauge_t) d;
  
        return (submit_values (host, plugin_inst, type, type_inst,
-                               &v, 1, timestamp));
+                               &v, 1, timestamp, interval));
  } /* }}} int submit_uint64 */
  
  /* Calculate hit ratio from old and new counters and submit the resulting
@@@ -647,7 -650,8 +650,8 @@@ static int submit_cache_ratio (const ch
                uint64_t new_misses,
                uint64_t old_hits,
                uint64_t old_misses,
-               time_t timestamp)
+               time_t timestamp,
+               int interval)
  {
        value_t v;
  
        }
  
        return (submit_values (host, plugin_inst, "cache_ratio", type_inst,
-                               &v, 1, timestamp));
+                               &v, 1, timestamp, interval));
  } /* }}} int submit_cache_ratio */
  
  /* Submits all the caches used by WAFL. Uses "submit_cache_ratio". */
  static int submit_wafl_data (const char *hostname, const char *instance, /* {{{ */
-               cfg_wafl_t *old_data, const cfg_wafl_t *new_data)
+               cfg_wafl_t *old_data, const cfg_wafl_t *new_data, int interval)
  {
        /* Submit requested counters */
        if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_NAME_CACHE | HAVE_WAFL_NAME_CACHE)
                submit_cache_ratio (hostname, instance, "name_cache_hit",
                                new_data->name_cache_hit, new_data->name_cache_miss,
                                old_data->name_cache_hit, old_data->name_cache_miss,
-                               new_data->timestamp);
+                               new_data->timestamp, interval);
  
        if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_DIR_CACHE | HAVE_WAFL_FIND_DIR)
                        && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_FIND_DIR))
                submit_cache_ratio (hostname, instance, "find_dir_hit",
                                new_data->find_dir_hit, new_data->find_dir_miss,
                                old_data->find_dir_hit, old_data->find_dir_miss,
-                               new_data->timestamp);
+                               new_data->timestamp, interval);
  
        if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_BUF_CACHE | HAVE_WAFL_BUF_HASH)
                        && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_BUF_HASH))
                submit_cache_ratio (hostname, instance, "buf_hash_hit",
                                new_data->buf_hash_hit, new_data->buf_hash_miss,
                                old_data->buf_hash_hit, old_data->buf_hash_miss,
-                               new_data->timestamp);
+                               new_data->timestamp, interval);
  
        if (HAS_ALL_FLAGS (old_data->flags, CFG_WAFL_INODE_CACHE | HAVE_WAFL_INODE_CACHE)
                        && HAS_ALL_FLAGS (new_data->flags, HAVE_WAFL_INODE_CACHE))
                submit_cache_ratio (hostname, instance, "inode_cache_hit",
                                new_data->inode_cache_hit, new_data->inode_cache_miss,
                                old_data->inode_cache_hit, old_data->inode_cache_miss,
-                               new_data->timestamp);
+                               new_data->timestamp, interval);
  
        /* Clear old HAVE_* flags */
        old_data->flags &= ~HAVE_WAFL_ALL;
   * update flags appropriately. */
  static int submit_volume_perf_data (const char *hostname, /* {{{ */
                data_volume_perf_t *old_data,
-               const data_volume_perf_t *new_data)
+               const data_volume_perf_t *new_data, int interval)
  {
        char plugin_instance[DATA_MAX_NAME_LEN];
  
                        && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_BYTES_READ | HAVE_VOLUME_PERF_BYTES_WRITE))
        {
                submit_two_counters (hostname, plugin_instance, "disk_octets", /* type instance = */ NULL,
-                               (counter_t) new_data->read_bytes, (counter_t) new_data->write_bytes, new_data->timestamp);
+                               (counter_t) new_data->read_bytes, (counter_t) new_data->write_bytes, new_data->timestamp, interval);
        }
  
        /* Check for and submit disk-operations values */
                        && HAS_ALL_FLAGS (new_data->flags, HAVE_VOLUME_PERF_OPS_READ | HAVE_VOLUME_PERF_OPS_WRITE))
        {
                submit_two_counters (hostname, plugin_instance, "disk_ops", /* type instance = */ NULL,
-                               (counter_t) new_data->read_ops, (counter_t) new_data->write_ops, new_data->timestamp);
+                               (counter_t) new_data->read_ops, (counter_t) new_data->write_ops, new_data->timestamp, interval);
        }
  
        /* Check for, calculate and submit disk-latency values */
                }
  
                submit_two_gauge (hostname, plugin_instance, "disk_latency", /* type instance = */ NULL,
-                               latency_per_op_read, latency_per_op_write, new_data->timestamp);
+                               latency_per_op_read, latency_per_op_write, new_data->timestamp, interval);
        }
  
        /* Clear all HAVE_* flags. */
   */
  /* Data corresponding to <WAFL /> */
  static int cna_handle_wafl_data (const char *hostname, cfg_wafl_t *cfg_wafl, /* {{{ */
-               na_elem_t *data)
+               na_elem_t *data, int interval)
  {
        cfg_wafl_t perf_data;
        const char *plugin_inst;
                }
        }
  
-       return (submit_wafl_data (hostname, plugin_inst, cfg_wafl, &perf_data));
+       return (submit_wafl_data (hostname, plugin_inst, cfg_wafl, &perf_data, interval));
  } /* }}} void cna_handle_wafl_data */
  
  static int cna_setup_wafl (cfg_wafl_t *cw) /* {{{ */
@@@ -973,7 -977,7 +977,7 @@@ static int cna_query_wafl (host_config_
                return (-1);
        }
  
-       status = cna_handle_wafl_data (host->name, host->cfg_wafl, data);
+       status = cna_handle_wafl_data (host->name, host->cfg_wafl, data, host->interval);
  
        if (status == 0)
                host->cfg_wafl->interval.last_read = now;
  
  /* Data corresponding to <Disks /> */
  static int cna_handle_disk_data (const char *hostname, /* {{{ */
-               cfg_disk_t *cfg_disk, na_elem_t *data)
+               cfg_disk_t *cfg_disk, na_elem_t *data, int interval)
  {
        time_t timestamp;
        na_elem_t *instances;
  
        if ((cfg_disk->flags & CFG_DISK_BUSIEST) && (worst_disk != NULL))
                submit_double (hostname, "system", "percent", "disk_busy",
-                               worst_disk->disk_busy_percent, timestamp);
+                               worst_disk->disk_busy_percent, timestamp, interval);
  
        return (0);
  } /* }}} int cna_handle_disk_data */
@@@ -1168,7 -1172,7 +1172,7 @@@ static int cna_query_disk (host_config_
                return (-1);
        }
  
-       status = cna_handle_disk_data (host->name, host->cfg_disk, data);
+       status = cna_handle_disk_data (host->name, host->cfg_disk, data, host->interval);
  
        if (status == 0)
                host->cfg_disk->interval.last_read = now;
  
  /* Data corresponding to <VolumePerf /> */
  static int cna_handle_volume_perf_data (const char *hostname, /* {{{ */
-               cfg_volume_perf_t *cvp, na_elem_t *data)
+               cfg_volume_perf_t *cvp, na_elem_t *data, int interval)
  {
        time_t timestamp;
        na_elem_t *elem_instances;
                        }
                } /* for (elem_counter) */
  
-               submit_volume_perf_data (hostname, v, &perf_data);
+               submit_volume_perf_data (hostname, v, &perf_data, interval);
        } /* for (volume) */
  
        return (0);
@@@ -1339,7 -1343,7 +1343,7 @@@ static int cna_query_volume_perf (host_
                return (-1);
        }
  
-       status = cna_handle_volume_perf_data (host->name, host->cfg_volume_perf, data);
+       status = cna_handle_volume_perf_data (host->name, host->cfg_volume_perf, data, host->interval);
  
        if (status == 0)
                host->cfg_volume_perf->interval.last_read = now;
  
  /* Data corresponding to <VolumeUsage /> */
  static int cna_submit_volume_usage_data (const char *hostname, /* {{{ */
-               cfg_volume_usage_t *cfg_volume)
+               cfg_volume_usage_t *cfg_volume, int interval)
  {
        data_volume_usage_t *v;
  
                if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_FREE))
                        submit_double (hostname, /* plugin instance = */ plugin_instance,
                                        "df_complex", "free",
-                                       (double) norm_free, /* timestamp = */ 0);
+                                       (double) norm_free, /* timestamp = */ 0, interval);
  
                if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SIS_SAVED))
                        submit_double (hostname, /* plugin instance = */ plugin_instance,
                                        "df_complex", "sis_saved",
-                                       (double) sis_saved, /* timestamp = */ 0);
+                                       (double) sis_saved, /* timestamp = */ 0, interval);
  
                if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_NORM_USED))
                        submit_double (hostname, /* plugin instance = */ plugin_instance,
                                        "df_complex", "used",
-                                       (double) norm_used, /* timestamp = */ 0);
+                                       (double) norm_used, /* timestamp = */ 0, interval);
  
                if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_RSVD))
                        submit_double (hostname, /* plugin instance = */ plugin_instance,
                                        "df_complex", "snap_reserved",
-                                       (double) snap_reserve_free, /* timestamp = */ 0);
+                                       (double) snap_reserve_free, /* timestamp = */ 0, interval);
  
                if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED | HAVE_VOLUME_USAGE_SNAP_RSVD))
                        submit_double (hostname, /* plugin instance = */ plugin_instance,
                                        "df_complex", "snap_reserve_used",
-                                       (double) snap_reserve_used, /* timestamp = */ 0);
+                                       (double) snap_reserve_used, /* timestamp = */ 0, interval);
  
                if (HAS_ALL_FLAGS (v->flags, HAVE_VOLUME_USAGE_SNAP_USED))
                        submit_double (hostname, /* plugin instance = */ plugin_instance,
                                        "df_complex", "snap_normal_used",
-                                       (double) snap_norm_used, /* timestamp = */ 0);
+                                       (double) snap_norm_used, /* timestamp = */ 0, interval);
  
                /* Clear all the HAVE_* flags */
                v->flags &= ~HAVE_VOLUME_USAGE_ALL;
@@@ -1650,7 -1654,7 +1654,7 @@@ static int cna_handle_volume_usage_dat
                } /* }}} end of 32-bit workaround */
        } /* for (elem_volume) */
  
-       return (cna_submit_volume_usage_data (host->name, cfg_volume));
+       return (cna_submit_volume_usage_data (host->name, cfg_volume, host->interval));
  } /* }}} int cna_handle_volume_usage_data */
  
  static int cna_setup_volume_usage (cfg_volume_usage_t *cvu) /* {{{ */
@@@ -1714,7 -1718,7 +1718,7 @@@ static int cna_query_volume_usage (host
  
  /* Data corresponding to <System /> */
  static int cna_handle_system_data (const char *hostname, /* {{{ */
-               cfg_system_t *cfg_system, na_elem_t *data)
+               cfg_system_t *cfg_system, na_elem_t *data, int interval)
  {
        na_elem_t *instances;
        na_elem_t *counter;
                                && (value > 0) && (strlen(name) > 4)
                                && (!strcmp(name + strlen(name) - 4, "_ops"))) {
                        submit_counter (hostname, instance, "disk_ops_complex", name,
-                                       (counter_t) value, timestamp);
+                                       (counter_t) value, timestamp, interval);
                }
        } /* for (counter) */
  
        if ((cfg_system->flags & CFG_SYSTEM_DISK)
                        && (HAS_ALL_FLAGS (counter_flags, 0x01 | 0x02)))
                submit_two_counters (hostname, instance, "disk_octets", NULL,
-                               disk_read, disk_written, timestamp);
+                               disk_read, disk_written, timestamp, interval);
                                
        if ((cfg_system->flags & CFG_SYSTEM_NET)
                        && (HAS_ALL_FLAGS (counter_flags, 0x04 | 0x08)))
                submit_two_counters (hostname, instance, "if_octets", NULL,
-                               net_recv, net_sent, timestamp);
+                               net_recv, net_sent, timestamp, interval);
  
        if ((cfg_system->flags & CFG_SYSTEM_CPU)
                        && (HAS_ALL_FLAGS (counter_flags, 0x10 | 0x20)))
        {
                submit_counter (hostname, instance, "cpu", "system",
-                               cpu_busy, timestamp);
+                               cpu_busy, timestamp, interval);
                submit_counter (hostname, instance, "cpu", "idle",
-                               cpu_total - cpu_busy, timestamp);
+                               cpu_total - cpu_busy, timestamp, interval);
        }
  
        return (0);
@@@ -1862,7 -1866,7 +1866,7 @@@ static int cna_query_system (host_confi
                return (-1);
        }
  
-       status = cna_handle_system_data (host->name, host->cfg_system, data);
+       status = cna_handle_system_data (host->name, host->cfg_system, data, host->interval);
  
        if (status == 0)
                host->cfg_system->interval.last_read = now;
@@@ -2559,7 -2563,7 +2563,7 @@@ static int cna_config (oconfig_item_t *
                        ud.data = host;
                        ud.free_func = (void (*) (void *)) free_host_config;
  
 -                      plugin_register_complex_read (cb_name,
 +                      plugin_register_complex_read (/* group = */ NULL, cb_name,
                                        /* callback  = */ cna_read, 
                                        /* interval  = */ (host->interval > 0) ? &interval : NULL,
                                        /* user data = */ &ud);
diff --combined src/network.c
@@@ -31,6 -31,7 +31,7 @@@
  #include "utils_fbhash.h"
  #include "utils_avltree.h"
  #include "utils_cache.h"
+ #include "utils_complain.h"
  
  #include "network.h"
  
@@@ -52,9 -53,6 +53,9 @@@
  #if HAVE_POLL_H
  # include <poll.h>
  #endif
 +#if HAVE_NET_IF_H
 +# include <net/if.h>
 +#endif
  
  #if HAVE_LIBGCRYPT
  # include <gcrypt.h>
@@@ -121,7 -119,6 +122,7 @@@ typedef struct socken
  
        char *node;
        char *service;
 +      int interface;
  
        union
        {
@@@ -350,8 -347,7 +351,8 @@@ static _Bool check_send_okay (const val
    return (!received);
  } /* }}} _Bool check_send_okay */
  
 -static int network_dispatch_values (value_list_t *vl) /* {{{ */
 +static int network_dispatch_values (value_list_t *vl, /* {{{ */
 +    const char *username)
  {
    int status;
  
      return (status);
    }
  
 +  if (username != NULL)
 +  {
 +    status = meta_data_add_string (vl->meta, "network:username", username);
 +    if (status != 0)
 +    {
 +      ERROR ("network plugin: meta_data_add_string failed.");
 +      meta_data_destroy (vl->meta);
 +      vl->meta = NULL;
 +      return (status);
 +    }
 +  }
 +
    plugin_dispatch_values (vl);
    stats_values_dispatched++;
  
@@@ -905,8 -889,7 +906,8 @@@ static int parse_part_string (void **re
  #define PP_SIGNED    0x01
  #define PP_ENCRYPTED 0x02
  static int parse_packet (sockent_t *se,
 -              void *buffer, size_t buffer_size, int flags);
 +              void *buffer, size_t buffer_size, int flags,
 +              const char *username);
  
  #define BUFFER_READ(p,s) do { \
    memcpy ((p), buffer + buffer_offset, (s)); \
  static int parse_part_sign_sha256 (sockent_t *se, /* {{{ */
      void **ret_buffer, size_t *ret_buffer_len, int flags)
  {
+   static c_complain_t complain_no_users = C_COMPLAIN_INIT_STATIC;
    char *buffer;
    size_t buffer_len;
    size_t buffer_offset;
  
    if (se->data.server.userdb == NULL)
    {
-     NOTICE ("network plugin: Received signed network packet but can't verify "
-         "it because no user DB has been configured. Will accept it.");
+     c_complain (LOG_NOTICE, &complain_no_users,
+         "network plugin: Received signed network packet but can't verify it "
+         "because no user DB has been configured. Will accept it.");
      return (0);
    }
  
    {
      ERROR ("network plugin: gcry_md_setkey failed: %s", gcry_strerror (err));
      gcry_md_close (hd);
 +    sfree (secret);
 +    sfree (pss.username);
      return (-1);
    }
  
    gcry_md_close (hd);
    hd = NULL;
  
 -  sfree (secret);
 -  sfree (pss.username);
 -
    if (memcmp (pss.hash, hash, sizeof (pss.hash)) != 0)
    {
      WARNING ("network plugin: Verifying HMAC-SHA-256 signature failed: "
    else
    {
      parse_packet (se, buffer + buffer_offset, buffer_len - buffer_offset,
 -        flags | PP_SIGNED);
 +        flags | PP_SIGNED, pss.username);
    }
  
 +  sfree (secret);
 +  sfree (pss.username);
 +
    *ret_buffer = buffer + buffer_len;
    *ret_buffer_len = 0;
  
@@@ -1081,8 -1065,7 +1085,8 @@@ static int parse_part_sign_sha256 (sock
      warning_has_been_printed = 1;
    }
  
 -  parse_packet (se, buffer + part_len, buffer_size - part_len, flags);
 +  parse_packet (se, buffer + part_len, buffer_size - part_len, flags,
 +      /* username = */ NULL);
  
    *ret_buffer = buffer + buffer_size;
    *ret_buffer_size = 0;
@@@ -1201,9 -1184,7 +1205,9 @@@ static int parse_part_encr_aes256 (sock
    }
  
    parse_packet (se, buffer + buffer_offset, payload_len,
 -      flags | PP_ENCRYPTED);
 +      flags | PP_ENCRYPTED, pea.username);
 +
 +  /* XXX: Free pea.username?!? */
  
    /* Update return values */
    *ret_buffer =     buffer     + part_size;
@@@ -1265,8 -1246,7 +1269,8 @@@ static int parse_part_encr_aes256 (sock
  #undef BUFFER_READ
  
  static int parse_packet (sockent_t *se, /* {{{ */
 -              void *buffer, size_t buffer_size, int flags)
 +              void *buffer, size_t buffer_size, int flags,
 +              const char *username)
  {
        int status;
  
                        if (status != 0)
                                break;
  
 -                      network_dispatch_values (&vl);
 +                      network_dispatch_values (&vl, username);
  
                        sfree (vl.values);
                }
@@@ -1614,110 -1594,7 +1618,110 @@@ static int network_set_ttl (const socke
        return (0);
  } /* int network_set_ttl */
  
 -static int network_bind_socket (int fd, const struct addrinfo *ai)
 +static int network_set_interface (const sockent_t *se, const struct addrinfo *ai) /* {{{ */
 +{
 +      DEBUG ("network plugin: network_set_interface: interface index = %i;",
 +                      se->interface);
 +
 +        assert (se->type == SOCKENT_TYPE_CLIENT);
 +
 +      if (ai->ai_family == AF_INET)
 +      {
 +              struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
 +
 +              if (IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
 +              {
 +#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
 +                      /* If possible, use the "ip_mreqn" structure which has
 +                       * an "interface index" member. Using the interface
 +                       * index is preferred here, because of its similarity
 +                       * to the way IPv6 handles this. Unfortunately, it
 +                       * appears not to be portable. */
 +                      struct ip_mreqn mreq;
 +
 +                      memset (&mreq, 0, sizeof (mreq));
 +                      mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
 +                      mreq.imr_address.s_addr = ntohl (INADDR_ANY);
 +                      mreq.imr_ifindex = se->interface;
 +#else
 +                      struct ip_mreq mreq;
 +
 +                      memset (&mreq, 0, sizeof (mreq));
 +                      mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
 +                      mreq.imr_interface.s_addr = ntohl (INADDR_ANY);
 +#endif
 +
 +                      if (setsockopt (se->data.client.fd, IPPROTO_IP, IP_MULTICAST_IF,
 +                                              &mreq, sizeof (mreq)) != 0)
 +                      {
 +                              char errbuf[1024];
 +                              ERROR ("setsockopt: %s",
 +                                              sstrerror (errno, errbuf, sizeof (errbuf)));
 +                              return (-1);
 +                      }
 +
 +                      return (0);
 +              }
 +      }
 +      else if (ai->ai_family == AF_INET6)
 +      {
 +              struct sockaddr_in6 *addr = (struct sockaddr_in6 *) ai->ai_addr;
 +
 +              if (IN6_IS_ADDR_MULTICAST (&addr->sin6_addr))
 +              {
 +                      if (setsockopt (se->data.client.fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
 +                                              &se->interface,
 +                                              sizeof (se->interface)) != 0)
 +                      {
 +                              char errbuf[1024];
 +                              ERROR ("setsockopt: %s",
 +                                              sstrerror (errno, errbuf,
 +                                                      sizeof (errbuf)));
 +                              return (-1);
 +                      }
 +
 +                      return (0);
 +              }
 +      }
 +
 +      /* else: Not a multicast interface. */
 +#if defined(HAVE_IF_INDEXTONAME) && HAVE_IF_INDEXTONAME && defined(SO_BINDTODEVICE)
 +      if (se->interface != 0)
 +      {
 +              char interface_name[IFNAMSIZ];
 +
 +              if (if_indextoname (se->interface, interface_name) == NULL)
 +                      return (-1);
 +
 +              DEBUG ("network plugin: Binding socket to interface %s", interface_name);
 +
 +              if (setsockopt (se->data.client.fd, SOL_SOCKET, SO_BINDTODEVICE,
 +                                      interface_name,
 +                                      sizeof(interface_name)) == -1 )
 +              {
 +                      char errbuf[1024];
 +                      ERROR ("setsockopt: %s",
 +                                      sstrerror (errno, errbuf, sizeof (errbuf)));
 +                      return (-1);
 +              }
 +      }
 +/* #endif HAVE_IF_INDEXTONAME && SO_BINDTODEVICE */
 +
 +#else
 +      WARNING ("network plugin: Cannot set the interface on a unicast "
 +                      "socket because "
 +# if !defined(SO_BINDTODEVICE)
 +                      "the the \"SO_BINDTODEVICE\" socket option "
 +# else
 +                      "the \"if_indextoname\" function "
 +# endif
 +                      "is not available on your system.");
 +#endif
 +
 +      return (0);
 +} /* }}} network_set_interface */
 +
 +static int network_bind_socket (int fd, const struct addrinfo *ai, const int interface_idx)
  {
        int loop = 0;
        int yes  = 1;
                struct sockaddr_in *addr = (struct sockaddr_in *) ai->ai_addr;
                if (IN_MULTICAST (ntohl (addr->sin_addr.s_addr)))
                {
 +#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
 +                      struct ip_mreqn mreq;
 +#else
                        struct ip_mreq mreq;
 +#endif
  
                        DEBUG ("fd = %i; IPv4 multicast address found", fd);
  
                        mreq.imr_multiaddr.s_addr = addr->sin_addr.s_addr;
 -                      mreq.imr_interface.s_addr = htonl (INADDR_ANY);
 +#if HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
 +                      /* Set the interface using the interface index if
 +                       * possible (available). Unfortunately, the struct
 +                       * ip_mreqn is not portable. */
 +                      mreq.imr_address.s_addr = ntohl (INADDR_ANY);
 +                      mreq.imr_ifindex = interface_idx;
 +#else
 +                      mreq.imr_interface.s_addr = ntohl (INADDR_ANY);
 +#endif
  
                        if (setsockopt (fd, IPPROTO_IP, IP_MULTICAST_LOOP,
                                                &loop, sizeof (loop)) == -1)
                                                        sizeof (errbuf)));
                                return (-1);
                        }
 +
 +                      return (0);
                }
        }
        else if (ai->ai_family == AF_INET6)
                         * single interface; programs running on
                         * multihomed hosts may need to join the same
                         * group on more than one interface.*/
 -                      mreq.ipv6mr_interface = 0;
 +                      mreq.ipv6mr_interface = interface_idx;
  
                        if (setsockopt (fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
                                                &loop, sizeof (loop)) == -1)
                                                        sizeof (errbuf)));
                                return (-1);
                        }
 +
 +                      return (0);
                }
        }
  
 +#if defined(HAVE_IF_INDEXTONAME) && HAVE_IF_INDEXTONAME && defined(SO_BINDTODEVICE)
 +      /* if a specific interface was set, bind the socket to it. But to avoid
 +       * possible problems with multicast routing, only do that for non-multicast
 +       * addresses */
 +      if (interface_idx != 0)
 +      {
 +              char interface_name[IFNAMSIZ];
 +
 +              if (if_indextoname (interface_idx, interface_name) == NULL)
 +                      return (-1);
 +
 +              DEBUG ("fd = %i; Binding socket to interface %s", fd, interface_name);
 +
 +              if (setsockopt (fd, SOL_SOCKET, SO_BINDTODEVICE,
 +                                      interface_name,
 +                                      sizeof(interface_name)) == -1 )
 +              {
 +                      char errbuf[1024];
 +                      ERROR ("setsockopt: %s",
 +                                      sstrerror (errno, errbuf, sizeof (errbuf)));
 +                      return (-1);
 +              }
 +      }
 +#endif /* HAVE_IF_INDEXTONAME && SO_BINDTODEVICE */
 +
        return (0);
  } /* int network_bind_socket */
  
@@@ -1877,7 -1713,6 +1881,7 @@@ static int sockent_init (sockent_t *se
        se->type = SOCKENT_TYPE_CLIENT;
        se->node = NULL;
        se->service = NULL;
 +      se->interface = 0;
        se->next = NULL;
  
        if (type == SOCKENT_TYPE_SERVER)
@@@ -2026,7 -1861,7 +2030,7 @@@ static int sockent_open (sockent_t *se
                                continue;
                        }
  
 -                      status = network_bind_socket (*tmp, ai_ptr);
 +                      status = network_bind_socket (*tmp, ai_ptr, se->interface);
                        if (status != 0)
                        {
                                close (*tmp);
                        se->data.client.addrlen = ai_ptr->ai_addrlen;
  
                        network_set_ttl (se, ai_ptr);
 +                      network_set_interface (se, ai_ptr);
  
                        /* We don't open more than one write-socket per
                         * node/service pair.. */
@@@ -2200,8 -2034,7 +2204,8 @@@ static void *dispatch_thread (void __at
        continue;
      }
  
 -    parse_packet (se, ent->data, ent->data_len, /* flags = */ 0);
 +    parse_packet (se, ent->data, ent->data_len, /* flags = */ 0,
 +      /* username = */ NULL);
      sfree (ent->data);
      sfree (ent);
    } /* while (42) */
@@@ -2779,25 -2612,6 +2783,25 @@@ static int network_config_set_ttl (cons
    return (0);
  } /* }}} int network_config_set_ttl */
  
 +static int network_config_set_interface (const oconfig_item_t *ci, /* {{{ */
 +    int *interface)
 +{
 +  if ((ci->values_num != 1)
 +      || (ci->values[0].type != OCONFIG_TYPE_STRING))
 +  {
 +    WARNING ("network plugin: The `Interface' config option needs exactly "
 +        "one string argument.");
 +    return (-1);
 +  }
 +
 +  if (interface == NULL)
 +    return (-1);
 +
 +  *interface = if_nametoindex (ci->values[0].value.string);
 +
 +  return (0);
 +} /* }}} int network_config_set_interface */
 +
  static int network_config_set_buffer_size (const oconfig_item_t *ci) /* {{{ */
  {
    int tmp;
@@@ -2909,10 -2723,6 +2913,10 @@@ static int network_config_add_listen (c
            &se->data.server.security_level);
      else
  #endif /* HAVE_LIBGCRYPT */
 +    if (strcasecmp ("Interface", child->key) == 0)
 +      network_config_set_interface (child,
 +          &se->interface);
 +    else
      {
        WARNING ("network plugin: Option `%s' is not allowed here.",
            child->key);
@@@ -2991,10 -2801,6 +2995,10 @@@ static int network_config_add_server (c
            &se->data.client.security_level);
      else
  #endif /* HAVE_LIBGCRYPT */
 +    if (strcasecmp ("Interface", child->key) == 0)
 +      network_config_set_interface (child,
 +          &se->interface);
 +    else
      {
        WARNING ("network plugin: Option `%s' is not allowed here.",
            child->key);
diff --combined src/python.c
@@@ -245,7 -245,7 +245,7 @@@ static void cpy_build_name(char *buf, s
        
        mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
        if (mod != NULL)
 -              module = PyString_AsString(mod);
 +              module = cpy_unicode_or_bytes_to_string(&mod);
        
        if (module != NULL) {
                snprintf(buf, size, "python.%s", module);
        PyErr_Clear();
  }
  
 -static void cpy_log_exception(const char *context) {
 +void cpy_log_exception(const char *context) {
        int l = 0, i;
        const char *typename = NULL, *message = NULL;
        PyObject *type, *value, *traceback, *tn, *m, *list;
        PyErr_NormalizeException(&type, &value, &traceback);
        if (type == NULL) return;
        tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
 -      m = PyObject_GetAttrString(value, "message"); /* New reference. */
 +      m = PyObject_Str(value); /* New reference. */
        if (tn != NULL)
 -              typename = PyString_AsString(tn);
 +              typename = cpy_unicode_or_bytes_to_string(&tn);
        if (m != NULL)
 -              message = PyString_AsString(m);
 +              message = cpy_unicode_or_bytes_to_string(&m);
        if (typename == NULL)
                typename = "NamelessException";
        if (message == NULL)
                PyObject *line;
                
                line = PyList_GET_ITEM(list, i); /* Borrowed reference. */
 -              s = strdup(PyString_AsString(line));
 +              Py_INCREF(line);
 +              s = strdup(cpy_unicode_or_bytes_to_string(&line));
 +              Py_DECREF(line);
                if (s[strlen(s) - 1] == '\n')
                        s[strlen(s) - 1] = 0;
                Py_BEGIN_ALLOW_THREADS
@@@ -335,8 -333,7 +335,8 @@@ static int cpy_read_callback(user_data_
  static int cpy_write_callback(const data_set_t *ds, const value_list_t *value_list, user_data_t *data) {
        int i;
        cpy_callback_t *c = data->data;
 -      PyObject *ret, *v, *list;
 +      PyObject *ret, *list, *temp, *dict = NULL, *val;
 +      Values *v;
  
        CPY_LOCK_THREADS
                list = PyList_New(value_list->values_len); /* New reference. */
                                CPY_RETURN_FROM_THREADS 0;
                        }
                }
 -              v = PyObject_CallFunction((void *) &ValuesType, "sOssssdi", value_list->type,
 -                              list, value_list->plugin_instance, value_list->type_instance,
 -                              value_list->plugin, value_list->host, (double) value_list->time,
 -                              value_list->interval); /* New reference. */
 -              Py_DECREF(list);
 +              dict = PyDict_New();
 +              if (value_list->meta) {
 +                      int i, num;
 +                      char **table;
 +                      meta_data_t *meta = value_list->meta;
 +
 +                      num = meta_data_toc(meta, &table);
 +                      for (i = 0; i < num; ++i) {
 +                              int type;
 +                              char *string;
 +                              int64_t si;
 +                              uint64_t ui;
 +                              double d;
 +                              _Bool b;
 +                              
 +                              type = meta_data_type(meta, table[i]);
 +                              if (type == MD_TYPE_STRING) {
 +                                      if (meta_data_get_string(meta, table[i], &string))
 +                                              continue;
 +                                      temp = cpy_string_to_unicode_or_bytes(string);
 +                                      free(string);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_SIGNED_INT) {
 +                                      if (meta_data_get_signed_int(meta, table[i], &si))
 +                                              continue;
 +                                      temp = PyObject_CallFunctionObjArgs((void *) &SignedType, PyLong_FromLongLong(si), (void *) 0);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_UNSIGNED_INT) {
 +                                      if (meta_data_get_unsigned_int(meta, table[i], &ui))
 +                                              continue;
 +                                      temp = PyObject_CallFunctionObjArgs((void *) &UnsignedType, PyLong_FromUnsignedLongLong(ui), (void *) 0);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_DOUBLE) {
 +                                      if (meta_data_get_double(meta, table[i], &d))
 +                                              continue;
 +                                      temp = PyFloat_FromDouble(d);
 +                                      PyDict_SetItemString(dict, table[i], temp);
 +                                      Py_XDECREF(temp);
 +                              } else if (type == MD_TYPE_BOOLEAN) {
 +                                      if (meta_data_get_boolean(meta, table[i], &b))
 +                                              continue;
 +                                      if (b)
 +                                              PyDict_SetItemString(dict, table[i], Py_True);
 +                                      else
 +                                              PyDict_SetItemString(dict, table[i], Py_False);
 +                              }
 +                              free(table[i]);
 +                      }
 +                      free(table);
 +              }
 +              val = Values_New(); /* New reference. */
 +              v = (Values *) val; 
 +              sstrncpy(v->data.host, value_list->host, sizeof(v->data.host));
 +              sstrncpy(v->data.type, value_list->type, sizeof(v->data.type));
 +              sstrncpy(v->data.type_instance, value_list->type_instance, sizeof(v->data.type_instance));
 +              sstrncpy(v->data.plugin, value_list->plugin, sizeof(v->data.plugin));
 +              sstrncpy(v->data.plugin_instance, value_list->plugin_instance, sizeof(v->data.plugin_instance));
 +              v->data.time = value_list->time;
 +              v->interval = value_list->interval;
 +              Py_CLEAR(v->values);
 +              v->values = list;
 +              Py_CLEAR(v->meta);
 +              v->meta = dict;
                ret = PyObject_CallFunctionObjArgs(c->callback, v, c->data, (void *) 0); /* New reference. */
 -              Py_XDECREF(v);
 +              Py_XDECREF(val);
                if (ret == NULL) {
                        cpy_log_exception("write callback");
                } else {
  
  static int cpy_notification_callback(const notification_t *notification, user_data_t *data) {
        cpy_callback_t *c = data->data;
 -      PyObject *ret, *n;
 +      PyObject *ret, *notify;
 +      Notification *n;
  
        CPY_LOCK_THREADS
 -              n = PyObject_CallFunction((void *) &NotificationType, "ssssssdi", notification->type, notification->message,
 -                              notification->plugin_instance, notification->type_instance, notification->plugin,
 -                              notification->host, (double) notification->time, notification->severity); /* New reference. */
 +              notify = Notification_New(); /* New reference. */
 +              n = (Notification *) notify;
 +              sstrncpy(n->data.host, notification->host, sizeof(n->data.host));
 +              sstrncpy(n->data.type, notification->type, sizeof(n->data.type));
 +              sstrncpy(n->data.type_instance, notification->type_instance, sizeof(n->data.type_instance));
 +              sstrncpy(n->data.plugin, notification->plugin, sizeof(n->data.plugin));
 +              sstrncpy(n->data.plugin_instance, notification->plugin_instance, sizeof(n->data.plugin_instance));
 +              n->data.time = notification->time;
 +              sstrncpy(n->message, notification->message, sizeof(n->message));
 +              n->severity = notification->severity;
                ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data, (void *) 0); /* New reference. */
 -              Py_XDECREF(n);
 +              Py_XDECREF(notify);
                if (ret == NULL) {
                        cpy_log_exception("notification callback");
                } else {
  
  static void cpy_log_callback(int severity, const char *message, user_data_t *data) {
        cpy_callback_t * c = data->data;
 -      PyObject *ret;
 +      PyObject *ret, *text;
  
        CPY_LOCK_THREADS
 +      text = cpy_string_to_unicode_or_bytes(message);
        if (c->data == NULL)
 -              ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iN", severity, text); /* New reference. */
        else
 -              ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iNO", severity, text, c->data); /* New reference. */
  
        if (ret == NULL) {
                /* FIXME */
  
  static void cpy_flush_callback(int timeout, const char *id, user_data_t *data) {
        cpy_callback_t * c = data->data;
 -      PyObject *ret;
 +      PyObject *ret, *text;
  
        CPY_LOCK_THREADS
 +      text = cpy_string_to_unicode_or_bytes(id);
        if (c->data == NULL)
 -              ret = PyObject_CallFunction(c->callback, "is", timeout, id); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iN", timeout, text); /* New reference. */
        else
 -              ret = PyObject_CallFunction(c->callback, "isO", timeout, id, c->data); /* New reference. */
 +              ret = PyObject_CallFunction(c->callback, "iNO", timeout, text, c->data); /* New reference. */
  
        if (ret == NULL) {
                cpy_log_exception("flush callback");
@@@ -529,7 -455,7 +529,7 @@@ static PyObject *cpy_register_generic(c
        PyObject *callback = NULL, *data = NULL, *mod = NULL;
        static char *kwlist[] = {"callback", "data", "name", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL;
        if (PyCallable_Check(callback) == 0) {
                PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
                return NULL;
        c->next = *list_head;
        *list_head = c;
        Py_XDECREF(mod);
 -      return PyString_FromString(buf);
 +      return cpy_string_to_unicode_or_bytes(buf);
  }
  
  static PyObject *cpy_flush(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
        const char *plugin = NULL, *identifier = NULL;
        static char *kwlist[] = {"plugin", "timeout", "identifier", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "|ziz", kwlist, &plugin, &timeout, &identifier) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "|etiet", kwlist, NULL, &plugin, &timeout, NULL, &identifier) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_flush(plugin, timeout, identifier);
        Py_END_ALLOW_THREADS
@@@ -579,7 -505,7 +579,7 @@@ static PyObject *cpy_register_generic_u
        PyObject *callback = NULL, *data = NULL;
        static char *kwlist[] = {"callback", "data", "name", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oz", kwlist, &callback, &data, &name) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data, NULL, &name) == 0) return NULL;
        if (PyCallable_Check(callback) == 0) {
                PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
                return NULL;
        user_data->free_func = cpy_destroy_user_data;
        user_data->data = c;
        register_function(buf, handler, user_data);
 -      return PyString_FromString(buf);
 +      return cpy_string_to_unicode_or_bytes(buf);
  }
  
  static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwds) {
        struct timespec ts;
        static char *kwlist[] = {"callback", "interval", "data", "name", NULL};
        
 -      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOz", kwlist, &callback, &interval, &data, &name) == 0) return NULL;
 +      if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOet", kwlist, &callback, &interval, &data, NULL, &name) == 0) return NULL;
        if (PyCallable_Check(callback) == 0) {
                PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
                return NULL;
        user_data->data = c;
        ts.tv_sec = interval;
        ts.tv_nsec = (interval - ts.tv_sec) * 1000000000;
 -      plugin_register_complex_read(buf, cpy_read_callback, &ts, user_data);
 -      return PyString_FromString(buf);
 +      plugin_register_complex_read(/* group = */ NULL, buf,
 +                      cpy_read_callback, &ts, user_data);
 +      return cpy_string_to_unicode_or_bytes(buf);
  }
  
  static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
@@@ -660,7 -585,7 +660,7 @@@ static PyObject *cpy_register_shutdown(
  
  static PyObject *cpy_error(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_ERR, "%s", text);
        Py_END_ALLOW_THREADS
  
  static PyObject *cpy_warning(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_WARNING, "%s", text);
        Py_END_ALLOW_THREADS
  
  static PyObject *cpy_notice(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_NOTICE, "%s", text);
        Py_END_ALLOW_THREADS
  
  static PyObject *cpy_info(PyObject *self, PyObject *args) {
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_INFO, "%s", text);
        Py_END_ALLOW_THREADS
  static PyObject *cpy_debug(PyObject *self, PyObject *args) {
  #ifdef COLLECT_DEBUG
        const char *text;
 -      if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
 +      if (PyArg_ParseTuple(args, "et", NULL, &text) == 0) return NULL;
        Py_BEGIN_ALLOW_THREADS
        plugin_log(LOG_DEBUG, "%s", text);
        Py_END_ALLOW_THREADS
@@@ -710,13 -635,17 +710,13 @@@ static PyObject *cpy_unregister_generic
        const char *name;
        cpy_callback_t *prev = NULL, *tmp;
  
 -      if (PyUnicode_Check(arg)) {
 -              arg = PyUnicode_AsEncodedString(arg, NULL, NULL);
 -              if (arg == NULL)
 -                      return NULL;
 -              name = PyString_AsString(arg);
 -              Py_DECREF(arg);
 -      } else if (PyString_Check(arg)) {
 -              name = PyString_AsString(arg);
 -      } else {
 +      Py_INCREF(arg);
 +      name = cpy_unicode_or_bytes_to_string(&arg);
 +      if (name == NULL) {
 +              PyErr_Clear();
                if (!PyCallable_Check(arg)) {
                        PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
 +                      Py_DECREF(arg);
                        return NULL;
                }
                cpy_build_name(buf, sizeof(buf), arg, NULL);
                if (strcmp(name, tmp->name) == 0)
                        break;
        
 +      Py_DECREF(arg);
        if (tmp == NULL) {
                PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
                return NULL;
@@@ -747,24 -675,25 +747,24 @@@ static PyObject *cpy_unregister_generic
        char buf[512];
        const char *name;
  
 -      if (PyUnicode_Check(arg)) {
 -              arg = PyUnicode_AsEncodedString(arg, NULL, NULL);
 -              if (arg == NULL)
 -                      return NULL;
 -              name = PyString_AsString(arg);
 -              Py_DECREF(arg);
 -      } else if (PyString_Check(arg)) {
 -              name = PyString_AsString(arg);
 -      } else {
 +      Py_INCREF(arg);
 +      name = cpy_unicode_or_bytes_to_string(&arg);
 +      if (name == NULL) {
 +              PyErr_Clear();
                if (!PyCallable_Check(arg)) {
                        PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
 +                      Py_DECREF(arg);
                        return NULL;
                }
                cpy_build_name(buf, sizeof(buf), arg, NULL);
                name = buf;
        }
 -      if (unreg(name) == 0)
 +      if (unreg(name) == 0) {
 +              Py_DECREF(arg);
                Py_RETURN_NONE;
 +      }
        PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
 +      Py_DECREF(arg);
        return NULL;
  }
  
@@@ -940,7 -869,7 +940,7 @@@ static PyObject *cpy_oconfig_to_pyconfi
        values = PyTuple_New(ci->values_num); /* New reference. */
        for (i = 0; i < ci->values_num; ++i) {
                if (ci->values[i].type == OCONFIG_TYPE_STRING) {
 -                      PyTuple_SET_ITEM(values, i, PyString_FromString(ci->values[i].value.string));
 +                      PyTuple_SET_ITEM(values, i, cpy_string_to_unicode_or_bytes(ci->values[i].value.string));
                } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
                        PyTuple_SET_ITEM(values, i, PyFloat_FromDouble(ci->values[i].value.number));
                } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
                }
        }
        
 -      item = PyObject_CallFunction((void *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
 +      tmp = cpy_string_to_unicode_or_bytes(ci->key);
 +      item = PyObject_CallFunction((void *) &ConfigType, "NONO", tmp, parent, values, Py_None);
        if (item == NULL)
                return NULL;
        children = PyTuple_New(ci->children_num); /* New reference. */
        return item;
  }
  
 +#ifdef IS_PY3K
 +static struct PyModuleDef collectdmodule = {
 +      PyModuleDef_HEAD_INIT,
 +      "collectd",   /* name of module */
 +      "The python interface to collectd", /* module documentation, may be NULL */
 +      -1,
 +      cpy_methods
 +};
 +
 +PyMODINIT_FUNC PyInit_collectd(void) {
 +      return PyModule_Create(&collectdmodule);
 +}
 +#endif
 +
  static int cpy_config(oconfig_item_t *ci) {
        int i;
+       char *argv = "";
        PyObject *sys, *tb;
        PyObject *sys_path;
        PyObject *module;
         * python code during the config callback so we have to start
         * the interpreter here. */
        /* Do *not* use the python "thread" module at this point! */
 +
 +#ifdef IS_PY3K
 +      /* Add a builtin module, before Py_Initialize */
 +      PyImport_AppendInittab("collectd", PyInit_collectd);
 +#endif
 +      
        Py_Initialize();
        
        PyType_Ready(&ConfigType);
        PyType_Ready(&ValuesType);
        NotificationType.tp_base = &PluginDataType;
        PyType_Ready(&NotificationType);
 +      SignedType.tp_base = &PyLong_Type;
 +      PyType_Ready(&SignedType);
 +      UnsignedType.tp_base = &PyLong_Type;
 +      PyType_Ready(&UnsignedType);
        sys = PyImport_ImportModule("sys"); /* New reference. */
        if (sys == NULL) {
                cpy_log_exception("python initialization");
                cpy_log_exception("python initialization");
                return 1;
        }
+       PySys_SetArgv(1, &argv);
+       PyList_SetSlice(sys_path, 0, 1, NULL);
 +#ifdef IS_PY3K
 +      module = PyImport_ImportModule("collectd");
 +#else
        module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
 +#endif
        PyModule_AddObject(module, "Config", (void *) &ConfigType); /* Steals a reference. */
        PyModule_AddObject(module, "Values", (void *) &ValuesType); /* Steals a reference. */
        PyModule_AddObject(module, "Notification", (void *) &NotificationType); /* Steals a reference. */
 +      PyModule_AddObject(module, "Signed", (void *) &SignedType); /* Steals a reference. */
 +      PyModule_AddObject(module, "Unsigned", (void *) &UnsignedType); /* Steals a reference. */
        PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
        PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
        PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
                        
                        if (cf_util_get_string(item, &dir) != 0) 
                                continue;
 -                      dir_object = PyString_FromString(dir); /* New reference. */
 +                      dir_object = cpy_string_to_unicode_or_bytes(dir); /* New reference. */
                        if (dir_object == NULL) {
                                ERROR("python plugin: Unable to convert \"%s\" to "
                                      "a python object.", dir);
                        if (module == NULL) {
                                ERROR("python plugin: Error importing module \"%s\".", module_name);
                                cpy_log_exception("importing module");
 -                              PyErr_Print();
                        }
                        free(module_name);
                        Py_XDECREF(module);
diff --combined src/utils_match.c
  #include <regex.h>
  
  #define UTILS_MATCH_FLAGS_FREE_USER_DATA 0x01
 +#define UTILS_MATCH_FLAGS_EXCLUDE_REGEX 0x02
  
  struct cu_match_s
  {
    regex_t regex;
 +  regex_t excluderegex;
    int flags;
  
    int (*callback) (const char *str, char * const *matches, size_t matches_num,
@@@ -212,7 -210,7 +212,7 @@@ static int default_callback (const cha
  /*
   * Public functions
   */
 -cu_match_t *match_create_callback (const char *regex,
 +cu_match_t *match_create_callback (const char *regex, const char *excluderegex,
                int (*callback) (const char *str,
                  char * const *matches, size_t matches_num, void *user_data),
                void *user_data)
    cu_match_t *obj;
    int status;
  
 -  DEBUG ("utils_match: match_create_callback: regex = %s", regex);
 +  DEBUG ("utils_match: match_create_callback: regex = %s, excluderegex = %s",
 +       regex, excluderegex);
  
    obj = (cu_match_t *) malloc (sizeof (cu_match_t));
    if (obj == NULL)
      return (NULL);
    memset (obj, '\0', sizeof (cu_match_t));
  
-   status = regcomp (&obj->regex, regex, REG_EXTENDED);
+   status = regcomp (&obj->regex, regex, REG_EXTENDED | REG_NEWLINE);
    if (status != 0)
    {
      ERROR ("Compiling the regular expression \"%s\" failed.", regex);
      return (NULL);
    }
  
 +  if (excluderegex && strcmp(excluderegex, "") != 0) {
 +    status = regcomp (&obj->excluderegex, excluderegex, REG_EXTENDED);
 +    if (status != 0)
 +    {
 +      ERROR ("Compiling the excluding regular expression \"%s\" failed.",
 +             excluderegex);
 +      sfree (obj);
 +      return (NULL);
 +    }
 +    obj->flags |= UTILS_MATCH_FLAGS_EXCLUDE_REGEX;
 +  }
 +
    obj->callback = callback;
    obj->user_data = user_data;
  
    return (obj);
  } /* cu_match_t *match_create_callback */
  
 -cu_match_t *match_create_simple (const char *regex, int match_ds_type)
 +cu_match_t *match_create_simple (const char *regex,
 +                               const char *excluderegex, int match_ds_type)
  {
    cu_match_value_t *user_data;
    cu_match_t *obj;
    memset (user_data, '\0', sizeof (cu_match_value_t));
    user_data->ds_type = match_ds_type;
  
 -  obj = match_create_callback (regex, default_callback, user_data);
 +  obj = match_create_callback (regex, excluderegex,
 +                             default_callback, user_data);
    if (obj == NULL)
    {
      sfree (user_data);
@@@ -303,17 -286,6 +303,17 @@@ int match_apply (cu_match_t *obj, cons
    if ((obj == NULL) || (str == NULL))
      return (-1);
  
 +  if (obj->flags & UTILS_MATCH_FLAGS_EXCLUDE_REGEX) {
 +    status = regexec (&obj->excluderegex, str,
 +                    STATIC_ARRAY_SIZE (re_match), re_match,
 +                    /* eflags = */ 0);
 +    /* Regex did match, so exclude this line */
 +    if (status == 0) {
 +      DEBUG("ExludeRegex matched, don't count that line\n");
 +      return (0);
 +    }
 +  }
 +
    status = regexec (&obj->regex, str,
        STATIC_ARRAY_SIZE (re_match), re_match,
        /* eflags = */ 0);