Merge branch 'ms/aix'
authorFlorian Forster <octo@leeloo.lan.home.verplant.org>
Tue, 8 Dec 2009 14:43:47 +0000 (15:43 +0100)
committerFlorian Forster <octo@leeloo.lan.home.verplant.org>
Tue, 8 Dec 2009 14:43:47 +0000 (15:43 +0100)
23 files changed:
AUTHORS
README
configure.in
src/Makefile.am
src/collectd-python.pod [new file with mode: 0644]
src/collectd.conf.pod
src/configfile.c
src/configfile.h
src/cpython.h [new file with mode: 0644]
src/disk.c
src/interface.c
src/load.c
src/memory.c
src/openvpn.c
src/plugin.c
src/plugin.h
src/processes.c
src/pyconfig.c [new file with mode: 0644]
src/python.c [new file with mode: 0644]
src/pyvalues.c [new file with mode: 0644]
src/routeros.c [new file with mode: 0644]
src/swap.c
src/types.db

diff --git a/AUTHORS b/AUTHORS
index a259446..4b133fa 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -96,6 +96,7 @@ Manuel Sanmartin
 Marco Chiappero <marco at absence.it>
  - uptime plugin.
  - ip6tables support in the iptables plugin.
+ - openvpn plugin (support for more status file formats)
 
 Michael Stapelberg <michael+git at stapelberg.de>
  - OpenBSD port of the tcpconns plugin.
@@ -165,6 +166,7 @@ Stefan Hacker <stefan.hacker at web.de>
 
 Sven Trenkel <collectd at semidefinite.de>
  - netapp plugin.
+ - python plugin.
 
 Tomasz Pala <gotar at pld-linux.org>
  - conntrack plugin.
diff --git a/README b/README
index d40fd2f..e95bae6 100644 (file)
--- a/README
+++ b/README
@@ -217,6 +217,12 @@ Features
     - protocols
       Counts various aspects of network protocols such as IP, TCP, UDP, etc.
 
+    - python
+      The python plugin implements a Python interpreter into collectd. This
+      makes it possible to write plugins in Python which are executed by
+      collectd without the need to start a heavy interpreter every interval.
+      See collectd-python(5) for details.
+
     - rrdcached
       RRDtool caching daemon (RRDcacheD) statistics.
 
@@ -301,6 +307,10 @@ Features
       you can easily do weird stuff with the plugins we didn't dare think of
       ;) See collectd-perl(5).
 
+    - python
+      It's possible to implement write plugins in Python using the python
+      plugin. See collectd-python(5) for details.
+
     - rrdcached
       Output to round-robin-database (RRD) files using the RRDtool caching
       daemon (RRDcacheD) - see rrdcached(1). That daemon provides a general
@@ -333,6 +343,10 @@ Features
       Log messages are propagated to plugins written in Perl as well.
       See collectd-perl(5).
 
+    - python
+      It's possible to implement log plugins in Python using the python plugin.
+      See collectd-python(5) for details.
+
     - syslog
       Logs to the standard UNIX logging mechanism, syslog.
 
@@ -362,6 +376,10 @@ Features
       Notifications are propagated to plugins written in Perl as well.
       See collectd-perl(5).
 
+    - python
+      It's possible to implement notification plugins in Python using the
+      python plugin. See collectd-python(5) for details.
+
   * Value processing can be controlled using the "filter chain" infrastructure
     and "matches" and "targets". The following plugins are available:
 
@@ -552,6 +570,14 @@ Prerequisites
     The PostgreSQL C client library used by the `postgresql' plugin.
     <http://www.postgresql.org/>
 
+  * libpython (optional)
+    Used by the `python' plugin. Currently, only 2.3 ≦ Python < 3 is supported.
+    <http://www.python.org/>
+
+  * librouteros (optional)
+    Used by the `routeros' plugin to connect to a device running `RouterOS'.
+    <http://verplant.org/librouteros/>
+
   * librrd (optional)
     Used by the `rrdtool' and `rrdcached' plugins. The latter requires RRDtool
     client support which was added after version 1.3 of RRDtool. Versions 1.0,
index 102b5bd..a11e3ba 100644 (file)
@@ -9,6 +9,7 @@ m4_ifdef([LT_PACKAGE_VERSION],
         LT_CONFIG_LTDL_DIR([libltdl])
         LT_INIT([dlopen])
         LTDL_INIT([convenience])
+        AC_DEFINE(LIBTOOL_VERSION, 2, [Define to used libtool version.])
        ]
 ,
        # libtool <= 1.5
@@ -18,6 +19,7 @@ m4_ifdef([LT_PACKAGE_VERSION],
         AC_SUBST(LIBLTDL)
         AC_LIBTOOL_DLOPEN
         AC_CONFIG_SUBDIRS(libltdl)
+        AC_DEFINE(LIBTOOL_VERSION, 1, [Define to used libtool version.])
        ]
 )
 
@@ -2692,6 +2694,184 @@ AC_DEFINE_UNQUOTED(HAVE_LIBPTHREAD, [$collect_pthread],
 AM_CONDITIONAL(BUILD_WITH_LIBPTHREAD, test "x$with_libpthread" = "xyes")
 # }}}
 
+# --with-python {{{
+with_python_prog=""
+with_python_path="$PATH"
+AC_ARG_WITH(python, [AS_HELP_STRING([--with-python@<:@=PREFIX@:>@], [Path to the python interpreter.])],
+[
+ if test "x$withval" = "xyes" || test "x$withval" = "xno"
+ then
+        with_python="$withval"
+ else if test -x "$withval"
+ then
+        with_python_prog="$withval"
+        with_python_path="`dirname \"$withval\"`$PATH_SEPARATOR$with_python_path"
+        with_python="yes"
+ else if test -d "$withval"
+ then
+        with_python_path="$withval$PATH_SEPARATOR$with_python_path"
+        with_python="yes"
+ else
+        AC_MSG_WARN([Argument not recognized: $withval])
+ fi; fi; fi
+], [with_python="yes"])
+
+SAVE_PATH="$PATH"
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+SAVE_LIBS="$LIBS"
+
+PATH="$with_python_path"
+
+if test "x$with_python" = "xyes" && test "x$with_python_prog" = "x"
+then
+       AC_MSG_CHECKING([for python])
+       with_python_prog="`which python 2>/dev/null`"
+       if test "x$with_python_prog" = "x"
+       then
+               AC_MSG_RESULT([not found])
+               with_python="no (interpreter not found)"
+       else
+               AC_MSG_RESULT([$with_python_prog])
+       fi
+fi
+
+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_config_status=$?
+
+       if test "$python_config_status" -ne 0 || test "x$python_include_path" = "x"
+       then
+               AC_MSG_RESULT([failed with status $python_config_status (output: $python_include_path)])
+               with_python="no"
+       else
+               AC_MSG_RESULT([$python_include_path])
+       fi
+fi
+
+if test "x$with_python" = "xyes"
+then
+       CPPFLAGS="-I$python_include_path $CPPFLAGS"
+       AC_CHECK_HEADERS(Python.h,
+                        [with_python="yes"],
+                        [with_python="no ('Python.h' not found)"])
+fi
+
+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_config_status=$?
+
+       if test "$python_config_status" -ne 0 || test "x$python_library_path" = "x"
+       then
+               AC_MSG_RESULT([failed with status $python_config_status (output: $python_library_path)])
+               with_python="no"
+       else
+               AC_MSG_RESULT([$python_library_path])
+       fi
+fi
+
+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_config_status=$?
+
+       if test "$python_config_status" -ne 0 || test "x$python_library_flags" = "x"
+       then
+               AC_MSG_RESULT([failed with status $python_config_status (output: $python_library_flags)])
+               with_python="no"
+       else
+               AC_MSG_RESULT([$python_library_flags])
+       fi
+fi
+
+if test "x$with_python" = "xyes"
+then
+       LDFLAGS="-L$python_library_path $LDFLAGS"
+       LIBS="$python_library_flags $LIBS"
+
+       AC_CHECK_FUNC(PyObject_CallFunction,
+                     [with_python="yes"],
+                     [with_python="no (Symbol 'PyObject_CallFunction' not found)"])
+fi
+
+PATH="$SAVE_PATH"
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+LIBS="$SAVE_LIBS"
+
+if test "x$with_python" = "xyes"
+then
+       BUILD_WITH_PYTHON_CPPFLAGS="-I$python_include_path"
+       BUILD_WITH_PYTHON_LDFLAGS="-L$python_library_path"
+       BUILD_WITH_PYTHON_LIBS="$python_library_flags"
+       AC_SUBST(BUILD_WITH_PYTHON_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_PYTHON_LDFLAGS)
+       AC_SUBST(BUILD_WITH_PYTHON_LIBS)
+fi
+# }}} --with-python
+
+# --with-librouteros {{{
+AC_ARG_WITH(librouteros, [AS_HELP_STRING([--with-librouteros@<:@=PREFIX@:>@], [Path to librouteros.])],
+[
+ if test "x$withval" = "xyes"
+ then
+        with_librouteros="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_librouteros="no"
+ else
+        with_librouteros="yes"
+        LIBROUTEROS_CPPFLAGS="$LIBROUTEROS_CPPFLAGS -I$withval/include"
+        LIBROUTEROS_LDFLAGS="$LIBROUTEROS_LDFLAGS -L$withval/lib"
+ fi; fi
+],
+[with_librouteros="yes"])
+
+SAVE_CPPFLAGS="$CPPFLAGS"
+SAVE_LDFLAGS="$LDFLAGS"
+
+CPPFLAGS="$CPPFLAGS $LIBROUTEROS_CPPFLAGS"
+LDFLAGS="$LDFLAGS $LIBROUTEROS_LDFLAGS"
+
+if test "x$with_librouteros" = "xyes"
+then
+       if test "x$LIBROUTEROS_CPPFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([librouteros CPPFLAGS: $LIBROUTEROS_CPPFLAGS])
+       fi
+       AC_CHECK_HEADERS(routeros_api.h,
+       [with_librouteros="yes"],
+       [with_librouteros="no ('routeros_api.h' not found)"])
+fi
+if test "x$with_librouteros" = "xyes"
+then
+       if test "x$LIBROUTEROS_LDFLAGS" != "x"
+       then
+               AC_MSG_NOTICE([librouteros LDFLAGS: $LIBROUTEROS_LDFLAGS])
+       fi
+       AC_CHECK_LIB(routeros, ros_interface,
+       [with_librouteros="yes"],
+       [with_librouteros="no (symbol 'ros_interface' not found)"])
+fi
+
+CPPFLAGS="$SAVE_CPPFLAGS"
+LDFLAGS="$SAVE_LDFLAGS"
+
+if test "x$with_librouteros" = "xyes"
+then
+       BUILD_WITH_LIBROUTEROS_CPPFLAGS="$LIBROUTEROS_CPPFLAGS"
+       BUILD_WITH_LIBROUTEROS_LDFLAGS="$LIBROUTEROS_LDFLAGS"
+       AC_SUBST(BUILD_WITH_LIBROUTEROS_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBROUTEROS_LDFLAGS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBROUTEROS, test "x$with_librouteros" = "xyes")
+# }}}
+
 # --with-librrd {{{
 # AC_ARG_WITH (package, help-string, [action-if-given], [action-if-not-given])
 librrd_cflags=""
@@ -3953,6 +4133,8 @@ AC_PLUGIN([postgresql],  [$with_libpq],        [PostgreSQL database statistics])
 AC_PLUGIN([powerdns],    [yes],                [PowerDNS statistics])
 AC_PLUGIN([processes],   [$plugin_processes],  [Process statistics])
 AC_PLUGIN([protocols],   [$plugin_protocols],  [Protocol (IP, TCP, ...) statistics])
+AC_PLUGIN([python],      [$with_python],       [Embed a Python interpreter])
+AC_PLUGIN([routeros],    [$with_librouteros],  [RouterOS plugin])
 AC_PLUGIN([rrdcached],   [$librrd_rrdc_update], [RRDTool output plugin])
 AC_PLUGIN([rrdtool],     [$with_librrd],       [RRDTool output plugin])
 AC_PLUGIN([sensors],     [$with_libsensors],   [lm_sensors statistics])
@@ -4176,6 +4358,7 @@ Configuration:
     libperl . . . . . . . $with_libperl
     libpq . . . . . . . . $with_libpq
     libpthread  . . . . . $with_libpthread
+    librouteros . . . . . $with_librouteros
     librrd  . . . . . . . $with_librrd
     libsensors  . . . . . $with_libsensors
     libstatgrab . . . . . $with_libstatgrab
@@ -4186,6 +4369,7 @@ Configuration:
     libxmms . . . . . . . $with_libxmms
     libyajl . . . . . . . $with_libyajl
     oracle  . . . . . . . $with_oracle
+    python  . . . . . . . $with_python
 
   Features:
     daemon mode . . . . . $enable_daemon
@@ -4259,6 +4443,8 @@ Configuration:
     powerdns  . . . . . . $enable_powerdns
     processes . . . . . . $enable_processes
     protocols . . . . . . $enable_protocols
+    python  . . . . . . . $enable_python
+    routeros  . . . . . . $enable_routeros
     rrdcached . . . . . . $enable_rrdcached
     rrdtool . . . . . . . $enable_rrdtool
     sensors . . . . . . . $enable_sensors
index 7798c75..d928311 100644 (file)
@@ -813,6 +813,16 @@ collectd_LDADD += "-dlopen" powerdns.la
 collectd_DEPENDENCIES += powerdns.la
 endif
 
+if BUILD_PLUGIN_PYTHON
+pkglib_LTLIBRARIES += python.la
+python_la_SOURCES = python.c pyconfig.c pyvalues.c cpython.h
+python_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_PYTHON_CPPFLAGS)
+python_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_PYTHON_LDFLAGS)
+python_la_LIBADD = $(BUILD_WITH_PYTHON_LIBS)
+collectd_LDADD += "-dlopen" python.la
+collectd_DEPENDENCIES += python.la
+endif
+
 if BUILD_PLUGIN_PROCESSES
 pkglib_LTLIBRARIES += processes.la
 processes_la_SOURCES = processes.c
@@ -833,6 +843,16 @@ collectd_LDADD += "-dlopen" protocols.la
 collectd_DEPENDENCIES += protocols.la
 endif
 
+if BUILD_PLUGIN_ROUTEROS
+pkglib_LTLIBRARIES += routeros.la
+routeros_la_SOURCES = routeros.c
+routeros_la_CPPFLAGS = $(BUILD_WITH_LIBROUTEROS_CPPFLAGS)
+routeros_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBROUTEROS_LDFLAGS)
+routeros_la_LIBADD = -lrouteros
+collectd_LDADD += "-dlopen" routeros.la
+collectd_DEPENDENCIES += routeros.la
+endif
+
 if BUILD_PLUGIN_RRDCACHED
 pkglib_LTLIBRARIES += rrdcached.la
 rrdcached_la_SOURCES = rrdcached.c utils_rrdcreate.c utils_rrdcreate.h
@@ -1149,6 +1169,7 @@ dist_man_MANS = collectd.1 \
                collectdmon.1 \
                collectd-nagios.1 \
                collectd-perl.5 \
+               collectd-python.5 \
                collectd-snmp.5 \
                collectd-unixsock.5 \
                types.db.5
@@ -1164,6 +1185,7 @@ EXTRA_DIST +=   collectd.conf.pod \
                collectdmon.pod \
                collectd-nagios.pod \
                collectd-perl.pod \
+               collectd-python.pod \
                collectd.pod \
                collectd-snmp.pod \
                collectd-unixsock.pod \
