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.
Sven Trenkel <collectd at semidefinite.de>
- netapp plugin.
+ - python plugin.
Tomasz Pala <gotar at pld-linux.org>
- conntrack plugin.
- 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.
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
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.
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:
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,
LT_CONFIG_LTDL_DIR([libltdl])
LT_INIT([dlopen])
LTDL_INIT([convenience])
+ AC_DEFINE(LIBTOOL_VERSION, 2, [Define to used libtool version.])
]
,
# libtool <= 1.5
AC_SUBST(LIBLTDL)
AC_LIBTOOL_DLOPEN
AC_CONFIG_SUBDIRS(libltdl)
+ AC_DEFINE(LIBTOOL_VERSION, 1, [Define to used libtool version.])
]
)
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=""
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])
libperl . . . . . . . $with_libperl
libpq . . . . . . . . $with_libpq
libpthread . . . . . $with_libpthread
+ librouteros . . . . . $with_librouteros
librrd . . . . . . . $with_librrd
libsensors . . . . . $with_libsensors
libstatgrab . . . . . $with_libstatgrab
libxmms . . . . . . . $with_libxmms
libyajl . . . . . . . $with_libyajl
oracle . . . . . . . $with_oracle
+ python . . . . . . . $with_python
Features:
daemon mode . . . . . $enable_daemon
powerdns . . . . . . $enable_powerdns
processes . . . . . . $enable_processes
protocols . . . . . . $enable_protocols
+ python . . . . . . . $enable_python
+ routeros . . . . . . $enable_routeros
rrdcached . . . . . . $enable_rrdcached
rrdtool . . . . . . . $enable_rrdtool
sensors . . . . . . . $enable_sensors
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
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
collectdmon.1 \
collectd-nagios.1 \
collectd-perl.5 \
+ collectd-python.5 \
collectd-snmp.5 \
collectd-unixsock.5 \
types.db.5
collectdmon.pod \
collectd-nagios.pod \
collectd-perl.pod \
+ collectd-python.pod \
collectd.pod \
collectd-snmp.pod \
collectd-unixsock.pod \
--- /dev/null
+=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
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>
=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)>,
*/
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
{
{"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);
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)
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)
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));
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);
}
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. */
{
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);
}
* 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. */
--- /dev/null
+/**
+ * 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;
{
#if HAVE_IOKIT_IOKITLIB_H
kern_return_t status;
-
+
if (io_master_port != MACH_PORT_NULL)
{
mach_port_deallocate (mach_task_self (),
char buffer[1024];
unsigned long long incoming, outgoing;
char *device;
-
+
char *dummy;
char *fields[16];
int numfields;
if (device[0] == '\0')
continue;
-
+
numfields = strsplit (dummy, fields, 16);
if (numfields < 11)
char *fields[8];
int numfields;
-
+
if ((loadavg = fopen ("/proc/loadavg", "r")) == NULL)
{
char errbuf[1024];
#elif KERNEL_LINUX
FILE *fh;
char buffer[1024];
-
+
char *fields[8];
int numfields;
* 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;
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 : */
* 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);
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 ();
}
#define BUFSIZE 512
-int plugin_load (const char *type)
+int plugin_load (const char *type, uint32_t flags)
{
DIR *dh;
const char *dir;
continue;
}
- if (plugin_load_file (filename) == 0)
+ if (plugin_load_file (filename, flags) == 0)
{
/* success */
ret = 0;
#include "configfile.h"
#include "meta_data.h"
+#define PLUGIN_FLAGS_GLOBAL 0x0001
+
#define DATA_MAX_NAME_LEN 64
#define DS_TYPE_COUNTER 0
*
* 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
* 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);
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);
return;
}
#endif
-
+
for (ptr = list_head_g; ptr != NULL; ptr = ptr->next)
{
if (strcmp (ptr->name, name) == 0)
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
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
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
--- /dev/null
+/**
+ * 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 */
+};
+
--- /dev/null
+/**
+ * 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);
+}
--- /dev/null
+/**
+ * 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(¬ification);
+ 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 */
+};
--- /dev/null
+/**
+ * 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 : */
#if KERNEL_LINUX
FILE *fh;
char buffer[1024];
-
+
char *fields[8];
int numfields;
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
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