diff --git a/src/collectd-python.pod b/src/collectd-python.pod
new file mode 100644 (file)
index 0000000..ac679c5
--- /dev/null
@@ -0,0 +1,653 @@
+=head1 NAME
+
+collectd-python - Documentation of collectd's C<python plugin>
+
+=head1 SYNOPSIS
+
+  <LoadPlugin python>
+    Globals true
+  </LoadPlugin>
+  # ...
+  <Plugin python>
+    ModulePath "/path/to/your/python/modules"
+    LogTraces true
+    Interactive true
+    Import "spam"
+
+    <Module spam>
+      spam "wonderful" "lovely"
+    </Module>
+  </Plugin>
+
+=head1 DESCRIPTION
+
+The C<python plugin> embeds a Python-interpreter into collectd and provides an
+interface to collectd's plugin system. This makes it possible to write plugins
+for collectd in Python. This is a lot more efficient than executing a
+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.
+
+=head1 CONFIGURATION
+
+=over 4
+
+=item B<LoadPlugin> I<Plugin>
+
+Loads the Python plugin I<Plugin>. Unlike most other LoadPlugin lines, this one
+should be a block containing the line "Globals true". This will cause collectd
+to export the name of all objects in the python interpreter for all plugins to
+see. If you don't do this or your platform does not support it, the embeded
+interpreter will start anywa but you won't be able to load certain python
+modules, e.g. "time".
+
+=item B<Encoding> I<Name>
+
+The default encoding for Unicode objects you pass to collectd. If you omit this
+option it will default to B<ascii> on I<Python 2> and B<utf-8> on I<Python 3>.
+This is hardcoded in Python and will ignore everything else, including your
+locale.
+
+=item B<ModulePath> I<Name>
+
+Appends I<Name> to B<sys.path>. You won't be able to import any scripts you
+wrote unless they are located in one of the directories in this list. Please
+note that it only has effect on plugins loaded after this option.
+
+=item B<LogTraces> I<bool>
+
+If a python script throws an exception it will be logged by collectd with the
+name of the exception and the message. If you set this option to true it will
+also log the full stacktrace just like the default output of an interactive
+python interpreter. This should probably be set to false most of the time but
+is very useful for development and debugging of new modules.
+
+=item B<Interactive> I<bool>
+
+This option will cause the module to launch an interactive python interpreter
+that reads from and writes to the terminal. Note that collectd will terminate
+right after starting up if you try to run it as a daemon while this option is
+enabled to make sure to start collectd with the B<-f> option.
+
+The B<collectd> module is I<not> imported into the interpreter's globals. You
+have to do it manually. Be sure to read the help text of the module, it can be
+used as a reference guide during coding.
+
+This interactive session will behave slightly differently from a daemonized
+collectd script as well as from a normal python interpreter:
+
+=over 4
+
+=item
+
+B<1.> collectd will try to import the B<readline> module to give you a decent
+way of entering your commands. The daemonized collectd won't do that.
+
+=item 
+
+B<2.> collectd will block I<SIGINT>. Pressing I<Ctrl+C> will usually cause
+collectd to shut down. This would be problematic in an interactive session,
+therefore this signal will be blocked. You can still use it to interrupt
+syscalls like sleep and pause but it won't generate a I<KeyboardInterrupt>
+exception either.
+
+To quit collectd send I<EOF> (press I<Ctrl+D> at the beginning of a new line).
+
+=back
+
+=item E<lt>B<Module> I<Name>E<gt> block
+
+This block may be used to pass on configuration settings to a Python module.
+The configuration is converted into an instance of the B<Config> class which is
+passed to the registered configuration callback. See below for details about
+the B<Config> class and how to register callbacks.
+
+The I<name> identifies the callback.
+
+=back
+
+=head1 WRITING YOUR OWN PLUGINS
+
+Writing your own plugins is quite simple. collectd manages plugins by means of
+B<dispatch functions> which call the appropriate B<callback functions>
+registered by the plugins. Any plugin basically consists of the implementation
+of these callback functions and initializing code which registers the
+functions with collectd. See the section "EXAMPLES" below for a really basic
+example. The following types of B<callback functions> are known to collectd
+(all of them are optional):
+
+=over 4
+
+=item configuration functions
+
+This type of functions is called during configuration if an appropriate
+B<Module> block has been encountered. It is called once for each B<Module>
+block which matches the name of the callback as provided with the
+B<register_config> method - see below.
+
+Python thread support has not been initialized at this point so do not use any
+threading functions here!
+
+=item init functions
+
+This type of functions is called once after loading the module and before any
+calls to the read and write functions. It should be used to initialize the
+internal state of the plugin (e.E<nbsp>g. open sockets, ...). This is the
+earliest point where you may use threads.
+
+=item read functions
+
+This type of function is used to collect the actual data. It is called once
+per interval (see the B<Interval> configuration option of collectd). Usually
+it will call B<plugin_dispatch_values> to dispatch the values to collectd
+which will pass them on to all registered B<write functions>. If this function
+throws any kind of exception the plugin will be skipped for an increasing
+amount of time until it returns normally again.
+
+=item write functions
+
+This type of function is used to write the dispatched values. It is called
+once for every value that was dispatched by any plugin.
+
+=item flush functions
+
+This type of function is used to flush internal caches of plugins. It is
+usually triggered by the user only. Any plugin which caches data before
+writing it to disk should provide this kind of callback function.
+
+=item log functions
+
+This type of function is used to pass messages of plugins or the daemon itself
+to the user.
+
+=item notification function
+
+This type of function is used to act upon notifications. In general, a
+notification is a status message that may be associated with a data instance.
+Usually, a notification is generated by the daemon if a configured threshold
+has been exceeded (see the section "THRESHOLD CONFIGURATION" in
+L<collectd.conf(5)> for more details), but any plugin may dispatch
+notifications as well.
+
+=item shutdown functions
+
+This type of function is called once before the daemon shuts down. It should
+be used to clean up the plugin (e.g. close sockets, ...).
+
+=back
+
+Any function (except log functions) may set throw an exception in case of any
+errors. The exception will be passed on to the user using collectd's logging
+mechanism. If a log callback throws an exception it will be printed to standard
+error instead.
+
+See the documentation of the various B<register_> methods in the section
+"FUNCTIONS" below for the number and types of arguments passed to each
+B<callback function>. This section also explains how to register B<callback
+functions> with collectd.
+
+To enable a module, copy it to a place where Python can find it (i.E<nbsp>e. a
+directory listed in B<sys.path>) just as any other Python plugin and add
+an appropriate B<Import> option to the configuration file. After restarting
+collectd you're done.
+
+=head1 CLASSES
+
+The following complex types are used to pass values between the Python plugin
+and collectd:
+
+=head2 Config
+
+The Config class is an object which keeps the informations provided in the
+configuration file. The sequence of children keeps one entry for each
+configuration option. Each such entry is another Config instance, which
+may nest further if nested blocks are used.
+
+ class Config(object)
+
+This represents a piece of collectd's config file. It is passed to scripts with
+config callbacks (see B<register_config>) and is of little use if created
+somewhere else.
+
+It has no methods beyond the bare minimum and only exists for its data members.
+
+Data descriptors defined here:
+
+=over 4
+=item parent
+
+This represents the parent of this node. On the root node
+of the config tree it will be None.
+=item key
+
+This is the keyword of this item, i.e. the first word of any given line in the
+config file. It will always be a string.
+=item values
+
+This is a tuple (which might be empty) of all value, i.e. words following the
+keyword in any given line in the config file.
+
+Every item in this tuple will be either a string or a float or a boolean,
+depending on the contents of the configuration file.
+=item children
+
+This is a tuple of child nodes. For most nodes this will be empty. If this node
+represents a block instead of a single line of the config file it will contain
+all nodes in this block.
+
+=back
+
+=head2 PluginData
+
+This should not be used directly but it is the base class for both Values and
+Notification. It is used to identify the source of a value or notification.
+
+ class PluginData(object)
+
+This is an internal class that is the base for Values and Notification. It is
+pretty useless by itself and was therefore not exported to the collectd module.
+
+Data descriptors defined here:
+
+=over 4
+
+=item host
+
+The hostname of the host this value was read from. For dispatching this can be
+set to an empty string which means the local hostname as defined in
+collectd.conf.
+
+=item plugin
+
+The name of the plugin that read the data. Setting this member to an empty
+string will insert "python" upon dispatching.
+
+=item plugin_instance
+
+Plugin instance string. May be empty.
+
+=item time
+
+This is the Unix timestamp of the time this value was read. For dispatching
+values this can be set to zero which means "now". This means the time the value
+is actually dispatched, not the time it was set to 0.
+
+=item type
+
+The type of this value. This type has to be defined in your I<types.db>.
+Attempting to set it to any other value will raise a I<TypeError> exception.
+Assigning a type is mandatory, calling dispatch without doing so will raise a
+I<RuntimeError> exception.
+
+=item type_instance
+
+Type instance string. May be empty.
+
+=back
+
+=head2 Values
+
+A Value is an object which features a sequence of values. It is based on then
+I<PluginData> type and uses its members to identify the values.
+
+ class Values(PluginData)
+
+A Values object used for dispatching values to collectd and receiving values
+from write callbacks.
+
+Method resolution order:
+
+=over 4
+
+=item Values
+
+=item PluginData
+
+=item object
+
+=back
+
+Methods defined here:
+
+=over 4
+
+=item B<dispatch>([type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.
+    
+Dispatch this instance to the collectd process. The object has members for each
+of the possible arguments for this method. For a detailed explanation of these
+parameters see the member of the same same.
+
+If you do not submit a parameter the value saved in its member will be
+submitted. If you do provide a parameter it will be used instead, without
+altering the member.
+
+=item B<write>([destination][, type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.
+
+Write this instance to a single plugin or all plugins if "destination" is
+omitted. This will bypass the main collectd process and all filtering and
+caching. Other than that it works similar to "dispatch". In most cases
+"dispatch" should be used instead of "write".
+
+=back
+
+Data descriptors defined here:
+
+=over 4
+
+=item interval
+
+The interval is the timespan in seconds between two submits for the same data
+source. This value has to be a positive integer, so you can't submit more than
+one value per second. If this member is set to a non-positive value, the
+default value as specified in the config file will be used (default: 10).
+    
+If you submit values more often than the specified interval, the average will
+be used. If you submit less values, your graphs will have gaps.
+
+=item values
+
+These are the actual values that get dispatched to collectd. It has to be a
+sequence (a tuple or list) of numbers. The size of the sequence and the type of
+its content depend on the type member your I<types.db> file. For more
+information on this read the L<types.db(5)> manual page.
+
+If the sequence does not have the correct size upon dispatch a I<RuntimeError>
+exception will be raised. If the content of the sequence is not a number, a
+I<TypeError> exception will be raised.
+
+=back
+
+=head2 Notification
+
+A notification is an object defining the severity and message of the status
+message as well as an identification of a data instance by means of the members
+of I<PluginData> on which it is based.
+
+class Notification(PluginData)
+The Notification class is a wrapper around the collectd notification.
+It can be used to notify other plugins about bad stuff happening. It works
+similar to Values but has a severity and a message instead of interval
+and time.
+Notifications can be dispatched at any time and can be received with
+register_notification.
+
+Method resolution order:
+
+=over 4
+
+=item Notification
+
+=item PluginData
+
+=item object
+
+=back
+
+Methods defined here:
+
+=over 4
+
+=item B<dispatch>([type][, values][, plugin_instance][, type_instance][, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.
+    
+Dispatch this instance to the collectd process. The object has members for each
+of the possible arguments for this method. For a detailed explanation of these
+parameters see the member of the same same.
+    
+If you do not submit a parameter the value saved in its member will be
+submitted. If you do provide a parameter it will be used instead, without
+altering the member.
+
+=back
+
+Data descriptors defined here:
+
+=over 4
+
+=item message
+
+Some kind of description what's going on and why this Notification was
+generated.
+
+=item severity
+
+The severity of this notification. Assign or compare to I<NOTIF_FAILURE>,
+I<NOTIF_WARNING> or I<NOTIF_OKAY>.
+
+=back
+
+=head1 FUNCTIONS
+
+The following functions provide the C-interface to Python-modules.
+
+=over 4
+
+=item B<register_*>(I<callback>[, I<data>][, I<name>]) -> identifier
+
+There are eight different register functions to get callback for eight
+different events. With one exception all of them are called as shown above.
+
+=over 4
+
+=item
+
+I<callback> is a callable object that will be called every time the event is
+triggered.
+
+=item
+
+I<data> is an optional object that will be passed back to the callback function
+every time it is called. If you omit this parameter no object is passed back to
+your callback, not even None.
+
+=item
+
+I<name> is an optional identifier for this callback. The default name is
+B<python>.I<module>. I<module> is taken from the B<__module__> attribute of
+your callback function. Every callback needs a unique identifier, so if you
+want to register the same callback multiple time in the same module you need to
+specify a name here. Otherwise it's save to ignore this parameter I<identifier>
+is the full identifier assigned to this callback.
+
+=back
+
+These functions are called in the various stages of the daemon (see the section
+L<"WRITING YOUR OWN PLUGINS"> above) and are passed the following arguments:
+
+=over 4
+
+=item register_config
+
+The only argument passed is a I<Config> object. See above for the layout of this
+data type.
+Note that you can not receive the whole config files this way, only B<Module>
+blocks inside the Python configuration block. Additionally you will only
+receive blocks where your callback identifier matches B<python.>I<blockname>.
+
+=item register_init
+
+The callback will be called without arguments.
+
+=item register_read(callback[, interval][, data][, name]) -> identifier
+
+This function takes an additional parameter: I<interval>. It specifies the
+time between calls to the callback function.
+
+The callback will be called without arguments.
+
+=item register_shutdown
+
+The callback will be called without arguments.
+
+=item register_write
+
+The callback function will be called with one arguments passed, which will be a
+I<Values> object. For the layout of I<Values> see above.
+If this callback function throws an exception the next call will be delayed by
+an increasing interval.
+
+=item register_flush
+
+Like B<register_config> is important for this callback because it determines
+what flush requests the plugin will receive.
+
+The arguments passed are I<timeout> and I<identifier>. I<timeout> indicates
+that only data older than I<timeout> seconds is to be flushed. I<identifier>
+specifies which values are to be flushed.
+
+=item register_log
+
+The arguments are I<severity> and I<message>. The severity is an integer and
+small for important messages and high for less important messages. The least
+important level is B<LOG_DEBUG>, the most important level is B<LOG_ERR>. In
+between there are (from least to most important): B<LOG_INFO>, B<LOG_NOTICE>,
+and B<LOG_WARNING>. I<message> is simply a string B<without> a newline at the
+end.
+
+If this callback throws an exception it will B<not> be logged. It will just be
+printed to B<sys.stderr> which usually means silently ignored.
+
+=item register_notification
+
+The only argument passed is a I<Notification> object. See above for the layout of this
+data type.
+
+=back
+
+=item B<unregister_*>(I<identifier>) -> None
+
+Removes a callback or data-set from collectd's internal list of callback
+functions. Every I<register_*> function has an I<unregister_*> function.
+I<identifier> is either the string that was returned by the register function
+or a callback function. The identifier will be constructed in the same way as
+for the register functions.
+
+=item B<flush>(I<plugin[, I<timeout>][, I<identifier>]) -> None
+
+Flush one or all plugins. I<timeout> and the specified I<identifiers> are
+passed on to the registered flush-callbacks. If omitted, the timeout defaults
+to C<-1>. The identifier defaults to None. If the B<plugin> argument has been
+specified, only named plugin will be flushed.
+
+=item B<error>, B<warning>, B<notice>, B<info>, B<debug>(I<message>)
+
+Log a message with the specified severity.
+
+=back
+
+=head1 EXAMPLES
+
+Any Python module will start similar to:
+
+  import collectd
+
+A very simple read function might look like:
+
+  def read(data=None):
+    vl = collectd.Values(type='gauge')
+    vl.plugin='python.spam'
+    vl.dispatch(values=[random.random() * 100])
+
+A very simple write function might look like:
+
+  def write(vl, data=None):
+    for i in vl.values:
+      print "%s (%s): %f" % (vl.plugin, vl.type, i)
+
+To register those functions with collectd:
+
+  collectd.register_read(read);
+  collectd.register_write(write);
+
+See the section L<"CLASSES"> above for a complete documentation of the data
+types used by the read, write and match functions.
+
+=head1 NOTES
+
+=over 4
+
+=item
+
+Please feel free to send in new plugins to collectd's mailinglist at
+E<lt>collectdE<nbsp>atE<nbsp>verplant.orgE<gt> for review and, possibly,
+inclusion in the main distribution. In the latter case, we will take care of
+keeping the plugin up to date and adapting it to new versions of collectd.
+
+Before submitting your plugin, please take a look at
+L<http://collectd.org/dev-info.shtml>.
+
+=back
+
+=head1 CAVEATS
+
+=over 4
+
+=item
+
+collectd is heavily multi-threaded. Each collectd thread accessing the python
+plugin will be mapped to a Python interpreter thread. Any such thread will be
+created and destroyed transparently and on-the-fly.
+
+Hence, any plugin has to be thread-safe if it provides several entry points
+from collectd (i.E<nbsp>e. if it registers more than one callback or if a
+registered callback may be called more than once in parallel).
+
+=item
+
+The Python thread module is initialized just before calling the init callbacks.
+This means you must not use Python's threading module prior to this point. This
+includes all config and possibly other callback as well.
+
+=item
+
+The python plugin exports the internal API of collectd which is considered
+unstable and subject to change at any time. We try hard to not break backwards
+compatibility in the Python API during the life cycle of one major release.
+However, this cannot be guaranteed at all times. Watch out for warnings
+dispatched by the python plugin after upgrades.
+
+=back
+
+=head1 KNOWN BUGS
+
+=over 4
+
+=item
+
+This plugin is not compatible with python3. Trying to compile it with python3
+will fail because of the ways string, unicode and bytearray bahavior 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.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-perl(5)>,
+L<collectd-exec(5)>,
+L<types.db(5)>,
+L<python(1)>,
+
+=head1 AUTHOR
+
+The C<python plugin> has been written by
+Sven Trenkel E<lt>collectdE<nbsp>atE<nbsp>semidefinite.deE<gt>.
+
+This manpage has been written by Sven Trenkel
+E<lt>collectdE<nbsp>atE<nbsp>semidefinite.deE<gt>.
+It is based on the L<collectd-perl(5)> manual page by
+Florian Forster E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt> and
+Sebastian Harl E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
+
+=cut
index 6bae339..d19a869 100644 (file)
@@ -2650,6 +2650,12 @@ Available options:
 
 Specifies the location of the status file.
 
+=item B<Compression> B<true>|B<false>
+
+Sets whether or not statistics about the compression used by OpenVPN should be
+collected. This information is only available in I<single> mode. Enabled by
+default.
+
 =back
 
 =head2 Plugin C<oracle>
@@ -3281,6 +3287,71 @@ matching values will be ignored.
 
 =back
 
+=head2 Plugin C<python>
+
+This plugin embeds a Python-interpreter into collectd and provides an interface
+to collectd's plugin system. See L<collectd-python(5)> for its documentation.
+
+=head2 Plugin C<routeros>
+
+The C<routeros> plugin connects to a device running I<RouterOS>, the
+Linux-based operating system for routers by I<MikroTik>. The plugin uses
+I<librouteros> to connect and reads information about the interfaces and
+wireless connections of the device. The configuration supports querying
+multiple routers:
+
+  <Plugin "routeros">
+    <Router>
+      Host "router0.example.com"
+      User "collectd"
+      Password "secr3t"
+      CollectInterface true
+    </Router>
+    <Router>
+      Host "router1.example.com"
+      User "collectd"
+      Password "5ecret"
+      CollectInterface true
+      CollectRegistrationTable true
+    </Router>
+  </Plugin>
+
+As you can see above, the configuration of the I<routeros> plugin consists of
+one or more B<E<lt>RouterE<gt>> blocks. Within each block, the following
+options are understood:
+
+=over 4
+
+=item B<Host> I<Host>
+
+Hostname or IP-address of the router to connect to.
+
+=item B<Port> I<Port>
+
+Port name or port number used when connecting. If left unspecified, the default
+will be chosen by I<librouteros>, currently "8728". This option expects a
+string argument, even when a numeric port number is given.
+
+=item B<User> I<User>
+
+Use the user name I<User> to authenticate. Defaults to "admin".
+
+=item B<Password> I<Password>
+
+Set the password used to authenticate.
+
+=item B<CollectInterface> B<true>|B<false>
+
+When set to B<true>, interface statistics will be collected for all interfaces
+present on the device. Defaults to B<false>.
+
+=item B<CollectRegistrationTable> B<true>|B<false>
+
+When set to B<true>, information about wireless LAN connections will be
+collected. Defaults to B<false>.
+
+=back
+
 =head2 Plugin C<rrdcached>
 
 The C<rrdcached> plugin uses the RRDtool accelerator daemon, L<rrdcached(1)>,
index 1a957f6..b2997d6 100644 (file)
@@ -75,7 +75,7 @@ typedef struct cf_global_option_s
  */
 static int dispatch_value_typesdb (const oconfig_item_t *ci);
 static int dispatch_value_plugindir (const oconfig_item_t *ci);
-static int dispatch_value_loadplugin (const oconfig_item_t *ci);
+static int dispatch_loadplugin (const oconfig_item_t *ci);
 
 /*
  * Private variables
@@ -87,7 +87,7 @@ static cf_value_map_t cf_value_map[] =
 {
        {"TypesDB",    dispatch_value_typesdb},
        {"PluginDir",  dispatch_value_plugindir},
-       {"LoadPlugin", dispatch_value_loadplugin}
+       {"LoadPlugin", dispatch_loadplugin}
 };
 static int cf_value_map_num = STATIC_ARRAY_LEN (cf_value_map);
 
@@ -239,8 +239,10 @@ static int dispatch_value_plugindir (const oconfig_item_t *ci)
        return (0);
 }
 
-static int dispatch_value_loadplugin (const oconfig_item_t *ci)
+static int dispatch_loadplugin (const oconfig_item_t *ci)
 {
+       int i;
+       uint32_t flags = 0;
        assert (strcasecmp (ci->key, "LoadPlugin") == 0);
 
        if (ci->values_num != 1)
@@ -248,7 +250,19 @@ static int dispatch_value_loadplugin (const oconfig_item_t *ci)
        if (ci->values[0].type != OCONFIG_TYPE_STRING)
                return (-1);
 
-       return (plugin_load (ci->values[0].value.string));
+       for (i = 0; i < ci->children_num; ++i) {
+               if (ci->children[i].values_num != 1 ||
+                               ci->children[i].values[0].type != OCONFIG_TYPE_BOOLEAN) {
+                       WARNING("Ignoring unknown LoadPlugin option %s for plugin %s", ci->children[i].key, ci->values[0].value.string);
+                       continue;
+               }
+               if (strcasecmp(ci->children[i].key, "globals") == 0) {
+                       flags |= PLUGIN_FLAGS_GLOBAL;
+               } else {
+                       WARNING("Ignoring unknown LoadPlugin option %s for plugin %s", ci->children[i].key, ci->values[0].value.string);
+               }
+       }
+       return (plugin_load (ci->values[0].value.string, flags));
 } /* int dispatch_value_loadplugin */
 
 static int dispatch_value_plugin (const char *plugin, oconfig_item_t *ci)
@@ -353,7 +367,9 @@ static int dispatch_block_plugin (oconfig_item_t *ci)
 
 static int dispatch_block (oconfig_item_t *ci)
 {
-       if (strcasecmp (ci->key, "Plugin") == 0)
+       if (strcasecmp (ci->key, "LoadPlugin") == 0)
+               return (dispatch_loadplugin (ci));
+       else if (strcasecmp (ci->key, "Plugin") == 0)
                return (dispatch_block_plugin (ci));
        else if (strcasecmp (ci->key, "Threshold") == 0)
                return (ut_config (ci));
@@ -927,7 +943,7 @@ int cf_util_get_string (const oconfig_item_t *ci, char **ret_string) /* {{{ */
 
        if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
        {
-               ERROR ("cf_util_get_string: The %s plugin requires "
+               ERROR ("cf_util_get_string: The %s option requires "
                                "exactly one string argument.", ci->key);
                return (-1);
        }
@@ -943,6 +959,23 @@ int cf_util_get_string (const oconfig_item_t *ci, char **ret_string) /* {{{ */
        return (0);
 } /* }}} int cf_util_get_string */
 
+int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool) /* {{{ */
+{
+       if ((ci == NULL) || (ret_bool == NULL))
+               return (EINVAL);
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+       {
+               ERROR ("cf_util_get_boolean: The %s option requires "
+                               "exactly one string argument.", ci->key);
+               return (-1);
+       }
+
+       *ret_bool = ci->values[0].value.boolean ? true : false;
+
+       return (0);
+} /* }}} int cf_util_get_boolean */
+
 /* Assures that the config option is a string. The string is then converted to
  * a port number using `service_name_to_port_number' and returned. Returns the
  * port number in the range [1-65535] or less than zero upon failure. */
@@ -950,7 +983,7 @@ int cf_util_get_port_number (const oconfig_item_t *ci) /* {{{ */
 {
        if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
        {
-               ERROR ("cf_util_get_port_number: The %s plugin requires "
+               ERROR ("cf_util_get_port_number: The %s option requires "
                                "exactly one string argument.", ci->key);
                return (-1);
        }
index 74d074e..a73def2 100644 (file)
@@ -91,6 +91,10 @@ const char *global_option_get (const char *option);
  * success. */
 int cf_util_get_string (const oconfig_item_t *ci, char **ret_string);
 
+/* Assures the config option is a boolean and assignes it to `ret_bool'.
+ * Otherwise, `ret_bool' is not changed and non-zero is returned. */
+int cf_util_get_boolean (const oconfig_item_t *ci, _Bool *ret_bool);
+
 /* Assures that the config option is a string. The string is then converted to
  * a port number using `service_name_to_port_number' and returned. Returns the
  * port number in the range [1-65535] or less than zero upon failure. */
diff --git a/src/cpython.h b/src/cpython.h
new file mode 100644 (file)
index 0000000..661bf6a
--- /dev/null
@@ -0,0 +1,113 @@
+/**
+ * collectd - src/cpython.h
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+/* These two macros are basicly Py_BEGIN_ALLOW_THREADS and Py_BEGIN_ALLOW_THREADS
+ * from the other direction. If a Python thread calls a C function
+ * Py_BEGIN_ALLOW_THREADS is used to allow other python threads to run because
+ * we don't intend to call any Python functions.
+ *
+ * These two macros are used whenever a C thread intends to call some Python
+ * function, usually because some registered callback was triggered.
+ * Just like Py_BEGIN_ALLOW_THREADS it opens a block so these macros have to be
+ * used in pairs. They aquire the GIL, create a new Python thread state and swap
+ * the current thread state with the new one. This means this thread is now allowed
+ * to execute Python code. */
+
+#define CPY_LOCK_THREADS {\
+       PyGILState_STATE gil_state;\
+       gil_state = PyGILState_Ensure();
+
+#define CPY_RETURN_FROM_THREADS \
+       PyGILState_Release(gil_state);\
+       return
+
+#define CPY_RELEASE_THREADS \
+       PyGILState_Release(gil_state);\
+}
+
+/* Python 2.4 has this macro, older versions do not. */
+#ifndef Py_VISIT
+#define Py_VISIT(o) do {\
+       int _vret;\
+       if ((o) != NULL) {\
+               _vret = visit((o), arg);\
+               if (_vret != 0)\
+               return _vret;\
+       }\
+} while (0)
+#endif
+
+/* Python 2.4 has this macro, older versions do not. */
+#ifndef Py_CLEAR
+#define Py_CLEAR(o) do {\
+       PyObject *tmp = o;\
+       (o) = NULL;\
+       Py_XDECREF(tmp);\
+} while (0)
+#endif
+
+/* Python 2.4 has this macro, older versions do not. */
+#ifndef Py_RETURN_NONE
+# define Py_RETURN_NONE return Py_INCREF(Py_None), Py_None
+#endif
+
+typedef struct {
+       PyObject_HEAD        /* No semicolon! */
+       PyObject *parent;    /* Config */
+       PyObject *key;       /* String */
+       PyObject *values;    /* Sequence */
+       PyObject *children;  /* Sequence */
+} Config;
+
+PyTypeObject ConfigType;
+
+typedef struct {
+       PyObject_HEAD        /* No semicolon! */
+       double time;
+       char host[DATA_MAX_NAME_LEN];
+       char plugin[DATA_MAX_NAME_LEN];
+       char plugin_instance[DATA_MAX_NAME_LEN];
+       char type[DATA_MAX_NAME_LEN];
+       char type_instance[DATA_MAX_NAME_LEN];
+} PluginData;
+
+PyTypeObject PluginDataType;
+
+typedef struct {
+       PluginData data;
+       PyObject *values;    /* Sequence */
+       int interval;
+} Values;
+
+PyTypeObject ValuesType;
+
+typedef struct {
+       PluginData data;
+       int severity;
+       char message[NOTIF_MAX_MSG_LEN];
+} Notification;
+
+PyTypeObject NotificationType;
index 65ac90e..0a90899 100644 (file)
@@ -164,7 +164,7 @@ static int disk_init (void)
 {
 #if HAVE_IOKIT_IOKITLIB_H
        kern_return_t status;
-       
+
        if (io_master_port != MACH_PORT_NULL)
        {
                mach_port_deallocate (mach_task_self (),
index 3e5a3d0..1ba6c8c 100644 (file)
@@ -235,7 +235,7 @@ static int interface_read (void)
        char buffer[1024];
        unsigned long long incoming, outgoing;
        char *device;
-       
+
        char *dummy;
        char *fields[16];
        int numfields;
@@ -261,7 +261,7 @@ static int interface_read (void)
 
                if (device[0] == '\0')
                        continue;
-               
+
                numfields = strsplit (dummy, fields, 16);
 
                if (numfields < 11)
index 76d6052..0188da7 100644 (file)
@@ -89,7 +89,7 @@ static int load_read (void)
 
        char *fields[8];
        int numfields;
-       
+
        if ((loadavg = fopen ("/proc/loadavg", "r")) == NULL)
        {
                char errbuf[1024];
index fa622f9..ecaa0c0 100644 (file)
@@ -263,7 +263,7 @@ static int memory_read (void)
 #elif KERNEL_LINUX
        FILE *fh;
        char buffer[1024];
-       
+
        char *fields[8];
        int numfields;
 
index 9f386f0..1ac5083 100644 (file)
@@ -2,6 +2,7 @@
  * collectd - src/openvpn.c
  * Copyright (C) 2008  Doug MacEachern
  * Copyright (C) 2009  Florian octo Forster
+ * Copyright (C) 2009  Marco Chiappero
  *
  * 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
  * Authors:
  *   Doug MacEachern <dougm at hyperic.com>
  *   Florian octo Forster <octo at verplant.org>
+ *   Marco Chiappero <marco at absence.it>
  **/
 
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
 
-#define DEFAULT_STATUS_FILE "/etc/openvpn/openvpn-status.log"
-#define CLIENT_LIST_PREFIX  "CLIENT_LIST,"
+#define V1STRING "Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since\n"
+#define V2STRING "HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t)\n"
+#define V3STRING "HEADER CLIENT_LIST Common Name Real Address Virtual Address Bytes Received Bytes Sent Connected Since Connected Since (time_t)\n"
+#define VSSTRING "OpenVPN STATISTICS\n"
 
-static char *status_file = NULL;
 
-/* For compression stats we need to convert these counters to a rate. */
-static counter_t pre_compress_old    = 0;
-static counter_t post_compress_old   = 0;
-static counter_t pre_decompress_old  = 0;
-static counter_t post_decompress_old = 0;
-static int compression_counter_valid = 0;
+struct vpn_status_s
+{
+       char *file;
+       enum
+       {
+               MULTI1 = 1, /* status-version 1 */
+               MULTI2,     /* status-version 2 */
+               MULTI3,     /* status-version 3 */
+               SINGLE = 10 /* currently no versions for single mode, maybe in the future */
+       } version;
+       char *name;
+};
+typedef struct vpn_status_s vpn_status_t;
+
+static vpn_status_t **vpn_list = NULL;
+static int vpn_num = 0;
+
+static int store_compression = 1;
+static int new_naming_schema = 0;
 
 static const char *config_keys[] =
 {
-       "StatusFile"
+       "StatusFile",
+       "Compression",
+       "ImprovedNamingSchema"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
-/* copy-n-pasted from common.c - changed delim to "," */
+
+/* Helper function
+ * copy-n-pasted from common.c - changed delim to ","  */
 static int openvpn_strsplit (char *string, char **fields, size_t size)
 {
-        size_t i;
-        char *ptr;
-        char *saveptr;
-
-        i = 0;
-        ptr = string;
-        saveptr = NULL;
-        while ((fields[i] = strtok_r (ptr, ",", &saveptr)) != NULL)
-        {
-                ptr = NULL;
-                i++;
-
-                if (i >= size)
-                        break;
-        }
-
-        return (i);
+       size_t i;
+       char *ptr;
+       char *saveptr;
+
+       i = 0;
+       ptr = string;
+       saveptr = NULL;
+       while ((fields[i] = strtok_r (ptr, ",", &saveptr)) != NULL)
+       {
+               ptr = NULL;
+               i++;
+
+               if (i >= size)
+                       break;
+       }
+
+       return (i);
 } /* int openvpn_strsplit */
 
-static void openvpn_submit (char *name, counter_t rx, counter_t tx)
+/* dispatches stats about traffic (TCP or UDP) generated by the tunnel per single endpoint */
+static void iostats_submit (char *pinst, char *tinst, counter_t rx, counter_t tx)
 {
        value_t values[2];
        value_list_t vl = VALUE_LIST_INIT;
@@ -73,163 +94,533 @@ static void openvpn_submit (char *name, counter_t rx, counter_t tx)
        values[0].counter = rx;
        values[1].counter = tx;
 
+       /* NOTE ON THE NEW NAMING SCHEMA:
+        *       using plugin_instance to identify each vpn config (and
+        *       status) file; using type_instance to identify the endpoint
+        *       host when in multimode, traffic or overhead when in single.
+        */
+
        vl.values = values;
        vl.values_len = STATIC_ARRAY_SIZE (values);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "openvpn", sizeof (vl.plugin));
-       sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance));
+       if (pinst != NULL)
+               sstrncpy (vl.plugin_instance, pinst,
+                               sizeof (vl.plugin_instance));
        sstrncpy (vl.type, "if_octets", sizeof (vl.type));
+       if (tinst != NULL)
+               sstrncpy (vl.type_instance, tinst, sizeof (vl.type_instance));
 
        plugin_dispatch_values (&vl);
-} /* void openvpn_submit */
+} /* void traffic_submit */
 
-static void compression_submit (char *type_instance, gauge_t ratio)
+/* dispatches stats about data compression shown when in single mode */
+static void compression_submit (char *pinst, char *tinst,
+               counter_t uncompressed, counter_t compressed)
 {
-       value_t values[1];
+       value_t values[2];
        value_list_t vl = VALUE_LIST_INIT;
 
-       values[0].gauge = ratio;
+       values[0].counter = uncompressed;
+       values[1].counter = compressed;
 
        vl.values = values;
        vl.values_len = STATIC_ARRAY_SIZE (values);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "openvpn", sizeof (vl.plugin));
-       sstrncpy (vl.type, "compression_ratio", sizeof (vl.type));
-       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type));
+       if (pinst != NULL)
+               sstrncpy (vl.plugin_instance, pinst,
+                               sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, "compression", sizeof (vl.type));
+       if (tinst != NULL)
+               sstrncpy (vl.type_instance, tinst, sizeof (vl.type_instance));
 
        plugin_dispatch_values (&vl);
 } /* void compression_submit */
 
-static int openvpn_read (void)
+static int single_read (char *name, FILE *fh)
 {
-       FILE *fh;
        char buffer[1024];
-       char *fields[10];
+       char *fields[4];
        const int max_fields = STATIC_ARRAY_SIZE (fields);
-       int   fields_num;
+       int  fields_num, read = 0;
+
+       counter_t link_rx, link_tx;
+       counter_t tun_rx, tun_tx;
+       counter_t pre_compress, post_compress;
+       counter_t pre_decompress, post_decompress;
+       counter_t overhead_rx, overhead_tx;
+
+       link_rx = 0;
+       link_tx = 0;
+       tun_rx = 0;
+       tun_tx = 0;
+       pre_compress = 0;
+       post_compress = 0;
+       pre_decompress = 0;
+       post_decompress = 0;
+       overhead_rx = 0;
+       overhead_tx = 0;
 
-       counter_t pre_compress_new    = 0;
-       counter_t post_compress_new   = 0;
-       counter_t pre_decompress_new  = 0;
-       counter_t post_decompress_new = 0;
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               fields_num = openvpn_strsplit (buffer, fields, max_fields);
 
-       /* Clear the least significant four bits, just to make sure all four
-        * counters above are considered to be invalid. */
-       compression_counter_valid &= ~0x0f;
+               /* status file is generated by openvpn/sig.c:print_status()
+                * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/sig.c
+                *
+                * The line we're expecting has 2 fields. We ignore all lines
+                *  with more or less fields.
+                */
+               if (fields_num != 2)
+               {
+                       continue;
+               }
 
-       fh = fopen ((status_file != NULL)
-                       ? status_file
-                       : DEFAULT_STATUS_FILE, "r");
-       if (fh == NULL)
-               return (-1);
+               if (strcmp (fields[0], "TUN/TAP read bytes") == 0)
+               {
+                       /* read from the system and sent over the tunnel */
+                       tun_tx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "TUN/TAP write bytes") == 0)
+               {
+                       /* read from the tunnel and written in the system */
+                       tun_rx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "TCP/UDP read bytes") == 0)
+               {
+                       link_rx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "TCP/UDP write bytes") == 0)
+               {
+                       link_tx = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "pre-compress bytes") == 0)
+               {
+                       pre_compress = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "post-compress bytes") == 0)
+               {
+                       post_compress = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "pre-decompress bytes") == 0)
+               {
+                       pre_decompress = atoll (fields[1]);
+               }
+               else if (strcmp (fields[0], "post-decompress bytes") == 0)
+               {
+                       post_decompress = atoll (fields[1]);
+               }
+       }
+
+       iostats_submit (name, "traffic", link_rx, link_tx);
+
+       /* we need to force this order to avoid negative values with these unsigned */
+       overhead_rx = (((link_rx - pre_decompress) + post_decompress) - tun_rx);
+       overhead_tx = (((link_tx - post_compress) + pre_compress) - tun_tx);
 
-        /* status file is generated by openvpn/multi.c:multi_print_status()
-        * this plugin requires server.conf: "status-version 2"
-         * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
-         */
+       iostats_submit (name, "overhead", overhead_rx, overhead_tx);
+
+       if (store_compression)
+       {
+               compression_submit (name, "data_in", post_decompress, pre_decompress);
+               compression_submit (name, "data_out", pre_compress, post_compress);
+       }
+
+       read = 1;
+
+       return (read);
+} /* int single_read */
+
+/* for reading status version 1 */
+static int multi1_read (char *name, FILE *fh)
+{
+       char buffer[1024];
+       char *fields[10];
+       int  fields_num, read = 0, found_header = 0;
+
+       /* read the file until the "ROUTING TABLE" line is found (no more info after) */
        while (fgets (buffer, sizeof (buffer), fh) != NULL)
        {
-               fields_num = openvpn_strsplit (buffer, fields, max_fields);
+               if (strcmp (buffer, "ROUTING TABLE\n") == 0)
+                       break;
 
-               /* Expect at least ``key,value''. */
-               if (fields_num < 2)
+               if (strcmp (buffer, V1STRING) == 0)
+               {
+                       found_header = 1;
                        continue;
+               }
 
-               if (strcmp (fields[0], "CLIENT_LIST") == 0)
-               {
-                       char *name;
-                       counter_t rx;
-                       counter_t tx;
+               /* skip the first lines until the client list section is found */
+               if (found_header == 0)
+                       /* we can't start reading data until this string is found */
+                       continue;
 
-                       /* The line we're expecting has 8 fields. We ignore all lines
-                        * with more or less fields. */
-                       if (fields_num != 8)
-                               continue;
+               fields_num = openvpn_strsplit (buffer,
+                               fields, STATIC_ARRAY_SIZE (fields));
+               if (fields_num < 4)
+                       continue;
 
-                       name =      fields[1];  /* "Common Name" */
-                       rx = atoll (fields[4]); /* "Bytes Received */
-                       tx = atoll (fields[5]); /* "Bytes Sent" */
-                       openvpn_submit (name, rx, tx);
+               if (new_naming_schema)
+               {
+                       iostats_submit (fields[0],          /* "Common Name" */
+                                       NULL,               /* unused when in multimode */
+                                       atoll (fields[2]),  /* "Bytes Received" */
+                                       atoll (fields[3])); /* "Bytes Sent" */
                }
-               else if (strcmp (fields[0], "pre-compress") == 0)
+               else
                {
-                       pre_compress_new = atoll (fields[1]);
-                       compression_counter_valid |= 0x01;
+                       iostats_submit (name,               /* vpn instance */
+                                       fields[0],          /* "Common Name" */
+                                       atoll (fields[2]),  /* "Bytes Received" */
+                                       atoll (fields[3])); /* "Bytes Sent" */
                }
-               else if (strcmp (fields[0], "post-compress") == 0)
+
+               read = 1;
+       }
+
+       return (read);
+} /* int multi1_read */
+
+/* for reading status version 2 */
+static int multi2_read (char *name, FILE *fh)
+{
+       char buffer[1024];
+       char *fields[10];
+       const int max_fields = STATIC_ARRAY_SIZE (fields);
+       int  fields_num, read = 0;
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               fields_num = openvpn_strsplit (buffer, fields, max_fields);
+
+               /* status file is generated by openvpn/multi.c:multi_print_status()
+                * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
+                *
+                * The line we're expecting has 8 fields. We ignore all lines
+                *  with more or less fields.
+                */
+               if (fields_num != 8)
+                       continue;
+
+               if (strcmp (fields[0], "CLIENT_LIST") != 0)
+                       continue;
+
+               if (new_naming_schema)
                {
-                       post_compress_new = atoll (fields[1]);
-                       compression_counter_valid |= 0x02;
+                       /* plugin inst = file name, type inst = fields[1] */
+                       iostats_submit (name,               /* vpn instance */
+                                       fields[1],          /* "Common Name" */
+                                       atoll (fields[4]),  /* "Bytes Received" */
+                                       atoll (fields[5])); /* "Bytes Sent" */
                }
-               else if (strcmp (fields[0], "pre-decompress") == 0)
+               else
+               {
+                       /* plugin inst = fields[1], type inst = "" */
+                       iostats_submit (fields[1],          /* "Common Name" */
+                                       NULL,               /* unused when in multimode */
+                                       atoll (fields[4]),  /* "Bytes Received" */
+                                       atoll (fields[5])); /* "Bytes Sent" */
+               }
+
+               read = 1;
+       }
+
+       return (read);
+} /* int multi2_read */
+
+/* for reading status version 3 */
+static int multi3_read (char *name, FILE *fh)
+{
+       char buffer[1024];
+       char *fields[15];
+       const int max_fields = STATIC_ARRAY_SIZE (fields);
+       int  fields_num, read = 0;
+
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               fields_num = strsplit (buffer, fields, max_fields);
+
+               /* status file is generated by openvpn/multi.c:multi_print_status()
+                * http://svn.openvpn.net/projects/openvpn/trunk/openvpn/multi.c
+                *
+                * The line we're expecting has 12 fields. We ignore all lines
+                *  with more or less fields.
+                */
+               if (fields_num != 12)
                {
-                       pre_decompress_new = atoll (fields[1]);
-                       compression_counter_valid |= 0x04;
+                       continue;
                }
-               else if (strcmp (fields[0], "post-decompress") == 0)
+               else
                {
-                       post_decompress_new = atoll (fields[1]);
-                       compression_counter_valid |= 0x08;
+                       if (strcmp (fields[0], "CLIENT_LIST") != 0)
+                               continue;
+
+                       if (new_naming_schema)
+                       {
+                               iostats_submit (name,               /* vpn instance */
+                                               fields[1],          /* "Common Name" */
+                                               atoll (fields[4]),  /* "Bytes Received" */
+                                               atoll (fields[5])); /* "Bytes Sent" */
+                       }
+                       else
+                       {
+                               iostats_submit (fields[1],          /* "Common Name" */
+                                               NULL,               /* unused when in multimode */
+                                               atoll (fields[4]),  /* "Bytes Received" */
+                                               atoll (fields[5])); /* "Bytes Sent" */
+                       }
+
+                       read = 1;
                }
        }
-       fclose (fh);
 
-       /* Check that all four counters are valid, {pre,post}_*_{old,new}. */
-       if ((compression_counter_valid & 0x33) == 0x33)
+       return (read);
+} /* int multi3_read */
+
+/* read callback */
+static int openvpn_read (void)
+{
+       FILE *fh;
+       int  i, read;
+
+       read = 0;
+
+       /* call the right read function for every status entry in the list */
+       for (i = 0; i < vpn_num; i++)
        {
-               counter_t pre_diff;
-               counter_t post_diff;
+               fh = fopen (vpn_list[i]->file, "r");
+               if (fh == NULL)
+               {
+                       char errbuf[1024];
+                       WARNING ("openvpn plugin: fopen(%s) failed: %s", vpn_list[i]->file,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+
+                       continue;
+               }
 
-               pre_diff = counter_diff (pre_compress_old, pre_compress_new);
-               post_diff = counter_diff (post_compress_old, post_compress_new);
+               switch (vpn_list[i]->version)
+               {
+                       case SINGLE:
+                               read = single_read(vpn_list[i]->name, fh);
+                               break;
 
-               /* If we compress, we're sending. */
-               compression_submit ("tx",
-                               ((gauge_t) post_diff) / ((gauge_t) pre_diff));
+                       case MULTI1:
+                               read = multi1_read(vpn_list[i]->name, fh);
+                               break;
+
+                       case MULTI2:
+                               read = multi2_read(vpn_list[i]->name, fh);
+                               break;
+
+                       case MULTI3:
+                               read = multi3_read(vpn_list[i]->name, fh);
+                               break;
+               }
+
+               fclose (fh);
        }
 
-       /* Now check the other found counters. */
-       if ((compression_counter_valid & 0xcc) == 0xcc)
+       return (read ? 0 : -1);
+} /* int openvpn_read */
+
+static int version_detect (const char *filename)
+{
+       FILE *fh;
+       char buffer[1024];
+       int version = 0;
+
+       /* Sanity checking. We're called from the config handling routine, so
+        * better play it save. */
+       if ((filename == NULL) || (*filename == 0))
+               return (0);
+
+       fh = fopen (filename, "r");
+       if (fh == NULL)
        {
-               counter_t pre_diff;
-               counter_t post_diff;
+               char errbuf[1024];
+               WARNING ("openvpn plugin: Unable to read \"%s\": %s", filename,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (0);
+       }
 
-               pre_diff = counter_diff (pre_decompress_old, pre_decompress_new);
-               post_diff = counter_diff (post_decompress_old, post_decompress_new);
+       /* now search for the specific multimode data format */
+       while ((fgets (buffer, sizeof (buffer), fh)) != NULL)
+       {
+               /* we look at the first line searching for SINGLE mode configuration */
+               if (strcmp (buffer, VSSTRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version SINGLE");
+                       version = SINGLE;
+                       break;
+               }
+               /* searching for multi version 1 */
+               else if (strcmp (buffer, V1STRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version MULTI1");
+                       version = MULTI1;
+                       break;
+               }
+               /* searching for multi version 2 */
+               else if (strcmp (buffer, V2STRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version MULTI2");
+                       version = MULTI2;
+                       break;
+               }
+               /* searching for multi version 3 */
+               else if (strcmp (buffer, V3STRING) == 0)
+               {
+                       DEBUG ("openvpn plugin: found status file version MULTI3");
+                       version = MULTI3;
+                       break;
+               }
+       }
 
-               /* If we decompress, we're receiving. */
-               compression_submit ("rx",
-                               ((gauge_t) pre_diff) / ((gauge_t) post_diff));
+       if (version == 0)
+       {
+               /* This is only reached during configuration, so complaining to
+                * the user is in order. */
+               NOTICE ("openvpn plugin: %s: Unknown file format, please "
+                               "report this as bug. Make sure to include "
+                               "your status file, so the plugin can "
+                               "be adapted.", filename);
        }
 
-       /* Now copy all the new counters to the old counters and move the flags
-        * up. */
-       pre_compress_old = pre_compress_new;
-       post_compress_old = post_compress_new;
-       pre_decompress_old = pre_decompress_new;
-       post_decompress_old = post_decompress_new;
-       compression_counter_valid = (compression_counter_valid & 0x0f) << 4;
+       fclose (fh);
 
-       return (0);
-} /* int openvpn_read */
+       return version;
+} /* int version_detect */
 
 static int openvpn_config (const char *key, const char *value)
 {
        if (strcasecmp ("StatusFile", key) == 0)
        {
-               sfree (status_file);
+               char    *status_file, *status_name, *filename;
+               int     status_version, i;
+               vpn_status_t *temp;
+
+               /* try to detect the status file format */
+               status_version = version_detect (value);
+
+               if (status_version == 0)
+               {
+                       WARNING ("openvpn plugin: unable to detect status version, \
+                               discarding status file \"%s\".", value);
+                       return (1);
+               }
+
                status_file = sstrdup (value);
-       }
+               if (status_file == NULL)
+               {
+                       char errbuf[1024];
+                       WARNING ("openvpn plugin: sstrdup failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (1);
+               }
+
+               /* it determines the file name as string starting at location filename + 1 */
+               filename = strrchr (status_file, (int) '/');
+               if (filename == NULL)
+               {
+                       /* status_file is already the file name only */
+                       status_name = status_file;
+               }
+               else
+               {
+                       /* doesn't waste memory, uses status_file starting at filename + 1 */
+                       status_name = filename + 1;
+               }
+
+               /* scan the list looking for a clone */
+               for (i = 0; i < vpn_num; i++)
+               {
+                       if (strcasecmp (vpn_list[i]->name, status_name) == 0)
+                       {
+                               WARNING ("openvpn plugin: status filename \"%s\" "
+                                               "already used, please choose a "
+                                               "different one.", status_name);
+                               sfree (status_file);
+                               return (1);
+                       }
+               }
+
+               /* create a new vpn element since file, version and name are ok */
+               temp = (vpn_status_t *) malloc (sizeof (vpn_status_t));
+               temp->file = status_file;
+               temp->version = status_version;
+               temp->name = status_name;
+
+               vpn_list = (vpn_status_t **) realloc (vpn_list, (vpn_num + 1) * sizeof (vpn_status_t *));
+               if (vpn_list == NULL)
+               {
+                       char errbuf[1024];
+                       ERROR ("openvpn plugin: malloc failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+
+                       sfree (temp->file);
+                       sfree (temp);
+                       return (1);
+               }
+
+               vpn_list[vpn_num] = temp;
+               vpn_num++;
+
+               DEBUG ("openvpn plugin: status file \"%s\" added", temp->file);
+
+       } /* if (strcasecmp ("StatusFile", key) == 0) */
+       else if (strcasecmp ("Compression", key) == 0)
+       {
+               if (IS_TRUE (value))
+                       store_compression = 1;
+               else
+               {
+                       store_compression = 0;
+                       DEBUG ("openvpn plugin: no 'compression statistcs' collected");
+               }
+       } /* if (strcasecmp ("Compression", key) == 0) */
+       else if (strcasecmp ("ImprovedNamingSchema", key) == 0)
+       {
+               if (IS_TRUE (value))
+               {
+                       DEBUG ("openvpn plugin: using the new naming schema");
+                       new_naming_schema = 1;
+               }
+               else
+               {
+                       new_naming_schema = 0;
+               }
+       } /* if (strcasecmp ("ImprovedNamingSchema", key) == 0) */
        else
        {
                return (-1);
        }
-        return (0);
+
+       return (0);
 } /* int openvpn_config */
 
+/* shutdown callback */
+static int openvpn_shutdown (void)
+{
+       int i;
+
+       for (i = 0; i < vpn_num; i++)
+       {
+               sfree (vpn_list[i]->file);
+               sfree (vpn_list[i]);
+       }
+
+       sfree (vpn_list);
+
+       return (0);
+} /* int openvpn_shutdown */
+
 void module_register (void)
 {
        plugin_register_config ("openvpn", openvpn_config,
-                               config_keys, config_keys_num);
+                       config_keys, config_keys_num);
        plugin_register_read ("openvpn", openvpn_read);
+       plugin_register_shutdown ("openvpn", openvpn_shutdown);
 } /* void module_register */
+
+/* vim: set sw=2 ts=2 : */
index 11a0ef6..24b2698 100644 (file)
@@ -270,7 +270,7 @@ static int plugin_unregister (llist_t *list, const char *name) /* {{{ */
  * object, but it will bitch about a shared object not having a
  * ``module_register'' symbol..
  */
-static int plugin_load_file (char *file)
+static int plugin_load_file (char *file, uint32_t flags)
 {
        lt_dlhandle dlh;
        void (*reg_handle) (void);
@@ -280,7 +280,24 @@ static int plugin_load_file (char *file)
        lt_dlinit ();
        lt_dlerror (); /* clear errors */
 
-       if ((dlh = lt_dlopen (file)) == NULL)
+#if LIBTOOL_VERSION == 2
+       if (flags & PLUGIN_FLAGS_GLOBAL) {
+               lt_dladvise advise;
+               lt_dladvise_init(&advise);
+               lt_dladvise_global(&advise);
+               dlh = lt_dlopenadvise(file, advise);
+               lt_dladvise_destroy(&advise);
+       } else {
+               dlh = lt_dlopen (file);
+       }
+#else /* if LIBTOOL_VERSION == 1 */
+       if (flags & PLUGIN_FLAGS_GLOBAL)
+               ERROR ("plugin_load_file: The global flag is not supported, "
+                               "libtool 2 is required for this.");
+       dlh = lt_dlopen (file);
+#endif
+
+       if (dlh == NULL)
        {
                const char *error = lt_dlerror ();
 
@@ -535,7 +552,7 @@ void plugin_set_dir (const char *dir)
 }
 
 #define BUFSIZE 512
-int plugin_load (const char *type)
+int plugin_load (const char *type, uint32_t flags)
 {
        DIR  *dh;
        const char *dir;
@@ -597,7 +614,7 @@ int plugin_load (const char *type)
                        continue;
                }
 
-               if (plugin_load_file (filename) == 0)
+               if (plugin_load_file (filename, flags) == 0)
                {
                        /* success */
                        ret = 0;
index b35fcf1..868f44a 100644 (file)
@@ -26,6 +26,8 @@
 #include "configfile.h"
 #include "meta_data.h"
 
+#define PLUGIN_FLAGS_GLOBAL 0x0001
+
 #define DATA_MAX_NAME_LEN 64
 
 #define DS_TYPE_COUNTER  0
@@ -200,7 +202,7 @@ void plugin_set_dir (const char *dir);
  *
  * ARGUMENTS
  *  `name'      Name of the plugin to load.
- *  `mr'        Types of functions to request from the plugin.
+ *  `flags'     Hints on how to handle this plugin.
  *
  * RETURN VALUE
  *  Returns zero upon success, a value greater than zero if no plugin was found
@@ -209,7 +211,7 @@ void plugin_set_dir (const char *dir);
  * NOTES
  *  No attempt is made to re-load an already loaded module.
  */
-int plugin_load (const char *name);
+int plugin_load (const char *name, uint32_t flags);
 
 void plugin_init_all (void);
 void plugin_read_all (void);
@@ -282,7 +284,6 @@ int plugin_unregister_config (const char *name);
 int plugin_unregister_complex_config (const char *name);
 int plugin_unregister_init (const char *name);
 int plugin_unregister_read (const char *name);
-int plugin_unregister_complex_read (const char *name, void **user_data);
 int plugin_unregister_write (const char *name);
 int plugin_unregister_flush (const char *name);
 int plugin_unregister_shutdown (const char *name);
index d847b89..576a5b1 100644 (file)
@@ -266,7 +266,7 @@ static void ps_list_register (const char *name, const char *regexp)
                return;
        }
 #endif
-       
+
        for (ptr = list_head_g; ptr != NULL; ptr = ptr->next)
        {
                if (strcmp (ptr->name, name) == 0)
@@ -342,13 +342,13 @@ static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t
                if ((pse == NULL) || (pse->id != entry->id))
                {
                        procstat_entry_t *new;
-                       
+
                        new = (procstat_entry_t *) malloc (sizeof (procstat_entry_t));
                        if (new == NULL)
                                return;
                        memset (new, 0, sizeof (procstat_entry_t));
                        new->id = entry->id;
-                       
+
                        if (pse == NULL)
                                ps->instances = new;
                        else
@@ -400,7 +400,7 @@ static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t
                                pse->vmem_minflt = entry->vmem_minflt_counter - pse->vmem_minflt_counter;
                        }
                        pse->vmem_minflt_counter = entry->vmem_minflt_counter;
-                       
+
                        if (entry->vmem_majflt_counter < pse->vmem_majflt_counter)
                        {
                                pse->vmem_majflt = entry->vmem_majflt_counter
@@ -437,7 +437,7 @@ static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t
                                pse->cpu_user = entry->cpu_user_counter - pse->cpu_user_counter;
                        }
                        pse->cpu_user_counter = entry->cpu_user_counter;
-                       
+
                        if (entry->cpu_system_counter < pse->cpu_system_counter)
                        {
                                pse->cpu_system = entry->cpu_system_counter
diff --git a/src/pyconfig.c b/src/pyconfig.c
new file mode 100644 (file)
index 0000000..bac39ae
--- /dev/null
@@ -0,0 +1,192 @@
+/**
+ * collectd - src/pyconfig.c
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "collectd.h"
+#include "common.h"
+
+#include "cpython.h"
+
+static char config_doc[] = "This represents a piece of collectd's config file.\n"
+               "It is passed to scripts with config callbacks (see \"register_config\")\n"
+               "and is of little use if created somewhere else.\n"
+               "\n"
+               "It has no methods beyond the bare minimum and only exists for its\n"
+               "data members";
+
+static char parent_doc[] = "This represents the parent of this node. On the root node\n"
+               "of the config tree it will be None.\n";
+
+static char key_doc[] = "This is the keyword of this item, ie the first word of any\n"
+               "given line in the config file. It will always be a string.\n";
+
+static char values_doc[] = "This is a tuple (which might be empty) of all value, ie words\n"
+               "following the keyword in any given line in the config file.\n"
+               "\n"
+               "Every item in this tuple will be either a string or a float or a bool,\n"
+               "depending on the contents of the configuration file.\n";
+
+static char children_doc[] = "This is a tuple of child nodes. For most nodes this will be\n"
+               "empty. If this node represents a block instead of a single line of the config\n"
+               "file it will contain all nodes in this block.\n";
+
+static PyObject *Config_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       Config *self;
+       
+       self = (Config *) type->tp_alloc(type, 0);
+       if (self == NULL)
+               return NULL;
+       
+       self->parent = NULL;
+       self->key = NULL;
+       self->values = NULL;
+       self->children = NULL;
+       return (PyObject *) self;
+}
+
+static int Config_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       PyObject *key = NULL, *parent = NULL, *values = NULL, *children = NULL, *tmp;
+       Config *self = (Config *) s;
+       static char *kwlist[] = {"key", "parent", "values", "children", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "S|OOO", kwlist,
+                       &key, &parent, &values, &children))
+               return -1;
+       
+       if (values == NULL) {
+               values = PyTuple_New(0);
+               PyErr_Clear();
+       }
+       if (children == NULL) {
+               children = PyTuple_New(0);
+               PyErr_Clear();
+       }
+       tmp = self->key;
+       Py_INCREF(key);
+       self->key = key;
+       Py_XDECREF(tmp);
+       if (parent != NULL) {
+               tmp = self->parent;
+               Py_INCREF(parent);
+               self->parent = parent;
+               Py_XDECREF(tmp);
+       }
+       if (values != NULL) {
+               tmp = self->values;
+               Py_INCREF(values);
+               self->values = values;
+               Py_XDECREF(tmp);
+       }
+       if (children != NULL) {
+               tmp = self->children;
+               Py_INCREF(children);
+               self->children = children;
+               Py_XDECREF(tmp);
+       }
+       return 0;
+}
+
+static PyObject *Config_repr(PyObject *s) {
+       Config *self = (Config *) s;
+       
+       return PyString_FromFormat("<collectd.Config %snode %s>", self->parent == Py_None ? "root " : "", PyString_AsString(PyObject_Str(self->key)));
+}
+
+static int Config_traverse(PyObject *self, visitproc visit, void *arg) {
+       Config *c = (Config *) self;
+       Py_VISIT(c->parent);
+       Py_VISIT(c->key);
+       Py_VISIT(c->values);
+       Py_VISIT(c->children);
+       return 0;
+}
+
+static int Config_clear(PyObject *self) {
+       Config *c = (Config *) self;
+       Py_CLEAR(c->parent);
+       Py_CLEAR(c->key);
+       Py_CLEAR(c->values);
+       Py_CLEAR(c->children);
+       return 0;
+}
+
+static void Config_dealloc(PyObject *self) {
+       Config_clear(self);
+       self->ob_type->tp_free(self);
+}
+
+static PyMemberDef Config_members[] = {
+       {"parent", T_OBJECT, offsetof(Config, parent), 0, parent_doc},
+       {"key", T_OBJECT_EX, offsetof(Config, key), 0, key_doc},
+       {"values", T_OBJECT_EX, offsetof(Config, values), 0, values_doc},
+       {"children", T_OBJECT_EX, offsetof(Config, children), 0, children_doc},
+       {NULL}
+};
+
+PyTypeObject ConfigType = {
+       PyObject_HEAD_INIT(NULL)
+       0,                         /* Always 0 */
+       "collectd.Config",         /* tp_name */
+       sizeof(Config),            /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       Config_dealloc,            /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       Config_repr,               /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+       config_doc,                /* tp_doc */
+       Config_traverse,           /* tp_traverse */
+       Config_clear,              /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       0,                         /* tp_methods */
+       Config_members,            /* tp_members */
+       0,                         /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       Config_init,               /* tp_init */
+       0,                         /* tp_alloc */
+       Config_new                 /* tp_new */
+};
+
diff --git a/src/python.c b/src/python.c
new file mode 100644 (file)
index 0000000..a33bb47
--- /dev/null
@@ -0,0 +1,1036 @@
+/**
+ * collectd - src/python.c
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+#include <Python.h>
+#include <structmember.h>
+
+#include <signal.h>
+#if HAVE_PTHREAD_H
+# include <pthread.h>
+#endif
+
+#include "collectd.h"
+#include "common.h"
+
+#include "cpython.h"
+
+typedef struct cpy_callback_s {
+       char *name;
+       PyObject *callback;
+       PyObject *data;
+       struct cpy_callback_s *next;
+} cpy_callback_t;
+
+static char log_doc[] = "This function sends a string to all logging plugins.";
+
+static char flush_doc[] = "flush([plugin][, timeout][, identifier]) -> None\n"
+               "\n"
+               "Flushes the cache of another plugin.";
+
+static char unregister_doc[] = "Unregisters a callback. This function needs exactly one parameter either\n"
+               "the function to unregister or the callback identifier to unregister.";
+
+static char reg_log_doc[] = "register_log(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for log messages.\n"
+               "\n"
+               "'callback' is a callable object that will be called every time something\n"
+               "    is logged.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with two or three parameters:\n"
+               "severity: An integer that should be compared to the LOG_ constants.\n"
+               "message: The text to be logged.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_init_doc[] = "register_init(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function that will be executed once after the config.\n"
+               "file has been read, all plugins heve been loaded and the collectd has\n"
+               "forked into the background.\n"
+               "\n"
+               "'callback' is a callable object that will be executed.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function when it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called without parameters, except for\n"
+               "data if it was supplied.";
+
+static char reg_config_doc[] = "register_config(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for config file entries.\n"
+               "'callback' is a callable object that will be called for every config block.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with one or two parameters:\n"
+               "config: A Config object.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_read_doc[] = "register_read(callback[, interval][, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for reading data. It will just be called\n"
+               "in a fixed interval to signal that it's time to dispatch new values.\n"
+               "'callback' is a callable object that will be called every time something\n"
+               "    is logged.\n"
+               "'interval' is the number of seconds between between calls to the callback\n"
+               "    function. Full float precision is supported here.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called without parameters, except for\n"
+               "data if it was supplied.";
+
+static char reg_write_doc[] = "register_write(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function to receive values dispatched by other plugins.\n"
+               "'callback' is a callable object that will be called every time a value\n"
+               "    is dispatched.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with one or two parameters:\n"
+               "values: A Values object which is a copy of the dispatched values.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_notification_doc[] = "register_notification(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for notifications.\n"
+               "'callback' is a callable object that will be called every time a notification\n"
+               "    is dispatched.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with one or two parameters:\n"
+               "notification: A copy of the notification that was dispatched.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_flush_doc[] = "register_flush(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for flush messages.\n"
+               "'callback' is a callable object that will be called every time a plugin\n"
+               "    requests a flush for either this or all plugins.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function every time it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with two or three parameters:\n"
+               "timeout: Indicates that only data older than 'timeout' seconds is to\n"
+               "    be flushed.\n"
+               "id: Specifies which values are to be flushed.\n"
+               "data: The optional data parameter passed to the register function.\n"
+               "    If the parameter was omitted it will be omitted here, too.";
+
+static char reg_shutdown_doc[] = "register_shutdown(callback[, data][, name]) -> identifier\n"
+               "\n"
+               "Register a callback function for collectd shutdown.\n"
+               "'callback' is a callable object that will be called once collectd is\n"
+               "    shutting down.\n"
+               "'data' is an optional object that will be passed back to the callback\n"
+               "    function if it is called.\n"
+               "'name' is an optional identifier for this callback. The default name\n"
+               "    is 'python.<module>'.\n"
+               "    Every callback needs a unique identifier, so if you want to\n"
+               "    register this callback multiple time from the same module you need\n"
+               "    to specify a name here.\n"
+               "'identifier' is the full identifier assigned to this callback.\n"
+               "\n"
+               "The callback function will be called with no parameters except for\n"
+               "    data if it was supplied.";
+
+
+static int do_interactive = 0;
+
+/* This is our global thread state. Python saves some stuff in thread-local
+ * storage. So if we allow the interpreter to run in the background
+ * (the scriptwriters might have created some threads from python), we have
+ * to save the state so we can resume it later after shutdown. */
+
+static PyThreadState *state;
+
+static PyObject *cpy_format_exception;
+
+static cpy_callback_t *cpy_config_callbacks;
+static cpy_callback_t *cpy_init_callbacks;
+static cpy_callback_t *cpy_shutdown_callbacks;
+
+static void cpy_destroy_user_data(void *data) {
+       cpy_callback_t *c = data;
+       free(c->name);
+       Py_DECREF(c->callback);
+       Py_XDECREF(c->data);
+       free(c);
+}
+
+/* You must hold the GIL to call this function!
+ * But if you managed to extract the callback parameter then you probably already do. */
+
+static void cpy_build_name(char *buf, size_t size, PyObject *callback, const char *name) {
+       const char *module = NULL;
+       PyObject *mod = NULL;
+       
+       if (name != NULL) {
+               snprintf(buf, size, "python.%s", name);
+               return;
+       }
+       
+       mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
+       if (mod != NULL)
+               module = PyString_AsString(mod);
+       
+       if (module != NULL) {
+               snprintf(buf, size, "python.%s", module);
+               Py_XDECREF(mod);
+               PyErr_Clear();
+               return;
+       }
+       Py_XDECREF(mod);
+       
+       snprintf(buf, size, "python.%p", callback);
+       PyErr_Clear();
+}
+
+static 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_Fetch(&type, &value, &traceback);
+       PyErr_NormalizeException(&type, &value, &traceback);
+       if (type == NULL) return;
+       tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
+       m = PyObject_GetAttrString(value, "message"); /* New reference. */
+       if (tn != NULL)
+               typename = PyString_AsString(tn);
+       if (m != NULL)
+               message = PyString_AsString(m);
+       if (typename == NULL)
+               typename = "NamelessException";
+       if (message == NULL)
+               message = "N/A";
+       Py_BEGIN_ALLOW_THREADS
+       ERROR("Unhandled python exception in %s: %s: %s", context, typename, message);
+       Py_END_ALLOW_THREADS
+       Py_XDECREF(tn);
+       Py_XDECREF(m);
+       if (!cpy_format_exception) {
+               PyErr_Clear();
+               Py_XDECREF(type);
+               Py_XDECREF(value);
+               Py_XDECREF(traceback);
+               return;
+       }
+       if (!traceback) {
+               PyErr_Clear();
+               return;
+       }
+       list = PyObject_CallFunction(cpy_format_exception, "NNN", type, value, traceback); /* New reference. */
+       if (list)
+               l = PyObject_Length(list);
+       for (i = 0; i < l; ++i) {
+               char *s;
+               PyObject *line;
+               
+               line = PyList_GET_ITEM(list, i); /* Borrowed reference. */
+               s = strdup(PyString_AsString(line));
+               Py_DECREF(line);
+               if (s[strlen(s) - 1] == '\n')
+                       s[strlen(s) - 1] = 0;
+               Py_BEGIN_ALLOW_THREADS
+               ERROR("%s", s);
+               Py_END_ALLOW_THREADS
+               free(s);
+       }
+       Py_XDECREF(list);
+       PyErr_Clear();
+}
+
+static int cpy_read_callback(user_data_t *data) {
+       cpy_callback_t *c = data->data;
+       PyObject *ret;
+
+       CPY_LOCK_THREADS
+               ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL) {
+                       cpy_log_exception("read callback");
+               } else {
+                       Py_DECREF(ret);
+               }
+       CPY_RELEASE_THREADS
+       if (ret == NULL)
+               return 1;
+       return 0;
+}
+
+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;
+
+       CPY_LOCK_THREADS
+               list = PyList_New(value_list->values_len); /* New reference. */
+               if (list == NULL) {
+                       cpy_log_exception("write callback");
+                       CPY_RETURN_FROM_THREADS 0;
+               }
+               for (i = 0; i < value_list->values_len; ++i) {
+                       if (ds->ds->type == DS_TYPE_COUNTER) {
+                               if ((long) value_list->values[i].counter == value_list->values[i].counter)
+                                       PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].counter));
+                               else
+                                       PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].counter));
+                       } else if (ds->ds->type == DS_TYPE_GAUGE) {
+                               PyList_SetItem(list, i, PyFloat_FromDouble(value_list->values[i].gauge));
+                       } else if (ds->ds->type == DS_TYPE_DERIVE) {
+                               if ((long) value_list->values[i].derive == value_list->values[i].derive)
+                                       PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].derive));
+                               else
+                                       PyList_SetItem(list, i, PyLong_FromLongLong(value_list->values[i].derive));
+                       } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
+                               if ((long) value_list->values[i].absolute == value_list->values[i].absolute)
+                                       PyList_SetItem(list, i, PyInt_FromLong(value_list->values[i].absolute));
+                               else
+                                       PyList_SetItem(list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
+                       } else {
+                               Py_BEGIN_ALLOW_THREADS
+                               ERROR("cpy_write_callback: Unknown value type %d.", ds->ds->type);
+                               Py_END_ALLOW_THREADS
+                               Py_DECREF(list);
+                               CPY_RETURN_FROM_THREADS 0;
+                       }
+                       if (PyErr_Occurred() != NULL) {
+                               cpy_log_exception("value building for write callback");
+                               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);
+               Py_DECREF(list);
+               ret = PyObject_CallFunctionObjArgs(c->callback, v, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL) {
+                       cpy_log_exception("write callback");
+               } else {
+                       Py_DECREF(ret);
+               }
+       CPY_RELEASE_THREADS
+       return 0;
+}
+
+static int cpy_notification_callback(const notification_t *notification, user_data_t *data) {
+       cpy_callback_t *c = data->data;
+       PyObject *ret, *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);
+               ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL) {
+                       cpy_log_exception("notification callback");
+               } else {
+                       Py_DECREF(ret);
+               }
+       CPY_RELEASE_THREADS
+       return 0;
+}
+
+static void cpy_log_callback(int severity, const char *message, user_data_t *data) {
+       cpy_callback_t * c = data->data;
+       PyObject *ret;
+
+       CPY_LOCK_THREADS
+       if (c->data == NULL)
+               ret = PyObject_CallFunction(c->callback, "is", severity, message); /* New reference. */
+       else
+               ret = PyObject_CallFunction(c->callback, "isO", severity, message, c->data); /* New reference. */
+
+       if (ret == NULL) {
+               /* FIXME */
+               /* Do we really want to trigger a log callback because a log callback failed?
+                * Probably not. */
+               PyErr_Print();
+               /* In case someone wanted to be clever, replaced stderr and failed at that. */
+               PyErr_Clear();
+       } else {
+               Py_DECREF(ret);
+       }
+       CPY_RELEASE_THREADS
+}
+
+static void cpy_flush_callback(int timeout, const char *id, user_data_t *data) {
+       cpy_callback_t * c = data->data;
+       PyObject *ret;
+
+       CPY_LOCK_THREADS
+       if (c->data == NULL)
+               ret = PyObject_CallFunction(c->callback, "is", timeout, id); /* New reference. */
+       else
+               ret = PyObject_CallFunction(c->callback, "isO", timeout, id, c->data); /* New reference. */
+
+       if (ret == NULL) {
+               cpy_log_exception("flush callback");
+       } else {
+               Py_DECREF(ret);
+       }
+       CPY_RELEASE_THREADS
+}
+
+static PyObject *cpy_register_generic(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
+       char buf[512];
+       cpy_callback_t *c;
+       const char *name = NULL;
+       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 (PyCallable_Check(callback) == 0) {
+               PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
+               return NULL;
+       }
+       cpy_build_name(buf, sizeof(buf), callback, name);
+
+       Py_INCREF(callback);
+       Py_XINCREF(data);
+       c = malloc(sizeof(*c));
+       c->name = strdup(buf);
+       c->callback = callback;
+       c->data = data;
+       c->next = *list_head;
+       *list_head = c;
+       Py_XDECREF(mod);
+       return PyString_FromString(buf);
+}
+
+static PyObject *cpy_flush(cpy_callback_t **list_head, PyObject *args, PyObject *kwds) {
+       int timeout = -1;
+       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;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_flush(plugin, timeout, identifier);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_register_config(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic(&cpy_config_callbacks, args, kwds);
+}
+
+static PyObject *cpy_register_init(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic(&cpy_init_callbacks, args, kwds);
+}
+
+typedef int reg_function_t(const char *name, void *callback, void *data);
+
+static PyObject *cpy_register_generic_userdata(void *reg, void *handler, PyObject *args, PyObject *kwds) {
+       char buf[512];
+       reg_function_t *register_function = (reg_function_t *) reg;
+       cpy_callback_t *c = NULL;
+       user_data_t *user_data = NULL;
+       const char *name = NULL;
+       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 (PyCallable_Check(callback) == 0) {
+               PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
+               return NULL;
+       }
+       cpy_build_name(buf, sizeof(buf), callback, name);
+       
+       Py_INCREF(callback);
+       Py_XINCREF(data);
+       c = malloc(sizeof(*c));
+       c->name = strdup(buf);
+       c->callback = callback;
+       c->data = data;
+       c->next = NULL;
+       user_data = malloc(sizeof(*user_data));
+       user_data->free_func = cpy_destroy_user_data;
+       user_data->data = c;
+       register_function(buf, handler, user_data);
+       return PyString_FromString(buf);
+}
+
+static PyObject *cpy_register_read(PyObject *self, PyObject *args, PyObject *kwds) {
+       char buf[512];
+       cpy_callback_t *c = NULL;
+       user_data_t *user_data = NULL;
+       double interval = 0;
+       const char *name = NULL;
+       PyObject *callback = NULL, *data = NULL;
+       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 (PyCallable_Check(callback) == 0) {
+               PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
+               return NULL;
+       }
+       cpy_build_name(buf, sizeof(buf), callback, name);
+       
+       Py_INCREF(callback);
+       Py_XINCREF(data);
+       c = malloc(sizeof(*c));
+       c->name = strdup(buf);
+       c->callback = callback;
+       c->data = data;
+       c->next = NULL;
+       user_data = malloc(sizeof(*user_data));
+       user_data->free_func = cpy_destroy_user_data;
+       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);
+}
+
+static PyObject *cpy_register_log(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_log,
+                       (void *) cpy_log_callback, args, kwds);
+}
+
+static PyObject *cpy_register_write(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_write,
+                       (void *) cpy_write_callback, args, kwds);
+}
+
+static PyObject *cpy_register_notification(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_notification,
+                       (void *) cpy_notification_callback, args, kwds);
+}
+
+static PyObject *cpy_register_flush(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic_userdata((void *) plugin_register_flush,
+                       (void *) cpy_flush_callback, args, kwds);
+}
+
+static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args, PyObject *kwds) {
+       return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
+}
+
+static PyObject *cpy_error(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_ERR, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_warning(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_WARNING, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_notice(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_NOTICE, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_info(PyObject *self, PyObject *args) {
+       const char *text;
+       if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_INFO, "%s", text);
+       Py_END_ALLOW_THREADS
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_debug(PyObject *self, PyObject *args) {
+#ifdef COLLECT_DEBUG
+       const char *text;
+       if (PyArg_ParseTuple(args, "s", &text) == 0) return NULL;
+       Py_BEGIN_ALLOW_THREADS
+       plugin_log(LOG_DEBUG, "%s", text);
+       Py_END_ALLOW_THREADS
+#endif
+       Py_RETURN_NONE;
+}
+
+static PyObject *cpy_unregister_generic(cpy_callback_t **list_head, PyObject *arg, const char *desc) {
+       char buf[512];
+       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 {
+               if (!PyCallable_Check(arg)) {
+                       PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
+                       return NULL;
+               }
+               cpy_build_name(buf, sizeof(buf), arg, NULL);
+               name = buf;
+       }
+       for (tmp = *list_head; tmp; prev = tmp, tmp = tmp->next)
+               if (strcmp(name, tmp->name) == 0)
+                       break;
+       
+       if (tmp == NULL) {
+               PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
+               return NULL;
+       }
+       /* Yes, this is actually save. To call this function the caller has to
+        * hold the GIL. Well, save as long as there is only one GIL anyway ... */
+       if (prev == NULL)
+               *list_head = tmp->next;
+       else
+               prev->next = tmp->next;
+       cpy_destroy_user_data(tmp);
+       Py_RETURN_NONE;
+}
+
+typedef int cpy_unregister_function_t(const char *name);
+
+static PyObject *cpy_unregister_generic_userdata(cpy_unregister_function_t *unreg, PyObject *arg, const char *desc) {
+       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 {
+               if (!PyCallable_Check(arg)) {
+                       PyErr_SetString(PyExc_TypeError, "This function needs a string or a callable object as its only parameter.");
+                       return NULL;
+               }
+               cpy_build_name(buf, sizeof(buf), arg, NULL);
+               name = buf;
+       }
+       if (unreg(name) == 0)
+               Py_RETURN_NONE;
+       PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.", desc, name);
+       return NULL;
+}
+
+static PyObject *cpy_unregister_log(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_log, arg, "log");
+}
+
+static PyObject *cpy_unregister_init(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic(&cpy_init_callbacks, arg, "init");
+}
+
+static PyObject *cpy_unregister_config(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic(&cpy_config_callbacks, arg, "config");
+}
+
+static PyObject *cpy_unregister_read(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_read, arg, "read");
+}
+
+static PyObject *cpy_unregister_write(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_write, arg, "write");
+}
+
+static PyObject *cpy_unregister_notification(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_notification, arg, "notification");
+}
+
+static PyObject *cpy_unregister_flush(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic_userdata(plugin_unregister_flush, arg, "flush");
+}
+
+static PyObject *cpy_unregister_shutdown(PyObject *self, PyObject *arg) {
+       return cpy_unregister_generic(&cpy_shutdown_callbacks, arg, "shutdown");
+}
+
+static PyMethodDef cpy_methods[] = {
+       {"debug", cpy_debug, METH_VARARGS, log_doc},
+       {"info", cpy_info, METH_VARARGS, log_doc},
+       {"notice", cpy_notice, METH_VARARGS, log_doc},
+       {"warning", cpy_warning, METH_VARARGS, log_doc},
+       {"error", cpy_error, METH_VARARGS, log_doc},
+       {"flush", (PyCFunction) cpy_flush, METH_VARARGS | METH_KEYWORDS, flush_doc},
+       {"register_log", (PyCFunction) cpy_register_log, METH_VARARGS | METH_KEYWORDS, reg_log_doc},
+       {"register_init", (PyCFunction) cpy_register_init, METH_VARARGS | METH_KEYWORDS, reg_init_doc},
+       {"register_config", (PyCFunction) cpy_register_config, METH_VARARGS | METH_KEYWORDS, reg_config_doc},
+       {"register_read", (PyCFunction) cpy_register_read, METH_VARARGS | METH_KEYWORDS, reg_read_doc},
+       {"register_write", (PyCFunction) cpy_register_write, METH_VARARGS | METH_KEYWORDS, reg_write_doc},
+       {"register_notification", (PyCFunction) cpy_register_notification, METH_VARARGS | METH_KEYWORDS, reg_notification_doc},
+       {"register_flush", (PyCFunction) cpy_register_flush, METH_VARARGS | METH_KEYWORDS, reg_flush_doc},
+       {"register_shutdown", (PyCFunction) cpy_register_shutdown, METH_VARARGS | METH_KEYWORDS, reg_shutdown_doc},
+       {"unregister_log", cpy_unregister_log, METH_O, unregister_doc},
+       {"unregister_init", cpy_unregister_init, METH_O, unregister_doc},
+       {"unregister_config", cpy_unregister_config, METH_O, unregister_doc},
+       {"unregister_read", cpy_unregister_read, METH_O, unregister_doc},
+       {"unregister_write", cpy_unregister_write, METH_O, unregister_doc},
+       {"unregister_notification", cpy_unregister_notification, METH_O, unregister_doc},
+       {"unregister_flush", cpy_unregister_flush, METH_O, unregister_doc},
+       {"unregister_shutdown", cpy_unregister_shutdown, METH_O, unregister_doc},
+       {0, 0, 0, 0}
+};
+
+static int cpy_shutdown(void) {
+       cpy_callback_t *c;
+       PyObject *ret;
+       
+       /* This can happen if the module was loaded but not configured. */
+       if (state != NULL)
+               PyEval_RestoreThread(state);
+
+       for (c = cpy_shutdown_callbacks; c; c = c->next) {
+               ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL)
+                       cpy_log_exception("shutdown callback");
+               else
+                       Py_DECREF(ret);
+       }
+       PyErr_Print();
+       Py_Finalize();
+       return 0;
+}
+
+static void cpy_int_handler(int sig) {
+       return;
+}
+
+static void *cpy_interactive(void *data) {
+       sigset_t sigset;
+       struct sigaction sig_int_action, old;
+       
+       /* Signal handler in a plugin? Bad stuff, but the best way to
+        * handle it I guess. In an interactive session people will
+        * press Ctrl+C at some time, which will generate a SIGINT.
+        * This will cause collectd to shutdown, thus killing the
+        * interactive interpreter, and leaving the terminal in a
+        * mess. Chances are, this isn't what the user wanted to do.
+        * 
+        * So this is the plan:
+        * 1. Block SIGINT in the main thread.
+        * 2. Install our own signal handler that does nothing.
+        * 3. Unblock SIGINT in the interactive thread.
+        *
+        * This will make sure that SIGINT won't kill collectd but
+        * still interrupt syscalls like sleep and pause.
+        * It does not raise a KeyboardInterrupt exception because so
+        * far nobody managed to figure out how to do that. */
+       memset (&sig_int_action, '\0', sizeof (sig_int_action));
+       sig_int_action.sa_handler = cpy_int_handler;
+       sigaction (SIGINT, &sig_int_action, &old);
+       
+       sigemptyset(&sigset);
+       sigaddset(&sigset, SIGINT);
+       pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
+       PyEval_AcquireThread(state);
+       if (PyImport_ImportModule("readline") == NULL) {
+               /* This interactive session will suck. */
+               cpy_log_exception("interactive session init");
+       }
+       PyRun_InteractiveLoop(stdin, "<stdin>");
+       PyErr_Print();
+       PyEval_ReleaseThread(state);
+       NOTICE("python: Interactive interpreter exited, stopping collectd ...");
+       /* Restore the original collectd SIGINT handler and raise SIGINT.
+        * The main thread still has SIGINT blocked and there's nothing we
+        * can do about that so this thread will handle it. But that's not
+        * important, except that it won't interrupt the main loop and so
+        * it might take a few seconds before collectd really shuts down. */
+       sigaction (SIGINT, &old, NULL);
+       raise(SIGINT);
+       pause();
+       return NULL;
+}
+
+static int cpy_init(void) {
+       cpy_callback_t *c;
+       PyObject *ret;
+       static pthread_t thread;
+       sigset_t sigset;
+       
+       PyEval_InitThreads();
+       /* Now it's finally OK to use python threads. */
+       for (c = cpy_init_callbacks; c; c = c->next) {
+               ret = PyObject_CallFunctionObjArgs(c->callback, c->data, (void *) 0); /* New reference. */
+               if (ret == NULL)
+                       cpy_log_exception("init callback");
+               else
+                       Py_DECREF(ret);
+       }
+       sigemptyset(&sigset);
+       sigaddset(&sigset, SIGINT);
+       pthread_sigmask(SIG_BLOCK, &sigset, NULL);
+       state = PyEval_SaveThread();
+       if (do_interactive) {
+               if (pthread_create(&thread, NULL, cpy_interactive, NULL)) {
+                       ERROR("python: Error creating thread for interactive interpreter.");
+               }
+       }
+
+       return 0;
+}
+
+static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
+       int i;
+       PyObject *item, *values, *children, *tmp;
+       
+       if (parent == NULL)
+               parent = Py_None;
+       
+       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));
+               } 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) {
+                       PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
+               }
+       }
+       
+       item = PyObject_CallFunction((void *) &ConfigType, "sONO", ci->key, parent, values, Py_None);
+       if (item == NULL)
+               return NULL;
+       children = PyTuple_New(ci->children_num); /* New reference. */
+       for (i = 0; i < ci->children_num; ++i) {
+               PyTuple_SET_ITEM(children, i, cpy_oconfig_to_pyconfig(ci->children + i, item));
+       }
+       tmp = ((Config *) item)->children;
+       ((Config *) item)->children = children;
+       Py_XDECREF(tmp);
+       return item;
+}
+
+static int cpy_config(oconfig_item_t *ci) {
+       int i;
+       PyObject *sys, *tb;
+       PyObject *sys_path;
+       PyObject *module;
+       
+       /* Ok in theory we shouldn't do initialization at this point
+        * but we have to. In order to give python scripts a chance
+        * to register a config callback we need to be able to execute
+        * python code during the config callback so we have to start
+        * the interpreter here. */
+       /* Do *not* use the python "thread" module at this point! */
+       Py_Initialize();
+       
+       PyType_Ready(&ConfigType);
+       PyType_Ready(&PluginDataType);
+       ValuesType.tp_base = &PluginDataType;
+       PyType_Ready(&ValuesType);
+       NotificationType.tp_base = &PluginDataType;
+       PyType_Ready(&NotificationType);
+       sys = PyImport_ImportModule("sys"); /* New reference. */
+       if (sys == NULL) {
+               cpy_log_exception("python initialization");
+               return 1;
+       }
+       sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
+       Py_DECREF(sys);
+       if (sys_path == NULL) {
+               cpy_log_exception("python initialization");
+               return 1;
+       }
+       module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
+       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_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
+       PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
+       PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
+       PyModule_AddIntConstant(module, "LOG_WARNING", LOG_WARNING);
+       PyModule_AddIntConstant(module, "LOG_ERROR", LOG_ERR);
+       PyModule_AddIntConstant(module, "NOTIF_FAILURE", NOTIF_FAILURE);
+       PyModule_AddIntConstant(module, "NOTIF_WARNING", NOTIF_WARNING);
+       PyModule_AddIntConstant(module, "NOTIF_OKAY", NOTIF_OKAY);
+       for (i = 0; i < ci->children_num; ++i) {
+               oconfig_item_t *item = ci->children + i;
+               
+               if (strcasecmp(item->key, "Interactive") == 0) {
+                       if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
+                               continue;
+                       do_interactive = item->values[0].value.boolean;
+               } else if (strcasecmp(item->key, "Encoding") == 0) {
+                       if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_STRING)
+                               continue;
+                       /* Why is this even necessary? And undocumented? */
+                       if (PyUnicode_SetDefaultEncoding(item->values[0].value.string))
+                               cpy_log_exception("setting default encoding");
+               } else if (strcasecmp(item->key, "LogTraces") == 0) {
+                       if (item->values_num != 1 || item->values[0].type != OCONFIG_TYPE_BOOLEAN)
+                               continue;
+                       if (!item->values[0].value.boolean) {
+                               Py_XDECREF(cpy_format_exception);
+                               cpy_format_exception = NULL;
+                               continue;
+                       }
+                       if (cpy_format_exception)
+                               continue;
+                       tb = PyImport_ImportModule("traceback"); /* New reference. */
+                       if (tb == NULL) {
+                               cpy_log_exception("python initialization");
+                               continue;
+                       }
+                       cpy_format_exception = PyObject_GetAttrString(tb, "format_exception"); /* New reference. */
+                       Py_DECREF(tb);
+                       if (cpy_format_exception == NULL)
+                               cpy_log_exception("python initialization");
+               } else if (strcasecmp(item->key, "ModulePath") == 0) {
+                       char *dir = NULL;
+                       PyObject *dir_object;
+                       
+                       if (cf_util_get_string(item, &dir) != 0) 
+                               continue;
+                       dir_object = PyString_FromString(dir); /* New reference. */
+                       if (dir_object == NULL) {
+                               ERROR("python plugin: Unable to convert \"%s\" to "
+                                     "a python object.", dir);
+                               free(dir);
+                               cpy_log_exception("python initialization");
+                               continue;
+                       }
+                       if (PyList_Append(sys_path, dir_object) != 0) {
+                               ERROR("python plugin: Unable to append \"%s\" to "
+                                     "python module path.", dir);
+                               cpy_log_exception("python initialization");
+                       }
+                       Py_DECREF(dir_object);
+                       free(dir);
+               } else if (strcasecmp(item->key, "Import") == 0) {
+                       char *module_name = NULL;
+                       PyObject *module;
+                       
+                       if (cf_util_get_string(item, &module_name) != 0) 
+                               continue;
+                       module = PyImport_ImportModule(module_name); /* New reference. */
+                       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);
+               } else if (strcasecmp(item->key, "Module") == 0) {
+                       char *name = NULL;
+                       cpy_callback_t *c;
+                       PyObject *ret;
+                       
+                       if (cf_util_get_string(item, &name) != 0)
+                               continue;
+                       for (c = cpy_config_callbacks; c; c = c->next) {
+                               if (strcasecmp(c->name + 7, name) == 0)
+                                       break;
+                       }
+                       if (c == NULL) {
+                               WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
+                                       "but the plugin isn't loaded or didn't register "
+                                       "a configuration callback.", name);
+                               free(name);
+                               continue;
+                       }
+                       free(name);
+                       if (c->data == NULL)
+                               ret = PyObject_CallFunction(c->callback, "N",
+                                       cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
+                       else
+                               ret = PyObject_CallFunction(c->callback, "NO",
+                                       cpy_oconfig_to_pyconfig(item, NULL), c->data); /* New reference. */
+                       if (ret == NULL)
+                               cpy_log_exception("loading module");
+                       else
+                               Py_DECREF(ret);
+               } else {
+                       WARNING("python plugin: Ignoring unknown config key \"%s\".", item->key);
+               }
+       }
+       Py_DECREF(sys_path);
+       return 0;
+}
+
+void module_register(void) {
+       plugin_register_complex_config("python", cpy_config);
+       plugin_register_init("python", cpy_init);
+       plugin_register_shutdown("python", cpy_shutdown);
+}
diff --git a/src/pyvalues.c b/src/pyvalues.c
new file mode 100644 (file)
index 0000000..d83f541
--- /dev/null
@@ -0,0 +1,780 @@
+/**
+ * collectd - src/pyvalues.c
+ * Copyright (C) 2009  Sven Trenkel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *   Sven Trenkel <collectd at semidefinite.de>  
+ **/
+
+#include <Python.h>
+#include <structmember.h>
+
+#include "collectd.h"
+#include "common.h"
+
+#include "cpython.h"
+
+static char time_doc[] = "This is the Unix timestap of the time this value was read.\n"
+               "For dispatching values this can be set to 0 which means \"now\".\n"
+               "This means the time the value is actually dispatched, not the time\n"
+               "it was set to 0.";
+
+static char host_doc[] = "The hostname of the host this value was read from.\n"
+               "For dispatching this can be set to an empty string which means\n"
+               "the local hostname as defined in the collectd.conf.";
+
+static char type_doc[] = "The type of this value. This type has to be defined\n"
+               "in your types.db. Attempting to set it to any other value will\n"
+               "raise a TypeError exception.\n"
+               "Assigning a type is mandetory, calling dispatch without doing\n"
+               "so will raise a RuntimeError exception.";
+
+static char type_instance_doc[] = "";
+
+static char plugin_doc[] = "The name of the plugin that read the data. Setting this\n"
+               "member to an empty string will insert \"python\" upon dispatching.";
+
+static char plugin_instance_doc[] = "";
+
+static char PluginData_doc[] = "This is an internal class that is the base for Values\n"
+               "and Notification. It is pretty useless by itself and was therefore not\n"
+               "exported to the collectd module.";
+
+static PyObject *PluginData_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       PluginData *self;
+       
+       self = (PluginData *) type->tp_alloc(type, 0);
+       if (self == NULL)
+               return NULL;
+       
+       self->time = 0;
+       self->host[0] = 0;
+       self->plugin[0] = 0;
+       self->plugin_instance[0] = 0;
+       self->type[0] = 0;
+       self->type_instance[0] = 0;
+       return (PyObject *) self;
+}
+
+static int PluginData_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       PluginData *self = (PluginData *) s;
+       double time = 0;
+       const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = "";
+       static char *kwlist[] = {"type", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sssssd", kwlist, &type,
+                       &plugin_instance, &type_instance, &plugin, &host, &time))
+               return -1;
+       
+       if (type[0] != 0 && plugin_get_ds(type) == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return -1;
+       }
+
+       sstrncpy(self->host, host, sizeof(self->host));
+       sstrncpy(self->plugin, plugin, sizeof(self->plugin));
+       sstrncpy(self->plugin_instance, plugin_instance, sizeof(self->plugin_instance));
+       sstrncpy(self->type, type, sizeof(self->type));
+       sstrncpy(self->type_instance, type_instance, sizeof(self->type_instance));
+       
+       self->time = time;
+       return 0;
+}
+
+static PyObject *PluginData_repr(PyObject *s) {
+       PluginData *self = (PluginData *) s;
+       
+       return PyString_FromFormat("collectd.Values(type='%s%s%s%s%s%s%s%s%s',time=%lu)", self->type,
+                       *self->type_instance ? "',type_instance='" : "", self->type_instance,
+                       *self->plugin ? "',plugin='" : "", self->plugin,
+                       *self->plugin_instance ? "',plugin_instance='" : "", self->plugin_instance,
+                       *self->host ? "',host='" : "", self->host,
+                       (long unsigned) self->time);
+}
+
+static PyMemberDef PluginData_members[] = {
+       {"time", T_DOUBLE, offsetof(PluginData, time), 0, time_doc},
+       {NULL}
+};
+
+static PyObject *PluginData_getstring(PyObject *self, void *data) {
+       const char *value = ((char *) self) + (intptr_t) data;
+       
+       return PyString_FromString(value);
+}
+
+static int PluginData_setstring(PyObject *self, PyObject *value, void *data) {
+       char *old;
+       const char *new;
+       
+       if (value == NULL) {
+               PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
+               return -1;
+       }
+       new = PyString_AsString(value);
+       if (new == NULL) return -1;
+       old = ((char *) self) + (intptr_t) data;
+       sstrncpy(old, new, DATA_MAX_NAME_LEN);
+       return 0;
+}
+
+static int PluginData_settype(PyObject *self, PyObject *value, void *data) {
+       char *old;
+       const char *new;
+       
+       if (value == NULL) {
+               PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
+               return -1;
+       }
+       new = PyString_AsString(value);
+       if (new == NULL) return -1;
+
+       if (plugin_get_ds(new) == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", new);
+               return -1;
+       }
+
+       old = ((char *) self) + (intptr_t) data;
+       sstrncpy(old, new, DATA_MAX_NAME_LEN);
+       return 0;
+}
+
+static PyGetSetDef PluginData_getseters[] = {
+       {"host", PluginData_getstring, PluginData_setstring, host_doc, (void *) offsetof(PluginData, host)},
+       {"plugin", PluginData_getstring, PluginData_setstring, plugin_doc, (void *) offsetof(PluginData, plugin)},
+       {"plugin_instance", PluginData_getstring, PluginData_setstring, plugin_instance_doc, (void *) offsetof(PluginData, plugin_instance)},
+       {"type_instance", PluginData_getstring, PluginData_setstring, type_instance_doc, (void *) offsetof(PluginData, type_instance)},
+       {"type", PluginData_getstring, PluginData_settype, type_doc, (void *) offsetof(PluginData, type)},
+       {NULL}
+};
+
+PyTypeObject PluginDataType = {
+       PyObject_HEAD_INIT(NULL)
+       0,                         /* Always 0 */
+       "collectd.PluginData",     /* tp_name */
+       sizeof(PluginData),        /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       0,                         /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       PluginData_repr,           /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE /*| Py_TPFLAGS_HAVE_GC*/, /*tp_flags*/
+       PluginData_doc,            /* tp_doc */
+       0,                         /* tp_traverse */
+       0,                         /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       0,                         /* tp_methods */
+       PluginData_members,        /* tp_members */
+       PluginData_getseters,      /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       PluginData_init,           /* tp_init */
+       0,                         /* tp_alloc */
+       PluginData_new             /* tp_new */
+};
+
+static char interval_doc[] = "The interval is the timespan in seconds between two submits for\n"
+               "the same data source. This value has to be a positive integer, so you can't\n"
+               "submit more than one value per second. If this member is set to a\n"
+               "non-positive value, the default value as specified in the config file will\n"
+               "be used (default: 10).\n"
+               "\n"
+               "If you submit values more often than the specified interval, the average\n"
+               "will be used. If you submit less values, your graphs will have gaps.";
+
+static char values_doc[] = "These are the actual values that get dispatched to collectd.\n"
+               "It has to be a sequence (a tuple or list) of numbers.\n"
+               "The size of the sequence and the type of its content depend on the type\n"
+               "member your types.db file. For more information on this read the types.db\n"
+               "man page.\n"
+               "\n"
+               "If the sequence does not have the correct size upon dispatch a RuntimeError\n"
+               "exception will be raised. If the content of the sequence is not a number,\n"
+               "a TypeError exception will be raised.";
+
+static char dispatch_doc[] = "dispatch([type][, values][, plugin_instance][, type_instance]"
+               "[, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.\n"
+               "\n"
+               "Dispatch this instance to the collectd process. The object has members\n"
+               "for each of the possible arguments for this method. For a detailed explanation\n"
+               "of these parameters see the member of the same same.\n"
+               "\n"
+               "If you do not submit a parameter the value saved in its member will be submitted.\n"
+               "If you do provide a parameter it will be used instead, without altering the member.";
+
+static char write_doc[] = "write([destination][, type][, values][, plugin_instance][, type_instance]"
+               "[, plugin][, host][, time][, interval]) -> None.  Dispatch a value list.\n"
+               "\n"
+               "Write this instance to a single plugin or all plugins if 'destination' is obmitted.\n"
+               "This will bypass the main collectd process and all filtering and caching.\n"
+               "Other than that it works similar to 'dispatch'. In most cases 'dispatch' should be\n"
+               "used instead of 'write'.\n";
+
+static char Values_doc[] = "A Values object used for dispatching values to collectd and receiving values from write callbacks.";
+
+static PyObject *Values_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       Values *self;
+       
+       self = (Values *) PluginData_new(type, args, kwds);
+       if (self == NULL)
+               return NULL;
+       
+       self->values = PyList_New(0);
+       self->interval = 0;
+       return (PyObject *) self;
+}
+
+static int Values_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       Values *self = (Values *) s;
+       int interval = 0, ret;
+       double time = 0;
+       PyObject *values = NULL, *tmp;
+       const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = "";
+       static char *kwlist[] = {"type", "values", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "interval", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sOssssdi", kwlist,
+                       &type, &values, &plugin_instance, &type_instance,
+                       &plugin, &host, &time, &interval))
+               return -1;
+       
+       tmp = Py_BuildValue("sssssd", type, plugin_instance, type_instance, plugin, host, time);
+       if (tmp == NULL)
+               return -1;
+       ret = PluginDataType.tp_init(s, tmp, NULL);
+       Py_DECREF(tmp);
+       if (ret != 0)
+               return -1;
+       
+       if (values == NULL) {
+               values = PyList_New(0);
+               PyErr_Clear();
+       } else {
+               Py_INCREF(values);
+       }
+       
+       tmp = self->values;
+       self->values = values;
+       Py_XDECREF(tmp);
+       
+       self->interval = interval;
+       return 0;
+}
+
+static PyObject *Values_dispatch(Values *self, PyObject *args, PyObject *kwds) {
+       int i, ret;
+       const data_set_t *ds;
+       int size;
+       value_t *value;
+       value_list_t value_list = VALUE_LIST_INIT;
+       PyObject *values = self->values;
+       double time = self->data.time;
+       int interval = self->interval;
+       const char *host = self->data.host;
+       const char *plugin = self->data.plugin;
+       const char *plugin_instance = self->data.plugin_instance;
+       const char *type = self->data.type;
+       const char *type_instance = self->data.type_instance;
+       
+       static char *kwlist[] = {"type", "values", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "interval", NULL};
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sOssssdi", kwlist,
+                       &type, &values, &plugin_instance, &type_instance,
+                       &plugin, &host, &time, &interval))
+               return NULL;
+
+       if (type[0] == 0) {
+               PyErr_SetString(PyExc_RuntimeError, "type not set");
+               return NULL;
+       }
+       ds = plugin_get_ds(type);
+       if (ds == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return NULL;
+       }
+       if (values == NULL || (PyTuple_Check(values) == 0 && PyList_Check(values) == 0)) {
+               PyErr_Format(PyExc_TypeError, "values must be list or tuple");
+               return NULL;
+       }
+       size = (int) PySequence_Length(values);
+       if (size != ds->ds_num) {
+               PyErr_Format(PyExc_RuntimeError, "type %s needs %d values, got %i", type, ds->ds_num, size);
+               return NULL;
+       }
+       value = malloc(size * sizeof(*value));
+       for (i = 0; i < size; ++i) {
+               PyObject *item, *num;
+               item = PySequence_GetItem(values, i);
+               if (ds->ds->type == DS_TYPE_COUNTER) {
+                       num = PyNumber_Long(item);
+                       if (num != NULL)
+                               value[i].counter = PyLong_AsUnsignedLongLong(num);
+               } else if (ds->ds->type == DS_TYPE_GAUGE) {
+                       num = PyNumber_Float(item);
+                       if (num != NULL)
+                               value[i].gauge = PyFloat_AsDouble(num);
+               } else if (ds->ds->type == DS_TYPE_DERIVE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item);
+                       if (num != NULL)
+                               value[i].derive = PyLong_AsLongLong(num);
+               } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item);
+                       if (num != NULL)
+                               value[i].absolute = PyLong_AsUnsignedLongLong(num);
+               } else {
+                       free(value);
+                       PyErr_Format(PyExc_RuntimeError, "unknown data type %d for %s", ds->ds->type, type);
+                       return NULL;
+               }
+               if (PyErr_Occurred() != NULL) {
+                       free(value);
+                       return NULL;
+               }
+       }
+       value_list.values = value;
+       value_list.values_len = size;
+       value_list.time = time;
+       value_list.interval = interval;
+       sstrncpy(value_list.host, host, sizeof(value_list.host));
+       sstrncpy(value_list.plugin, plugin, sizeof(value_list.plugin));
+       sstrncpy(value_list.plugin_instance, plugin_instance, sizeof(value_list.plugin_instance));
+       sstrncpy(value_list.type, type, sizeof(value_list.type));
+       sstrncpy(value_list.type_instance, type_instance, sizeof(value_list.type_instance));
+       value_list.meta = NULL;
+       if (value_list.host[0] == 0)
+               sstrncpy(value_list.host, hostname_g, sizeof(value_list.host));
+       if (value_list.plugin[0] == 0)
+               sstrncpy(value_list.plugin, "python", sizeof(value_list.plugin));
+       Py_BEGIN_ALLOW_THREADS;
+       ret = plugin_dispatch_values(&value_list);
+       Py_END_ALLOW_THREADS;
+       if (ret != 0) {
+               PyErr_SetString(PyExc_RuntimeError, "error dispatching values, read the logs");
+               return NULL;
+       }
+       free(value);
+       Py_RETURN_NONE;
+}
+
+static PyObject *Values_write(Values *self, PyObject *args, PyObject *kwds) {
+       int i, ret;
+       const data_set_t *ds;
+       int size;
+       value_t *value;
+       value_list_t value_list = VALUE_LIST_INIT;
+       PyObject *values = self->values;
+       double time = self->data.time;
+       int interval = self->interval;
+       const char *host = self->data.host;
+       const char *plugin = self->data.plugin;
+       const char *plugin_instance = self->data.plugin_instance;
+       const char *type = self->data.type;
+       const char *type_instance = self->data.type_instance;
+       const char *dest = NULL;
+       
+       static char *kwlist[] = {"destination", "type", "values", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "interval", NULL};
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sOssssdi", kwlist,
+                       &type, &values, &plugin_instance, &type_instance,
+                       &plugin, &host, &time, &interval))
+               return NULL;
+
+       if (type[0] == 0) {
+               PyErr_SetString(PyExc_RuntimeError, "type not set");
+               return NULL;
+       }
+       ds = plugin_get_ds(type);
+       if (ds == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return NULL;
+       }
+       if (values == NULL || (PyTuple_Check(values) == 0 && PyList_Check(values) == 0)) {
+               PyErr_Format(PyExc_TypeError, "values must be list or tuple");
+               return NULL;
+       }
+       size = (int) PySequence_Length(values);
+       if (size != ds->ds_num) {
+               PyErr_Format(PyExc_RuntimeError, "type %s needs %d values, got %i", type, ds->ds_num, size);
+               return NULL;
+       }
+       value = malloc(size * sizeof(*value));
+       for (i = 0; i < size; ++i) {
+               PyObject *item, *num;
+               item = PySequence_GetItem(values, i);
+               if (ds->ds->type == DS_TYPE_COUNTER) {
+                       num = PyNumber_Long(item);
+                       if (num != NULL)
+                               value[i].counter = PyLong_AsUnsignedLongLong(num);
+               } else if (ds->ds->type == DS_TYPE_GAUGE) {
+                       num = PyNumber_Float(item);
+                       if (num != NULL)
+                               value[i].gauge = PyFloat_AsDouble(num);
+               } else if (ds->ds->type == DS_TYPE_DERIVE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item);
+                       if (num != NULL)
+                               value[i].derive = PyLong_AsLongLong(num);
+               } else if (ds->ds->type == DS_TYPE_ABSOLUTE) {
+                       /* This might overflow without raising an exception.
+                        * Not much we can do about it */
+                       num = PyNumber_Long(item);
+                       if (num != NULL)
+                               value[i].absolute = PyLong_AsUnsignedLongLong(num);
+               } else {
+                       free(value);
+                       PyErr_Format(PyExc_RuntimeError, "unknown data type %d for %s", ds->ds->type, type);
+                       return NULL;
+               }
+               if (PyErr_Occurred() != NULL) {
+                       free(value);
+                       return NULL;
+               }
+       }
+       value_list.values = value;
+       value_list.values_len = size;
+       value_list.time = time;
+       value_list.interval = interval;
+       sstrncpy(value_list.host, host, sizeof(value_list.host));
+       sstrncpy(value_list.plugin, plugin, sizeof(value_list.plugin));
+       sstrncpy(value_list.plugin_instance, plugin_instance, sizeof(value_list.plugin_instance));
+       sstrncpy(value_list.type, type, sizeof(value_list.type));
+       sstrncpy(value_list.type_instance, type_instance, sizeof(value_list.type_instance));
+       value_list.meta = NULL;
+       if (value_list.host[0] == 0)
+               sstrncpy(value_list.host, hostname_g, sizeof(value_list.host));
+       if (value_list.plugin[0] == 0)
+               sstrncpy(value_list.plugin, "python", sizeof(value_list.plugin));
+       Py_BEGIN_ALLOW_THREADS;
+       ret = plugin_write(dest, NULL, &value_list);
+       Py_END_ALLOW_THREADS;
+       if (ret != 0) {
+               PyErr_SetString(PyExc_RuntimeError, "error dispatching values, read the logs");
+               return NULL;
+       }
+       free(value);
+       Py_RETURN_NONE;
+}
+
+static PyObject *Values_repr(PyObject *s) {
+       PyObject *ret, *valuestring = NULL;
+       Values *self = (Values *) s;
+       
+       if (self->values != NULL)
+               valuestring = PyObject_Repr(self->values);
+       if (valuestring == NULL)
+               return NULL;
+       
+       ret = PyString_FromFormat("collectd.Values(type='%s%s%s%s%s%s%s%s%s',time=%lu,interval=%i,values=%s)", self->data.type,
+                       *self->data.type_instance ? "',type_instance='" : "", self->data.type_instance,
+                       *self->data.plugin ? "',plugin='" : "", self->data.plugin,
+                       *self->data.plugin_instance ? "',plugin_instance='" : "", self->data.plugin_instance,
+                       *self->data.host ? "',host='" : "", self->data.host,
+                       (long unsigned) self->data.time, self->interval,
+                       valuestring ? PyString_AsString(valuestring) : "[]");
+       Py_XDECREF(valuestring);
+       return ret;
+}
+
+static int Values_traverse(PyObject *self, visitproc visit, void *arg) {
+       Values *v = (Values *) self;
+       Py_VISIT(v->values);
+       return 0;
+}
+
+static int Values_clear(PyObject *self) {
+       Values *v = (Values *) self;
+       Py_CLEAR(v->values);
+       return 0;
+}
+
+static void Values_dealloc(PyObject *self) {
+       Values_clear(self);
+       self->ob_type->tp_free(self);
+}
+
+static PyMemberDef Values_members[] = {
+       {"interval", T_INT, offsetof(Values, interval), 0, interval_doc},
+       {"values", T_OBJECT_EX, offsetof(Values, values), 0, values_doc},
+       {NULL}
+};
+
+static PyMethodDef Values_methods[] = {
+       {"dispatch", (PyCFunction) Values_dispatch, METH_VARARGS | METH_KEYWORDS, dispatch_doc},
+       {"write", (PyCFunction) Values_write, METH_VARARGS | METH_KEYWORDS, write_doc},
+       {NULL}
+};
+
+PyTypeObject ValuesType = {
+       PyObject_HEAD_INIT(NULL)
+       0,                         /* Always 0 */
+       "collectd.Values",         /* tp_name */
+       sizeof(Values),            /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       Values_dealloc,            /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       Values_repr,               /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /*tp_flags*/
+       Values_doc,                /* tp_doc */
+       Values_traverse,           /* tp_traverse */
+       Values_clear,              /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       Values_methods,            /* tp_methods */
+       Values_members,            /* tp_members */
+       0,                         /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       Values_init,               /* tp_init */
+       0,                         /* tp_alloc */
+       Values_new                 /* tp_new */
+};
+
+static char severity_doc[] = "The severity of this notification. Assign or compare to\n"
+               "NOTIF_FAILURE, NOTIF_WARNING or NOTIF_OKAY.";
+
+static char message_doc[] = "Some kind of description what's going on and why this Notification was generated.";
+
+static char Notification_doc[] = "The Notification class is a wrapper around the collectd notification.\n"
+               "It can be used to notify other plugins about bad stuff happening. It works\n"
+               "similar to Values but has a severity and a message instead of interval\n"
+               "and time.\n"
+               "Notifications can be dispatched at any time and can be received with register_notification.";
+
+static int Notification_init(PyObject *s, PyObject *args, PyObject *kwds) {
+       Notification *self = (Notification *) s;
+       PyObject *tmp;
+       int severity = 0, ret;
+       double time = 0;
+       const char *message = "";
+       const char *type = "", *plugin_instance = "", *type_instance = "", *plugin = "", *host = "";
+       static char *kwlist[] = {"type", "message", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "severity", NULL};
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ssssssdi", kwlist,
+                       &type, &message, &plugin_instance, &type_instance,
+                       &plugin, &host, &time, &severity))
+               return -1;
+       
+       tmp = Py_BuildValue("sssssd", type, plugin_instance, type_instance, plugin, host, time);
+       if (tmp == NULL)
+               return -1;
+       ret = PluginDataType.tp_init(s, tmp, NULL);
+       Py_DECREF(tmp);
+       if (ret != 0)
+               return -1;
+       
+       sstrncpy(self->message, message, sizeof(self->message));
+       self->severity = severity;
+       return 0;
+}
+
+static PyObject *Notification_dispatch(Notification *self, PyObject *args, PyObject *kwds) {
+       int ret;
+       const data_set_t *ds;
+       notification_t notification;
+       double t = self->data.time;
+       int severity = self->severity;
+       const char *host = self->data.host;
+       const char *plugin = self->data.plugin;
+       const char *plugin_instance = self->data.plugin_instance;
+       const char *type = self->data.type;
+       const char *type_instance = self->data.type_instance;
+       const char *message = self->message;
+       
+       static char *kwlist[] = {"type", "message", "plugin_instance", "type_instance",
+                       "plugin", "host", "time", "severity", NULL};
+       if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ssssssdi", kwlist,
+                       &type, &message, &plugin_instance, &type_instance,
+                       &plugin, &host, &t, &severity))
+               return NULL;
+
+       if (type[0] == 0) {
+               PyErr_SetString(PyExc_RuntimeError, "type not set");
+               return NULL;
+       }
+       ds = plugin_get_ds(type);
+       if (ds == NULL) {
+               PyErr_Format(PyExc_TypeError, "Dataset %s not found", type);
+               return NULL;
+       }
+
+       notification.time = t;
+       notification.severity = severity;
+       sstrncpy(notification.message, message, sizeof(notification.message));
+       sstrncpy(notification.host, host, sizeof(notification.host));
+       sstrncpy(notification.plugin, plugin, sizeof(notification.plugin));
+       sstrncpy(notification.plugin_instance, plugin_instance, sizeof(notification.plugin_instance));
+       sstrncpy(notification.type, type, sizeof(notification.type));
+       sstrncpy(notification.type_instance, type_instance, sizeof(notification.type_instance));
+       notification.meta = NULL;
+       if (notification.time < 1)
+               notification.time = time(0);
+       if (notification.host[0] == 0)
+               sstrncpy(notification.host, hostname_g, sizeof(notification.host));
+       if (notification.plugin[0] == 0)
+               sstrncpy(notification.plugin, "python", sizeof(notification.plugin));
+       Py_BEGIN_ALLOW_THREADS;
+       ret = plugin_dispatch_notification(&notification);
+       Py_END_ALLOW_THREADS;
+       if (ret != 0) {
+               PyErr_SetString(PyExc_RuntimeError, "error dispatching notification, read the logs");
+               return NULL;
+       }
+       Py_RETURN_NONE;
+}
+
+static PyObject *Notification_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
+       Notification *self;
+       
+       self = (Notification *) PluginData_new(type, args, kwds);
+       if (self == NULL)
+               return NULL;
+       
+       self->message[0] = 0;
+       self->severity = 0;
+       return (PyObject *) self;
+}
+
+static int Notification_setstring(PyObject *self, PyObject *value, void *data) {
+       char *old;
+       const char *new;
+       
+       if (value == NULL) {
+               PyErr_SetString(PyExc_TypeError, "Cannot delete this attribute");
+               return -1;
+       }
+       new = PyString_AsString(value);
+       if (new == NULL) return -1;
+       old = ((char *) self) + (intptr_t) data;
+       sstrncpy(old, new, NOTIF_MAX_MSG_LEN);
+       return 0;
+}
+
+static PyObject *Notification_repr(PyObject *s) {
+       PyObject *ret;
+       Notification *self = (Notification *) s;
+       
+       ret = PyString_FromFormat("collectd.Values(type='%s%s%s%s%s%s%s%s%s%s%s',time=%lu,interval=%i)", self->data.type,
+                       *self->data.type_instance ? "',type_instance='" : "", self->data.type_instance,
+                       *self->data.plugin ? "',plugin='" : "", self->data.plugin,
+                       *self->data.plugin_instance ? "',plugin_instance='" : "", self->data.plugin_instance,
+                       *self->data.host ? "',host='" : "", self->data.host,
+                       *self->message ? "',message='" : "", self->message,
+                       (long unsigned) self->data.time, self->severity);
+       return ret;
+}
+
+static PyMethodDef Notification_methods[] = {
+       {"dispatch", (PyCFunction) Notification_dispatch, METH_VARARGS | METH_KEYWORDS, dispatch_doc},
+       {NULL}
+};
+
+static PyMemberDef Notification_members[] = {
+       {"severity", T_INT, offsetof(Notification, severity), 0, severity_doc},
+       {NULL}
+};
+
+static PyGetSetDef Notification_getseters[] = {
+       {"message", PluginData_getstring, Notification_setstring, message_doc, (void *) offsetof(Notification, message)},
+       {NULL}
+};
+
+PyTypeObject NotificationType = {
+       PyObject_HEAD_INIT(NULL)
+       0,                         /* Always 0 */
+       "collectd.Notification",   /* tp_name */
+       sizeof(Notification),      /* tp_basicsize */
+       0,                         /* Will be filled in later */
+       0,                         /* tp_dealloc */
+       0,                         /* tp_print */
+       0,                         /* tp_getattr */
+       0,                         /* tp_setattr */
+       0,                         /* tp_compare */
+       Notification_repr,         /* tp_repr */
+       0,                         /* tp_as_number */
+       0,                         /* tp_as_sequence */
+       0,                         /* tp_as_mapping */
+       0,                         /* tp_hash */
+       0,                         /* tp_call */
+       0,                         /* tp_str */
+       0,                         /* tp_getattro */
+       0,                         /* tp_setattro */
+       0,                         /* tp_as_buffer */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+       Notification_doc,          /* tp_doc */
+       0,                         /* tp_traverse */
+       0,                         /* tp_clear */
+       0,                         /* tp_richcompare */
+       0,                         /* tp_weaklistoffset */
+       0,                         /* tp_iter */
+       0,                         /* tp_iternext */
+       Notification_methods,      /* tp_methods */
+       Notification_members,      /* tp_members */
+       Notification_getseters,    /* tp_getset */
+       0,                         /* tp_base */
+       0,                         /* tp_dict */
+       0,                         /* tp_descr_get */
+       0,                         /* tp_descr_set */
+       0,                         /* tp_dictoffset */
+       Notification_init,         /* tp_init */
+       0,                         /* tp_alloc */
+       Notification_new           /* tp_new */
+};
diff --git a/src/routeros.c b/src/routeros.c
new file mode 100644 (file)
index 0000000..02c96d7
--- /dev/null
@@ -0,0 +1,347 @@
+/**
+ * collectd - src/routeros.c
+ * Copyright (C) 2009  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <routeros_api.h>
+
+struct cr_data_s
+{
+  ros_connection_t *connection;
+
+  char *node;
+  char *service;
+  char *username;
+  char *password;
+
+  _Bool collect_interface;
+  _Bool collect_regtable;
+};
+typedef struct cr_data_s cr_data_t;
+
+static void cr_submit_io (const char *type, const char *type_instance, /* {{{ */
+    counter_t rx, counter_t tx)
+{
+       value_t values[2];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].counter = rx;
+       values[1].counter = tx;
+
+       vl.values = values;
+       vl.values_len = STATIC_ARRAY_SIZE (values);
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "routeros", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* }}} void cr_submit_io */
+
+static void submit_interface (const ros_interface_t *i) /* {{{ */
+{
+  if (i == NULL)
+    return;
+
+  if (!i->running)
+  {
+    submit_interface (i->next);
+    return;
+  }
+
+  cr_submit_io ("if_packets", i->name,
+      (counter_t) i->rx_packets, (counter_t) i->tx_packets);
+  cr_submit_io ("if_octets", i->name,
+      (counter_t) i->rx_bytes, (counter_t) i->tx_bytes);
+  cr_submit_io ("if_errors", i->name,
+      (counter_t) i->rx_errors, (counter_t) i->tx_errors);
+  cr_submit_io ("if_dropped", i->name,
+      (counter_t) i->rx_drops, (counter_t) i->tx_drops);
+
+  submit_interface (i->next);
+} /* }}} void submit_interface */
+
+static int handle_interface (__attribute__((unused)) ros_connection_t *c, /* {{{ */
+    const ros_interface_t *i, __attribute__((unused)) void *user_data)
+{
+  submit_interface (i);
+  return (0);
+} /* }}} int handle_interface */
+
+static void cr_submit_gauge (const char *type, /* {{{ */
+    const char *type_instance, gauge_t value)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = value;
+
+       vl.values = values;
+       vl.values_len = STATIC_ARRAY_SIZE (values);
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "routeros", sizeof (vl.plugin));
+       sstrncpy (vl.type, type, sizeof (vl.type));
+       sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+       plugin_dispatch_values (&vl);
+} /* }}} void cr_submit_gauge */
+
+static void submit_regtable (const ros_registration_table_t *r) /* {{{ */
+{
+  char type_instance[DATA_MAX_NAME_LEN];
+
+  if (r == NULL)
+    return;
+
+  /*** RX ***/
+  ssnprintf (type_instance, sizeof (type_instance), "%s-%s-rx",
+      r->interface, r->radio_name);
+  cr_submit_gauge ("bitrate", type_instance,
+      (gauge_t) (1000000.0 * r->rx_rate));
+  cr_submit_gauge ("signal_power", type_instance,
+      (gauge_t) r->rx_signal_strength);
+  cr_submit_gauge ("signal_quality", type_instance,
+      (gauge_t) r->rx_ccq);
+
+  /*** TX ***/
+  ssnprintf (type_instance, sizeof (type_instance), "%s-%s-tx",
+      r->interface, r->radio_name);
+  cr_submit_gauge ("bitrate", type_instance,
+      (gauge_t) (1000000.0 * r->tx_rate));
+  cr_submit_gauge ("signal_power", type_instance,
+      (gauge_t) r->tx_signal_strength);
+  cr_submit_gauge ("signal_quality", type_instance,
+      (gauge_t) r->tx_ccq);
+
+  /*** RX / TX ***/
+  ssnprintf (type_instance, sizeof (type_instance), "%s-%s",
+      r->interface, r->radio_name);
+  cr_submit_io ("if_octets", type_instance,
+      (counter_t) r->rx_bytes, (counter_t) r->tx_bytes);
+  cr_submit_gauge ("snr", type_instance, (gauge_t) r->signal_to_noise);
+
+  submit_regtable (r->next);
+} /* }}} void submit_regtable */
+
+static int handle_regtable (__attribute__((unused)) ros_connection_t *c, /* {{{ */
+    const ros_registration_table_t *r,
+    __attribute__((unused)) void *user_data)
+{
+  submit_regtable (r);
+  return (0);
+} /* }}} int handle_regtable */
+
+static int cr_read (user_data_t *user_data) /* {{{ */
+{
+  int status;
+  cr_data_t *rd;
+
+  if (user_data == NULL)
+    return (EINVAL);
+
+  rd = user_data->data;
+  if (rd == NULL)
+    return (EINVAL);
+
+  if (rd->connection == NULL)
+  {
+    rd->connection = ros_connect (rd->node, rd->service,
+       rd->username, rd->password);
+    if (rd->connection == NULL)
+    {
+      char errbuf[128];
+      ERROR ("routeros plugin: ros_connect failed: %s",
+         sstrerror (errno, errbuf, sizeof (errbuf)));
+      return (-1);
+    }
+  }
+  assert (rd->connection != NULL);
+
+  if (rd->collect_interface)
+  {
+    status = ros_interface (rd->connection, handle_interface,
+       /* user data = */ NULL);
+    if (status != 0)
+    {
+      char errbuf[128];
+      ERROR ("routeros plugin: ros_interface failed: %s",
+         sstrerror (status, errbuf, sizeof (errbuf)));
+      ros_disconnect (rd->connection);
+      rd->connection = NULL;
+      return (-1);
+    }
+  }
+
+  if (rd->collect_regtable)
+  {
+    status = ros_registration_table (rd->connection, handle_regtable,
+       /* user data = */ NULL);
+    if (status != 0)
+    {
+      char errbuf[128];
+      ERROR ("routeros plugin: ros_registration_table failed: %s",
+         sstrerror (status, errbuf, sizeof (errbuf)));
+      ros_disconnect (rd->connection);
+      rd->connection = NULL;
+      return (-1);
+    }
+  }
+
+  return (0);
+} /* }}} int cr_read */
+
+static void cr_free_data (cr_data_t *ptr) /* {{{ */
+{
+  if (ptr == NULL)
+    return;
+
+  ros_disconnect (ptr->connection);
+  ptr->connection = NULL;
+
+  sfree (ptr->node);
+  sfree (ptr->service);
+  sfree (ptr->username);
+  sfree (ptr->password);
+
+  sfree (ptr);
+} /* }}} void cr_free_data */
+
+static int cr_config_router (oconfig_item_t *ci) /* {{{ */
+{
+  cr_data_t *router_data;
+  char read_name[128];
+  user_data_t user_data;
+  int status;
+  int i;
+
+  router_data = malloc (sizeof (*router_data));
+  if (router_data == NULL)
+    return (-1);
+  memset (router_data, 0, sizeof (router_data));
+  router_data->connection = NULL;
+  router_data->node = NULL;
+  router_data->service = NULL;
+  router_data->username = NULL;
+  router_data->password = NULL;
+  router_data->collect_interface = false;
+  router_data->collect_regtable = false;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Host", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->node);
+    else if (strcasecmp ("Port", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->service);
+    else if (strcasecmp ("User", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->username);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = cf_util_get_string (child, &router_data->password);
+    else if (strcasecmp ("CollectInterface", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_interface);
+    else if (strcasecmp ("CollectRegistrationTable", child->key) == 0)
+      cf_util_get_boolean (child, &router_data->collect_regtable);
+    else
+    {
+      WARNING ("routeros plugin: Unknown config option `%s'.", child->key);
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+  {
+    if (router_data->node == NULL)
+    {
+      ERROR ("routeros plugin: No `Host' option within a `Router' block. "
+         "Where should I connect to?");
+      status = -1;
+    }
+
+    if (router_data->password == NULL)
+    {
+      ERROR ("routeros plugin: No `Password' option within a `Router' block. "
+         "How should I authenticate?");
+      status = -1;
+    }
+
+    if (!router_data->collect_interface
+       && !router_data->collect_regtable)
+    {
+      ERROR ("routeros plugin: No `Collect*' option within a `Router' block. "
+         "What statistics should I collect?");
+      status = -1;
+    }
+  }
+
+  if ((status == 0) && (router_data->username == NULL))
+  {
+    router_data->username = sstrdup ("admin");
+    if (router_data->username == NULL)
+    {
+      ERROR ("routeros plugin: sstrdup failed.");
+      status = -1;
+    }
+  }
+
+  ssnprintf (read_name, sizeof (read_name), "routeros/%s", router_data->node);
+  user_data.data = router_data;
+  user_data.free_func = (void *) cr_free_data;
+  if (status == 0)
+    status = plugin_register_complex_read (read_name, cr_read,
+       /* interval = */ NULL, &user_data);
+
+  if (status != 0)
+    cr_free_data (router_data);
+
+  return (status);
+} /* }}} int cr_config_router */
+
+static int cr_config (oconfig_item_t *ci)
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Router", child->key) == 0)
+      cr_config_router (child);
+    else
+    {
+      WARNING ("routeros plugin: Unknown config option `%s'.", child->key);
+    }
+  }
+
+  return (0);
+} /* }}} int cr_config */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("routeros", cr_config);
+} /* void module_register */
+
+/* vim: set sw=2 noet fdm=marker : */
index e9f49cf..7f41c9e 100644 (file)
@@ -189,7 +189,7 @@ static int swap_read (void)
 #if KERNEL_LINUX
        FILE *fh;
        char buffer[1024];
-       
+
        char *fields[8];
        int numfields;
 
index c8c6b98..a5872eb 100644 (file)
@@ -16,6 +16,7 @@ cache_ratio           value:GAUGE:0:100
 cache_result           value:COUNTER:0:4294967295
 cache_size             value:GAUGE:0:4294967295
 charge                 value:GAUGE:0:U
+compression            uncompressed:COUNTER:0:U, compressed:COUNTER:0:U
 compression_ratio      value:GAUGE:0:2
 connections            value:COUNTER:0:U
 conntrack              entropy:GAUGE:0:4294967295
@@ -139,6 +140,7 @@ serial_octets               rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
 signal_noise           value:GAUGE:U:0
 signal_power           value:GAUGE:U:0
 signal_quality         value:GAUGE:0:U
+snr                    value:GAUGE:0:U
 spam_check             value:GAUGE:0:U
 spam_score             value:GAUGE:U:U
 swap_io                        value:DERIVE:0:1099511627776