Merge branch 'collectd-4.5' into collectd-4.6
authorFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sat, 9 May 2009 10:29:44 +0000 (12:29 +0200)
committerFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sat, 9 May 2009 10:29:44 +0000 (12:29 +0200)
146 files changed:
.gitignore
.mailmap [new file with mode: 0644]
AUTHORS
ChangeLog
README
TODO
bindings/perl/Collectd.pm
build.sh
configure.in
contrib/README
contrib/cussh.pl
contrib/exec-munin.px
contrib/fedora/collectd.spec
contrib/oracle/create_schema.ddl [new file with mode: 0644]
contrib/oracle/db_systat.sql [new file with mode: 0644]
contrib/redhat/collectd.conf
contrib/redhat/collectd.spec
contrib/solaris-smf/README [new file with mode: 0644]
contrib/solaris-smf/collectd [new file with mode: 0755]
contrib/solaris-smf/collectd.xml [new file with mode: 0644]
src/Makefile.am
src/apache.c
src/apcups.c
src/apple_sensors.c
src/ascent.c
src/battery.c
src/bind.c [new file with mode: 0644]
src/collectd-exec.pod
src/collectd-nagios.c
src/collectd-nagios.pod
src/collectd-perl.pod
src/collectd.c
src/collectd.conf.in
src/collectd.conf.pod
src/collectd.h
src/collectd.pod
src/collectdmon.c
src/common.c
src/common.h
src/configfile.c
src/cpu.c
src/cpufreq.c
src/csv.c
src/curl.c [new file with mode: 0644]
src/dbi.c [new file with mode: 0644]
src/df.c
src/disk.c
src/dns.c
src/email.c
src/entropy.c
src/exec.c
src/filecount.c
src/filter_chain.c [new file with mode: 0644]
src/filter_chain.h [new file with mode: 0644]
src/hddtemp.c
src/interface.c
src/ipmi.c
src/iptables.c
src/ipvs.c
src/irq.c
src/libcollectdclient/Makefile.am [new file with mode: 0644]
src/libcollectdclient/client.c [new file with mode: 0644]
src/libcollectdclient/client.h [new file with mode: 0644]
src/libcollectdclient/lcc_features.h.in [new file with mode: 0644]
src/libcollectdclient/libcollectdclient.pc.in [new file with mode: 0644]
src/libiptc/Makefile.am
src/libiptc/README.collectd [new file with mode: 0644]
src/libiptc/libip6tc.h
src/libiptc/libiptc.c
src/libiptc/libiptc.h
src/libiptc/libxtc.h [new file with mode: 0644]
src/libiptc/xtables.h [new file with mode: 0644]
src/load.c
src/match_regex.c [new file with mode: 0644]
src/match_timediff.c [new file with mode: 0644]
src/match_value.c [new file with mode: 0644]
src/mbmon.c
src/memcached.c
src/memory.c
src/meta_data.c [new file with mode: 0644]
src/meta_data.h [new file with mode: 0644]
src/multimeter.c
src/mysql.c
src/netlink.c
src/network.c
src/nfs.c
src/nginx.c
src/notify_email.c
src/ntpd.c
src/nut.c
src/onewire.c
src/openvpn.c [new file with mode: 0644]
src/oracle.c [new file with mode: 0644]
src/perl.c
src/ping.c
src/plugin.c
src/plugin.h
src/postgresql.c
src/postgresql_default.conf
src/powerdns.c
src/processes.c
src/rrdcached.c [new file with mode: 0644]
src/rrdtool.c
src/sensors.c
src/serial.c
src/snmp.c
src/swap.c
src/tail.c
src/tape.c
src/target_notification.c [new file with mode: 0644]
src/target_replace.c [new file with mode: 0644]
src/target_set.c [new file with mode: 0644]
src/tcpconns.c
src/teamspeak2.c
src/thermal.c
src/types.db
src/types.db.pod
src/unixsock.c
src/users.c
src/utils_cache.c
src/utils_cmd_flush.h
src/utils_cmd_getval.c
src/utils_cmd_getval.h
src/utils_cmd_listval.h
src/utils_cmd_putnotif.h
src/utils_cmd_putval.c
src/utils_cmd_putval.h
src/utils_complain.h
src/utils_db_query.c [new file with mode: 0644]
src/utils_db_query.h [new file with mode: 0644]
src/utils_dns.c
src/utils_dns.h
src/utils_match.c
src/utils_parse_option.c
src/utils_parse_option.h
src/utils_rrdcreate.c [new file with mode: 0644]
src/utils_rrdcreate.h [new file with mode: 0644]
src/utils_subst.c [new file with mode: 0644]
src/utils_subst.h [new file with mode: 0644]
src/utils_tail_match.c
src/utils_threshold.c
src/vmem.c
src/vserver.c
src/wireless.c
src/xmms.c
version-gen.sh

index 03bd0c4..1f491f3 100644 (file)
@@ -31,11 +31,9 @@ config.log
 config.status
 libtool
 src/.deps
-src/Makefile
 src/collectd.conf
 src/config.h
-src/libconfig/Makefile
-src/liboping/Makefile
+src/libcollectdclient/libcollectdclient.pc
 src/stamp-h1
 
 # make stuff:
@@ -50,6 +48,10 @@ src/collectd*.1
 src/collectd*.5
 src/types.db.5
 src/config.h.in~
+src/libcollectdclient/.libs
+src/libcollectdclient/*.la
+src/libcollectdclient/*.lo
+src/libcollectdclient/lcc_features.h
 src/libiptc/.libs
 src/libiptc/*.la
 src/libiptc/*.lo
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..9bb4399
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,8 @@
+Anthony <anthony>
+Florian Forster <octo>
+Florian Forster <octo@dev4.office.noris.de>
+Luboš Staněk <kolektor@atlas.cz>
+Luboš Staněk <lubek@users.sourceforge.net>
+Niki W. Waibel <niki>
+Sebastian Harl <tokkee>
+
diff --git a/AUTHORS b/AUTHORS
index 234d431..782e8f2 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -27,6 +27,12 @@ Anthony Gialluca <tonyabg at charter.net>
 Antony Dovgal <tony at daylessday.org>
  - memcached plugin.
 
+Bruno Prémont <bonbons at linux-vserver.org>
+ - BIND plugin.
+ - Many bugreports and -fixes in various plugins,
+   especially a nasty bug in the network plugin.
+ - Wireshark dissector.
+
 Christophe Kalt <collectd at klb.taranis.org>
  - The version 3 `log' mode.
  - Many Solaris related hints and fixes.
@@ -37,6 +43,15 @@ Dan Berrange <berrange at redhat.com>
 David Bacher <drbacher at gmail.com>
  - serial plugin.
 
+Doug MacEachern <dougm at hyperic.com>
+ - The `-T' option (config testing mode).
+ - OpenVPN plugin.
+ - jcollectd (two-way JMX integration).
+ - A few other patches to various plugins.
+
+Fabian Linzberger <e at lefant.net>
+ - Percentage aggregation for `collectd-nagios'.
+
 Flavio Stanchina <flavio at stanchina.net>
  - mbmon plugin.
 
@@ -54,9 +69,16 @@ Luke Herberling <collectd at c-ware.com>
 Lyonel Vincent <lyonel at ezix.org>
  - processes plugin.
 
-Michael Stapelberg <michael+git@stapelberg.de>
+Michael Stapelberg <michael+git at stapelberg.de>
  - OpenBSD port of the tcpconns plugin.
 
+Michał Mirosław <mirq-linux at rere.qmqm.pl>
+ - thermal plugin.
+ - Streamlines recursive directory traversion.
+
+Mirko Buffoni <briareos at eswat.org>
+ - Port/Socket selection in the MySQL plugin.
+
 Niki W. Waibel <niki.waibel at newlogic.com>
  - Initial autotools fixes.
  - libltdl code.
@@ -75,10 +97,16 @@ Peter Holik <peter at holik.at>
  - Some bugfixes in the exec plugin.
  - Notifications in the ipmi plugin.
 
+Piotr Hosowicz <the55 at wp.pl>
+ - SMF manifest for collectd.
+
 Richard W. M. Jones <rjones at redhat.com>
  - libvirt plugin.
  - uuid plugin.
 
+Roman Klesel <roman.klesel at noris.de>
+ - Oracle schema and sample SQL statements to be used with the Oracle plugin.
+
 Scott Garrett <sgarrett at technomancer.com>
  - tape plugin.
 
index 2c1f8d8..924d404 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,102 @@
+2009-03-18, Version 4.6.2
+       * collectd: Some Solaris utility code has been improved.
+       * filter subsystem: Allow `Chains' without default targets.
+       * liboping: A patch to comply with strict aliasing rules has been
+         added.
+       * timediff match: Fix a typo: The match was registered with a wrong
+         name which prevented this match to be used as documented. Thanks to
+         Bruno Prémont for finding this problem.
+       * bind plugin: Fix collection of the cached RR sets. The number of RR
+         sets currently in the cache was collected as a counter value, which
+         is nonsense. Thanks to Bruno Prémont for implementing this.
+       * dns plugin: Don't pass NULL to `pcap_open_live': Some systems,
+         primarily BSDs, don't take it well and crash.
+       * oracle plugin: Portability to 64 bit systems has been improved.
+       * postgresql plugin: The default configuration has been improved.
+       * rrdtool plugin: Fix a possible race condition: If the network plugin
+         is brought and dispatches a value before the rrdtool plugin is
+         initialized, the daemon may crash.
+
+2009-02-22, Version 4.6.1
+       * collectd: Many documentation fixes.
+       * Collectd::Unixsock: Error handling has been improved.
+       * regex match: Don't link with the PCRE library.
+       * bind plugin: Various bugs have been fixed. Thanks to Bruno Prémont
+         for finding and fixing most of them.
+       * ipmi plugin: Fix an off-by-one error which could cause segmentation
+         faults. Thanks to Peter Holik for his patch.
+
+2009-02-16, Version 4.6.0
+       * collectd: Added the `filter chain' infrastructure, which allows the
+         user to use `matches' and `targets' to control value processing.
+       * collectd: The new `-T' command line argument allows more in-depth
+         testing of a configuration. Thanks to Doug MacEachern for the patch.
+       * collectd-nagios: The Nagios integration command has been updated to
+         use libcollectdclient. The `percentage' aggregation function has
+         been added. Thanks to Fabian Linzberger for the patch.
+       * libcollectdclient: A library which abstracts communication with the
+         unixsock plugin for clients has been added.
+       * regex match: Match values by their identifies using regular
+         expressions.
+       * timediff match: Match for values with an invalid timestamp.
+       * value match: Select values by their data sources' values.
+       * notification target: Create and dispatch a notification.
+       * replace target: Replace parts of an identifier using regular
+         expressions.
+       * set target: Set (overwrite) entire parts of an identifier.
+       * bind plugin: This new plugin uses the new HTTP/XML interface to BIND
+         statistics, allowing very detailed name server statistics. Thanks to
+         Bruno Prémont for this plugin.
+       * cpu plugin: Report `interrupt' separately when using
+         sysctlbyname(3) (used under *BSD). Support for sysctl(3), for
+         example for native OpenBSD support, has been added. Thanks to Simon
+         Kuhnle for the patch.
+       * csv plugin: Make it possible to write values to STDOUT instead of
+         files. This is meant for testing purposes mostly. The output written
+         to STDOUT is compatible with the exec plugin. Thanks to Doug
+         MacEachern for the patch.
+       * curl plugin: This new plugin can be used to read web pages and parse
+         them using the same mechanism that's used in the tail plugin.
+       * dbi plugin: This new plugin allows you to connect to a variety of
+         relational databases and use SQL to gather custom statistics from
+         it. It is similar to the already existing PostgreSQL plugin but uses
+         libdbi to communicate with the database(s).
+       * interface plugin: Use the ignorelist framework when selecting /
+         ignoring interfaces. This allows one to use regular expressions to
+         select interfaces, too.
+       * ipmi plugin: Handle temporary IPMI error conditions more gracefully.
+         Thanks to Bruno Prémont for this patch.
+       * memcached plugin: Add hit-ratio metric. Thanks to Doug MacEachern
+         for the patch.
+       * mysql plugin: Allow connecting to a database via the UNIX domain
+         socket, too. Thanks to Mirko Buffoni for the patch.
+       * network plugin: Further performance improvements for the receive
+         code. This hopefully will help very large setups.
+       * openvpn plugin: This new plugin collects statistics provided by the
+         OpenVPN daemon. Thanks to Doug MacEachern for the patch.
+       * oracle plugin: This new plugin allows you to connect to an Oracle
+         database and use SQL to gather custom statistics from it. It is
+         similar to the already existing PostgreSQL plugin.
+       * perl plugin: Compatibility fixes for broken versions of Perl 5.10
+         have been added.
+       * perl plugin: Export the newly added plugin_write() to Perl plugins.
+       * perl plugin: Added support for `notification meta data'.
+       * perl plugin: Added support for the `filter chain' infrastructure by
+         allowing plugins to register `matches' and `targets'.
+       * postgresql plugin: The preferred configuration syntax has been
+         updated to be in line with the syntax used by the new dbi and oracle
+         plugins. The compatibility code for the old syntax is present.
+         Support for the new `Result' blocks and the interval parameter has
+         been added.
+       * processes plugin: Stacksize and virtual memory usage statistics have
+         been added. Portability fixes.
+       * rrdcached plugin: This new plugin uses the (still in development)
+         RRD accelerator daemon, rrdcached. This daemon works very similar to
+         the original rrdtool plugin of collectd, but adds some more nice
+         features.
+       * swap plugin: Code for OpenBSD (and possibly other *BSDs) has been
+         added.
+
 2009-02-22, Version 4.5.3
        * build system: The check for libupsclient even when `pkg-config' is
          not available.
diff --git a/README b/README
index 539574a..b641741 100644 (file)
--- a/README
+++ b/README
@@ -33,6 +33,13 @@ Features
       Batterycharge, -current and voltage of ACPI and PMU based laptop
       batteries.
 
+    - curl
+      Parse statistics from websites using regular expressions.
+
+    - bind
+      Name server and resolver statistics from the `statistics-channel'
+      interface of BIND 9.5, 9,6 and later.
+
     - cpu
       CPU utilization: Time spent in the system, user, nice, idle, and related
       states.
@@ -40,6 +47,10 @@ Features
     - cpufreq
       CPU frequency (For laptops with speed step or a similar technology)
 
+    - dbi
+      Executes SQL statements on various databases and interprets the returned
+      data.
+
     - df
       Mountpoint usage (Basically the values `df(1)' delivers)
 
@@ -76,6 +87,9 @@ Features
       Iptables' counters: Number of bytes that were matched by a certain
       iptables rule.
 
+    - ipmi
+      IPMI (Intelligent Platform Management Interface) sensors information.
+
     - ipvs
       IPVS connection statistics (number of connections, octets and packets
       for each service and destination).
@@ -139,6 +153,13 @@ Features
       Read onewire sensors using the owcapu library of the owfs project.
       Please read in collectd.conf(5) why this plugin is experimental.
 
+    - openvpn
+      RX and TX of each client in openvpn-status.log (status-version 2).
+      <http://openvpn.net/index.php/documentation/howto.html>
+
+    - oracle
+      Query data from an Oracle database.
+
     - perl
       The perl plugin implements a Perl-interpreter into collectd. You can
       write your own plugins in Perl and return arbitrary values using this
@@ -152,9 +173,15 @@ Features
       PostgreSQL database statistics: active server connections, transaction
       numbers, block IO, table row manipulations.
 
+    - powerdns
+      PowerDNS name server statistics.
+
     - processes
       Process counts: Number of running, sleeping, zombie, ... processes.
 
+    - rrdcached
+      RRDtool caching daemon (RRDcacheD) statistics.
+
     - sensors
       System sensors, accessed using lm_sensors: Voltages, temperatures and
       fan rotation speeds.
@@ -180,6 +207,12 @@ Features
     - tcpconns
       Number of TCP connections to specific local and remote ports.
 
+    - teamspeak2
+      TeamSpeak2 server statistics.
+
+    - thermal
+      Linux ACPI thermal zone information.
+
     - users
       Users currently logged in.
 
@@ -214,6 +247,11 @@ Features
       you can easily do weird stuff with the plugins we didn't dare think of
       ;) See collectd-perl(5).
 
+    - rrdcached
+      Output to round-robin-database (RRD) files using the RRDtool caching
+      daemon (RRDcacheD) - see rrdcached(1). That daemon provides a general
+      implementation of the caching done by the `rrdtool' plugin.
+
     - rrdtool
       Output to round-robin-database (RRD) files using librrd. See rrdtool(1).
       This is likely the most popular destination for such values. Since
@@ -265,6 +303,27 @@ Features
       Notifications are propagated to plugins written in Perl as well.
       See collectd-perl(5).
 
+  * Value processing can be controlled using the "filter chain" infrastructure
+    and "matches" and "targets". The following plugins are available:
+
+    - match_regex
+      Match values by their identifier based on regular expressions.
+
+    - match_timediff
+      Match values with an invalid timestamp.
+
+    - match_value
+      Select values by their data sources' values.
+
+    - target_notification
+      Create and dispatch a notification.
+
+    - target_replace
+      Replace parts of an identifier using regular expressions.
+
+    - target_set
+      Set (overwrite) entire parts of an identifier.
+
   * Miscellaneous plugins:
 
     - uuid
@@ -279,7 +338,7 @@ Features
     since collectd is programmed multithreaded it benefits from hyperthreading
     and multicore processors and makes sure that the daemon isn't idle if only
     one plugins waits for an IO-operation to complete.
-    
+
   * Once set up, hardly any maintenance is necessary. Setup is kept as easy
     as possible and the default values should be okay for most users.
 
@@ -339,76 +398,104 @@ Prerequisites
   * CoreFoundation.framework and IOKit.framework (optional)
     For compiling on Darwin in general and the `apple_sensors' plugin in
     particular.
+    <http://developer.apple.com/corefoundation/>
+
+  * libclntsh (optional)
+    Used by the `oracle' plugin.
 
   * libcurl (optional)
-    If you want to use the `apache', `ascent', or `nginx' plugin.
+    If you want to use the `apache', `ascent', `curl' or `nginx' plugin.
+    <http://curl.haxx.se/>
+
+  * libdbi (optional)
+    Used by the `dbi' plugin to connect to various databases.
+    <http://libdbi.sourceforge.net/>
 
   * libesmtp (optional)
     For the `notify_email' plugin.
+    <http://www.stafford.uklinux.net/libesmtp/>
 
   * libhal (optional)
     If present, the uuid plugin will check for UUID from HAL.
+    <http://hal.freedesktop.org/>
 
-  * libiptc (optional)
+  * libiptc (optional, if not found a version shipped with this distribution
+    can be used if the Linux kernel headers are available)
     For querying iptables counters.
+    <http://netfilter.org/>
 
   * libmysqlclient (optional)
     Unsurprisingly used by the `mysql' plugin.
+    <http://dev.mysql.com/>
 
   * libnetlink (optional)
     Used, obviously, for the `netlink' plugin.
+    <http://www.linuxfoundation.org/en/Net:Iproute2>
 
   * libnetsnmp (optional)
     For the `snmp' plugin.
+    <http://www.net-snmp.org/>
 
   * libnotify (optional)
     For the `notify_desktop' plugin.
+    <http://www.galago-project.org/>
 
   * liboping (optional, if not found a version shipped with this distribution
     can be used)
     Used by the `ping' plugin to send and receive ICMP packets.
+    <http://verplant.org/liboping/>
 
   * libowcapi (optional)
     Used by the `onewire' plugin to read values from onewire sensors (or the
     owserver(1) daemon).
+    <http://www.owfs.org/>
 
   * libpcap (optional)
     Used to capture packets by the `dns' plugin.
+    <http://www.tcpdump.org/>
 
   * libperl (optional)
     Obviously used by the `perl' plugin. The library has to be compiled with
     ithread support (introduced in Perl 5.6.0).
+    <http://www.perl.org/>
 
   * libpq (optional)
     The PostgreSQL C client library used by the `postgresql' plugin.
+    <http://www.postgresql.org/>
 
-  * librrd (optional; headers and library; rrdtool 1.0 and 1.2 both work fine)
-    If built without `librrd' the resulting binary will be `client only', i.e.
-    will send its values via multicast and not create any RRD files itself.
-    Alternatively you can chose to write CSV-files (Comma Separated Values)
-    instead.
+  * 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,
+    1.2 and 1.3 are known to work with the `rrdtool' plugin.
+    <http://oss.oetiker.ch/rrdtool/>
 
   * librt, libsocket, libkstat, libdevinfo (optional)
     Various standard Solaris libraries which provide system functions.
+    <http://developers.sun.com/solaris/>
 
   * libsensors (optional)
     To read from `lm_sensors', see the `sensors' plugin.
+    <http://www.lm-sensors.org/>
 
-  * libstatgrab (optional) may be used to collect statistics on systems other
-    than Linux and/or Solaris. Note that CPU- and disk-statistics, while being
-    provided by this library, are not supported in collectd right now..
-    <http://www.i-scream.org/libstatgrab/> 
+  * libstatgrab (optional)
+    Used by various plugins to collect statistics on systems other than Linux
+    and/or Solaris.
+    <http://www.i-scream.org/libstatgrab/>
 
   * libupsclient/nut (optional)
     For the `nut' plugin which queries nut's `upsd'.
+    <http://networkupstools.org/>
 
   * libvirt (optional)
     Collect statistics from virtual machines.
+    <http://libvirt.org/>
 
   * libxml2 (optional)
     Parse XML data. This is needed for the `ascent' and `libvirt' plugins.
+    <http://xmlsoft.org/>
 
   * libxmms (optional)
+    <http://www.xmms.org/>
 
 
 Configuring / Compiling / Installing
@@ -418,7 +505,7 @@ Configuring / Compiling / Installing
   `./configure && make && make install'.  For detailed, generic instructions
   see INSTALL. For a complete list of configure options and their description,
   run `./configure --help'.
-  
+
   By default, the configure script will check for all build dependencies and
   disable all plugins whose requirements cannot be fulfilled (any other plugin
   will be enabled). To enable a plugin, install missing dependencies (see
diff --git a/TODO b/TODO
index b5cd6b5..3c8f49e 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,6 @@
-* Parse options/identifiers with spaces in them correctly.
-* Figure out what to do with the onewire plugin.
+* Finalize the onewire plugin.
 * Custom notification messages?
+* Implement moving-average calculation for the threshold stuff.
 
 src/battery.c: commend not working code.
 
@@ -10,6 +10,7 @@ Wishlist:
   - be free of syntax errors.
 * Port nfs module to solaris
 * Port tape module to Linux
+* Port the apple_sensors plugin to Linux/PPC.
 * Maybe look into porting the serial module
 * Build Darwin package
 * Maybe let the network plugin configure whether or not notifications should be
index 738206b..557950c 100644 (file)
@@ -1,5 +1,5 @@
 # collectd - Collectd.pm
-# Copyright (C) 2007, 2008  Sebastian Harl
+# Copyright (C) 2007-2009  Sebastian Harl
 #
 # This program is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by the
@@ -42,6 +42,7 @@ our %EXPORT_TAGS = (
                        plugin_register
                        plugin_unregister
                        plugin_dispatch_values
+                       plugin_write
                        plugin_flush
                        plugin_flush_one
                        plugin_flush_all
@@ -75,6 +76,18 @@ our %EXPORT_TAGS = (
                        LOG_INFO
                        LOG_DEBUG
        ) ],
+       'filter_chain' => [ qw(
+                       fc_register
+                       FC_MATCH_NO_MATCH
+                       FC_MATCH_MATCHES
+                       FC_TARGET_CONTINUE
+                       FC_TARGET_STOP
+                       FC_TARGET_RETURN
+       ) ],
+       'fc_types' => [ qw(
+                       FC_MATCH
+                       FC_TARGET
+       ) ],
        'notif' => [ qw(
                        NOTIF_FAILURE
                        NOTIF_WARNING
@@ -99,6 +112,7 @@ our $interval_g;
 Exporter::export_ok_tags ('all');
 
 my @plugins : shared = ();
+my @fc_plugins : shared = ();
 my %cf_callbacks : shared = ();
 
 my %types = (
@@ -111,10 +125,24 @@ my %types = (
        TYPE_FLUSH,    "flush"
 );
 
+my %fc_types = (
+       FC_MATCH,  "match",
+       FC_TARGET, "target"
+);
+
+my %fc_exec_names = (
+       FC_MATCH,  "match",
+       FC_TARGET, "invoke"
+);
+
 foreach my $type (keys %types) {
        $plugins[$type] = &share ({});
 }
 
+foreach my $type (keys %fc_types) {
+       $fc_plugins[$type] = &share ({});
+}
+
 sub _log {
        my $caller = shift;
        my $lvl    = shift;
@@ -318,6 +346,66 @@ sub plugin_unregister {
        }
 }
 
+sub plugin_write {
+       my %args = @_;
+
+       my @plugins    = ();
+       my @datasets   = ();
+       my @valuelists = ();
+
+       if (! defined $args{'valuelists'}) {
+               ERROR ("Collectd::plugin_write: Missing 'valuelists' argument.");
+               return;
+       }
+
+       DEBUG ("Collectd::plugin_write:"
+               . (defined ($args{'plugins'}) ? " plugins = $args{'plugins'}" : "")
+               . (defined ($args{'datasets'}) ? " datasets = $args{'datasets'}" : "")
+               . " valueslists = $args{'valuelists'}");
+
+       if (defined ($args{'plugins'})) {
+               if ("ARRAY" eq ref ($args{'plugins'})) {
+                       @plugins = @{$args{'plugins'}};
+               }
+               else {
+                       @plugins = ($args{'plugins'});
+               }
+       }
+       else {
+               @plugins = (undef);
+       }
+
+       if ("ARRAY" eq ref ($args{'valuelists'})) {
+               @valuelists = @{$args{'valuelists'}};
+       }
+       else {
+               @valuelists = ($args{'valuelists'});
+       }
+
+       if (defined ($args{'datasets'})) {
+               if ("ARRAY" eq ref ($args{'datasets'})) {
+                       @datasets = @{$args{'datasets'}};
+               }
+               else {
+                       @datasets = ($args{'datasets'});
+               }
+       }
+       else {
+               @datasets = (undef) x scalar (@valuelists);
+       }
+
+       if ($#datasets != $#valuelists) {
+               ERROR ("Collectd::plugin_write: Invalid number of datasets.");
+               return;
+       }
+
+       foreach my $plugin (@plugins) {
+               for (my $i = 0; $i < scalar (@valuelists); ++$i) {
+                       _plugin_write ($plugin, $datasets[$i], $valuelists[$i]);
+               }
+       }
+}
+
 sub plugin_flush {
        my %args = @_;
 
@@ -395,6 +483,141 @@ sub plugin_flush_all {
        plugin_flush (timeout => $timeout);
 }
 
+sub fc_call {
+       my $type    = shift;
+       my $name    = shift;
+       my $cb_type = shift;
+
+       my %proc;
+
+       our $cb_name = undef;
+       my  $status;
+
+       if (! ((defined $type) && (defined $name) && (defined $cb_type))) {
+               ERROR ("Usage: Collectd::fc_call(type, name, cb_type, ...)");
+               return;
+       }
+
+       if (! defined $fc_plugins[$type]) {
+               ERROR ("Collectd::fc_call: Invalid type \"$type\"");
+               return;
+       }
+
+       if (! defined $fc_plugins[$type]->{$name}) {
+               ERROR ("Collectd::fc_call: Unknown "
+                       . ($type == FC_MATCH ? "match" : "target")
+                       . " \"$name\"");
+               return;
+       }
+
+       DEBUG ("Collectd::fc_call: "
+               . "type = \"$type\", name = \"$name\", cb_type = \"$cb_type\"");
+
+       {
+               lock %{$fc_plugins[$type]};
+               %proc = %{$fc_plugins[$type]->{$name}};
+       }
+
+       if (FC_CB_EXEC == $cb_type) {
+               $cb_name = $proc{$fc_exec_names{$type}};
+       }
+       elsif (FC_CB_CREATE == $cb_type) {
+               if (defined $proc{'create'}) {
+                       $cb_name = $proc{'create'};
+               }
+               else {
+                       return 1;
+               }
+       }
+       elsif (FC_CB_DESTROY == $cb_type) {
+               if (defined $proc{'destroy'}) {
+                       $cb_name = $proc{'destroy'};
+               }
+               else {
+                       return 1;
+               }
+       }
+
+       $status = call_by_name (@_);
+
+       if ($status < 0) {
+               my $err = undef;
+
+               if ($@) {
+                       $err = $@;
+               }
+               else {
+                       $err = "callback returned false";
+               }
+
+               ERROR ("Execution of fc callback \"$cb_name\" failed: $err");
+               return;
+       }
+       return $status;
+}
+
+sub fc_register {
+       my $type = shift;
+       my $name = shift;
+       my $proc = shift;
+
+       my %fc : shared;
+
+       DEBUG ("Collectd::fc_register: "
+               . "type = \"$type\", name = \"$name\", proc = \"$proc\"");
+
+       if (! ((defined $type) && (defined $name) && (defined $proc))) {
+               ERROR ("Usage: Collectd::fc_register(type, name, proc)");
+               return;
+       }
+
+       if (! defined $fc_plugins[$type]) {
+               ERROR ("Collectd::fc_register: Invalid type \"$type\"");
+               return;
+       }
+
+       if (("HASH" ne ref ($proc)) || (! defined $proc->{$fc_exec_names{$type}})
+                       || ("" ne ref ($proc->{$fc_exec_names{$type}}))) {
+               ERROR ("Collectd::fc_register: Invalid proc.");
+               return;
+       }
+
+       for my $p (qw( create destroy )) {
+               if ((defined $proc->{$p}) && ("" ne ref ($proc->{$p}))) {
+                       ERROR ("Collectd::fc_register: Invalid proc.");
+                       return;
+               }
+       }
+
+       %fc = %$proc;
+
+       foreach my $p (keys %fc) {
+               my $pkg = scalar caller;
+
+               if ($p !~ m/^(create|destroy|$fc_exec_names{$type})$/) {
+                       next;
+               }
+
+               if ($fc{$p} !~ m/^$pkg\:\:/) {
+                       $fc{$p} = $pkg . "::" . $fc{$p};
+               }
+       }
+
+       lock %{$fc_plugins[$type]};
+       if (defined $fc_plugins[$type]->{$name}) {
+               WARNING ("Collectd::fc_register: Overwriting previous "
+                       . "definition of match \"$name\".");
+       }
+
+       if (! _fc_register ($type, $name)) {
+               ERROR ("Collectd::fc_register: Failed to register \"$name\".");
+               return;
+       }
+
+       $fc_plugins[$type]->{$name} = \%fc;
+       return 1;
+}
+
 sub _plugin_dispatch_config {
        my $plugin = shift;
        my $config = shift;
index 3822c2b..804b2e2 100755 (executable)
--- a/build.sh
+++ b/build.sh
@@ -25,20 +25,24 @@ check_for_application lex yacc autoheader aclocal automake autoconf
 check_for_application pkg-config
 
 libtoolize=""
-if which libtoolize >/dev/null 2>&1
+libtoolize --version >/dev/null 2>/dev/null
+if test $? -eq 0
 then
        libtoolize=libtoolize
-else if which glibtoolize >/dev/null 2>&1
-then
-       libtoolize=glibtoolize
 else
-       cat >&2 <<EOF
+       glibtoolize --version >/dev/null 2>/dev/null
+       if test $? -eq 0
+       then
+               libtoolize=glibtoolize
+       else
+               cat >&2 <<EOF
 WARNING: Neither \`libtoolize' nor \`glibtoolize' have been found!
     Please make sure that one of them is installed and is in one of the
     directories listed in the PATH environment variable.
 EOF
-       GLOBAL_ERROR_INDICATOR=1
-fi; fi
+               GLOBAL_ERROR_INDICATOR=1
+       fi
+ fi
 
 if test "$GLOBAL_ERROR_INDICATOR" != "0"
 then
index 2c51d21..795a9e1 100644 (file)
@@ -70,6 +70,10 @@ then
        CPPFLAGS="$CPPFLAGS -D_POSIX_PTHREAD_SEMANTICS"
 fi
 
+# Where to install .pc files.
+pkgconfigdir="${libdir}/pkgconfig"
+AC_SUBST(pkgconfigdir)
+
 #
 # Checks for header files.
 #
@@ -408,7 +412,7 @@ AC_HEADER_TIME
 # Checks for library functions.
 #
 AC_PROG_GCC_TRADITIONAL
-AC_CHECK_FUNCS(gettimeofday select strdup strtol getaddrinfo getnameinfo strchr memcpy strstr strcmp strncmp strncpy strlen strncasecmp strcasecmp openlog closelog)
+AC_CHECK_FUNCS(gettimeofday select strdup strtol getaddrinfo getnameinfo strchr memcpy strstr strcmp strncmp strncpy strlen strncasecmp strcasecmp openlog closelog sysconf)
 
 AC_FUNC_STRERROR_R
 
@@ -484,6 +488,7 @@ nanosleep_needs_rt="no"
 AC_CHECK_FUNCS(nanosleep, [], AC_CHECK_LIB(rt, nanosleep, [nanosleep_needs_rt="yes"], AC_MSG_ERROR(cannot find nanosleep)))
 AM_CONDITIONAL(BUILD_WITH_LIBRT, test "x$nanosleep_needs_rt" = "xyes")
 
+AC_CHECK_FUNCS(sysctl, [have_sysctl="yes"], [have_sysctl="no"])
 AC_CHECK_FUNCS(sysctlbyname, [have_sysctlbyname="yes"], [have_sysctlbyname="no"])
 AC_CHECK_FUNCS(host_statistics, [have_host_statistics="yes"], [have_host_statistics="no"])
 AC_CHECK_FUNCS(processor_info, [have_processor_info="yes"], [have_processor_info="no"])
@@ -494,6 +499,7 @@ AC_CHECK_FUNCS(getifaddrs, [have_getifaddrs="yes"], [have_getifaddrs="no"])
 AC_CHECK_FUNCS(syslog, [have_syslog="yes"], [have_syslog="no"])
 AC_CHECK_FUNCS(getutent, [have_getutent="yes"], [have_getutent="no"])
 AC_CHECK_FUNCS(getutxent, [have_getutxent="yes"], [have_getutxent="no"])
+AC_CHECK_FUNCS(swapctl, [have_swapctl="yes"], [have_swapctl="no"])
 
 # For load module
 AC_CHECK_FUNCS(getloadavg, [have_getloadavg="yes"], [have_getloadavg="no"])
@@ -629,8 +635,13 @@ if test "x$fp_layout_type" = "xunknown"; then
       [[[[
 #include <stdlib.h>
 #include <stdio.h>
-#include <stdint.h>
 #include <string.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#endif
       ]]]],
       [[[[
        uint64_t i0;
@@ -668,8 +679,13 @@ if test "x$fp_layout_type" = "xunknown"; then
       [[[[
 #include <stdlib.h>
 #include <stdio.h>
-#include <stdint.h>
 #include <string.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#endif
 #define endianflip(A) ((((uint64_t)(A) & 0xff00000000000000LL) >> 56) | \
                        (((uint64_t)(A) & 0x00ff000000000000LL) >> 40) | \
                        (((uint64_t)(A) & 0x0000ff0000000000LL) >> 24) | \
@@ -715,8 +731,13 @@ if test "x$fp_layout_type" = "xunknown"; then
       [[[[
 #include <stdlib.h>
 #include <stdio.h>
-#include <stdint.h>
 #include <string.h>
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_STDBOOL_H
+# include <stdbool.h>
+#endif
 #define intswap(A)    ((((uint64_t)(A) & 0xffffffff00000000LL) >> 32) | \
                        (((uint64_t)(A) & 0x00000000ffffffffLL) << 32))
       ]]]],
@@ -970,102 +991,6 @@ fi
 m4_divert_once([HELP_WITH], [
 collectd additional packages:])
 
-# AC_ARG_WITH (package, help-string, [action-if-given], [action-if-not-given])
-librrd_cflags=""
-librrd_ldflags=""
-librrd_threadsafe="yes"
-AC_ARG_WITH(rrdtool, [AS_HELP_STRING([--with-rrdtool@<:@=PREFIX@:>@], [Path to rrdtool.])],
-[      if test "x$withval" != "xno" && test "x$withval" != "xyes"
-       then
-               librrd_cflags="-I$withval/include"
-               librrd_ldflags="-L$withval/lib"
-               with_rrdtool="yes"
-       else
-               with_rrdtool="$withval"
-       fi
-], [with_rrdtool="yes"])
-if test "x$with_rrdtool" = "xyes"
-then
-       SAVE_CPPFLAGS="$CPPFLAGS"
-       SAVE_LDFLAGS="$LDFLAGS"
-
-       CPPFLAGS="$CPPFLAGS $librrd_cflags"
-       LDFLAGS="$LDFLAGS $librrd_ldflags"
-
-       AC_CHECK_HEADERS(rrd.h,, [with_rrdtool="no (rrd.h not found)"])
-
-       CPPFLAGS="$SAVE_CPPFLAGS"
-       LDFLAGS="$SAVE_LDFLAGS"
-fi
-if test "x$with_rrdtool" = "xyes"
-then
-       SAVE_CPPFLAGS="$CPPFLAGS"
-       SAVE_LDFLAGS="$LDFLAGS"
-
-       CPPFLAGS="$CPPFLAGS $librrd_cflags"
-       LDFLAGS="$LDFLAGS $librrd_ldflags"
-
-       AC_CHECK_LIB(rrd_th, rrd_update_r,
-       [with_rrdtool="yes"
-        librrd_ldflags="$librrd_ldflags -lrrd_th -lm"
-       ],
-       [librrd_threadsafe="no"
-        AC_CHECK_LIB(rrd, rrd_update,
-        [with_rrdtool="yes"
-         librrd_ldflags="$librrd_ldflags -lrrd -lm"
-        ],
-        [with_rrdtool="no (symbol 'rrd_update' not found)"],
-        [-lm])
-       ],
-       [-lm])
-
-       CPPFLAGS="$SAVE_CPPFLAGS"
-       LDFLAGS="$SAVE_LDFLAGS"
-fi
-if test "x$with_rrdtool" = "xyes"
-then
-       BUILD_WITH_LIBRRD_CFLAGS="$librrd_cflags"
-       BUILD_WITH_LIBRRD_LDFLAGS="$librrd_ldflags"
-       AC_SUBST(BUILD_WITH_LIBRRD_CFLAGS)
-       AC_SUBST(BUILD_WITH_LIBRRD_LDFLAGS)
-fi
-if test "x$librrd_threadsafe" = "xyes"
-then
-       AC_DEFINE(HAVE_THREADSAFE_LIBRRD, 1, [Define to 1 if you have the threadsafe rrd library (-lrrd_th).])
-fi
-
-AC_ARG_WITH(libpthread, [AS_HELP_STRING([--with-libpthread=@<:@=PREFIX@:>@], [Path to libpthread.])],
-[      if test "x$withval" != "xno" \
-               && test "x$withval" != "xyes"
-       then
-               LDFLAGS="$LDFLAGS -L$withval/lib"
-               CPPFLAGS="$CPPFLAGS -I$withval/include"
-               with_libpthread="yes"
-       else
-               if test "x$withval" = "xno"
-               then
-                       with_libpthread="no (disabled)"
-               fi
-       fi
-], [with_libpthread="yes"])
-if test "x$with_libpthread" = "xyes"
-then
-       AC_CHECK_LIB(pthread, pthread_create, [with_libpthread="yes"], [with_libpthread="no (libpthread not found)"], [])
-fi
-if test "x$with_libpthread" = "xyes"
-then
-       AC_CHECK_HEADERS(pthread.h,, [with_libpthread="no (pthread.h not found)"])
-fi
-if test "x$with_libpthread" = "xyes"
-then
-       collect_pthread=1
-else
-       collect_pthread=0
-fi
-AC_DEFINE_UNQUOTED(HAVE_LIBPTHREAD, [$collect_pthread],
-       [Wether or not to use pthread (POSIX threads) library])
-AM_CONDITIONAL(BUILD_WITH_LIBPTHREAD, test "x$with_libpthread" = "xyes")
-
 if test "x$ac_system" = "xSolaris"
 then
        with_kstat="yes"
@@ -1092,7 +1017,54 @@ fi
 AM_CONDITIONAL(BUILD_WITH_LIBKSTAT, test "x$with_kstat" = "xyes")
 AM_CONDITIONAL(BUILD_WITH_LIBDEVINFO, test "x$with_devinfo" = "xyes")
 
-### BEGIN of check for libcurl ###
+with_libiokit="no"
+AC_CHECK_LIB(IOKit, IOServiceGetMatchingServices,
+[
+       with_libiokit="yes"
+], 
+[
+       with_libiokit="no"
+])
+AM_CONDITIONAL(BUILD_WITH_LIBIOKIT, test "x$with_libiokit" = "xyes")
+
+with_libkvm="no"
+AC_CHECK_LIB(kvm, kvm_getprocs, [with_kvm_getprocs="yes"], [with_kvm_getprocs="no"])
+if test "x$with_kvm_getprocs" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_GETPROCS, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_getprocs' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_GETPROCS, test "x$with_kvm_getprocs" = "xyes")
+
+AC_CHECK_LIB(kvm, kvm_getswapinfo, [with_kvm_getswapinfo="yes"], [with_kvm_getswapinfo="no"])
+if test "x$with_kvm_getswapinfo" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_GETSWAPINFO, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_getswapinfo' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_GETSWAPINFO, test "x$with_kvm_getswapinfo" = "xyes")
+
+AC_CHECK_LIB(kvm, kvm_nlist, [with_kvm_nlist="yes"], [with_kvm_nlist="no"])
+if test "x$with_kvm_nlist" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_NLIST, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_nlist' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_NLIST, test "x$with_kvm_nlist" = "xyes")
+
+AC_CHECK_LIB(kvm, kvm_openfiles, [with_kvm_openfiles="yes"], [with_kvm_openfiles="no"])
+if test "x$with_kvm_openfiles" = "xyes"
+then
+       AC_DEFINE(HAVE_LIBKVM_NLIST, 1,
+                 [Define to 1 if you have the 'kvm' library with the 'kvm_openfiles' symbol (-lkvm)])
+       with_libkvm="yes"
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBKVM_OPENFILES, test "x$with_kvm_openfiles" = "xyes")
+
+# --with-libcurl {{{
 with_curl_config="curl-config"
 with_curl_cflags=""
 with_curl_libs=""
@@ -1160,230 +1132,166 @@ then
        AC_SUBST(BUILD_WITH_LIBCURL_LIBS)
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBCURL, test "x$with_libcurl" = "xyes")
-### END of check for libcurl ###
-
-with_libiokit="no"
-AC_CHECK_LIB(IOKit, IOServiceGetMatchingServices,
-[
-       with_libiokit="yes"
-], 
-[
-       with_libiokit="no"
-])
-AM_CONDITIONAL(BUILD_WITH_LIBIOKIT, test "x$with_libiokit" = "xyes")
+# }}}
 
-with_libstatgrab_cflags=""
-with_libstatgrab_ldflags=""
-AC_ARG_WITH(libstatgrab, [AS_HELP_STRING([--with-libstatgrab@<:@=PREFIX@:>@], [Path to libstatgrab.])],
+# --with-libdbi {{{
+with_libdbi_cppflags=""
+with_libdbi_ldflags=""
+AC_ARG_WITH(libdbi, [AS_HELP_STRING([--with-libdbi@<:@=PREFIX@:>@], [Path to libdbi.])],
 [
-       if test "x$withval" != "xno" \
-               && test "x$withval" != "xyes"
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
        then
-               with_libstatgrab_cflags="-I$withval/include"
-               with_libstatgrab_ldflags="-L$withval/lib"
-               with_libstatgrab="yes"
+               with_libdbi_cppflags="-I$withval/include"
+               with_libdbi_ldflags="-L$withval/lib"
+               with_libdbi="yes"
        else
-               with_libstatgrab="$withval"
+               with_libdbi="$withval"
        fi
 ],
 [
-       if test "x$ac_system" = "xunknown"
-       then
-               with_libstatgrab="yes"
-       else
-               with_libstatgrab="no"
-       fi
+       with_libdbi="yes"
 ])
-with_libstatgrab_pkg_config="yes"
-if test "x$with_libstatgrab" = "xyes" \
-  && test "x$PKG_CONFIG" != "x"
+if test "x$with_libdbi" = "xyes"
 then
-  AC_MSG_CHECKING([pkg-config for libstatgrab])
-  temp_result="found"
-  $PKG_CONFIG --exists libstatgrab 2>/dev/null
-  if test "$?" != "0"
-  then
-    with_libstatgrab_pkg_config="no"
-    temp_result="not found"
-  fi
-  AC_MSG_RESULT([$temp_result])
-else
-  AC_MSG_NOTICE([pkg-config not available, trying to guess flags for the statgrab library.])
-  with_libstatgrab_pkg_config="no"
-  with_libstatgrab_ldflags="$with_libstatgrab_ldflags -lstatgrab"
-fi
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libdbi_cppflags"
 
-if test "x$with_libstatgrab" = "xyes" \
-  && test "x$with_libstatgrab_pkg_config" = "xyes" \
-  && test "x$with_libstatgrab_cflags" = "x"
-then
-  AC_MSG_CHECKING([for libstatgrab CFLAGS])
-  temp_result="`$PKG_CONFIG --cflags libstatgrab`"
-  if test "$?" = "0"
-  then
-    with_libstatgrab_cflags="$temp_result"
-  else
-    with_libstatgrab="no ($PKG_CONFIG --cflags libstatgrab failed)"
-    temp_result="$PKG_CONFIG --cflags libstatgrab failed"
-  fi
-  AC_MSG_RESULT([$temp_result])
-fi
+       AC_CHECK_HEADERS(dbi/dbi.h, [with_libdbi="yes"], [with_libdbi="no (dbi/dbi.h not found)"])
 
-if test "x$with_libstatgrab" = "xyes" \
-  && test "x$with_libstatgrab_pkg_config" = "xyes" \
-  && test "x$with_libstatgrab_ldflags" = "x"
-then
-  AC_MSG_CHECKING([for libstatgrab LDFLAGS])
-  temp_result="`$PKG_CONFIG --libs libstatgrab`"
-  if test "$?" = "0"
-  then
-    with_libstatgrab_ldflags="$temp_result"
-  else
-    with_libstatgrab="no ($PKG_CONFIG --libs libstatgrab failed)"
-    temp_result="$PKG_CONFIG --libs libstatgrab failed"
-  fi
-  AC_MSG_RESULT([$temp_result])
+       CPPFLAGS="$SAVE_CPPFLAGS"
 fi
-
-if test "x$with_libstatgrab" = "xyes"
+if test "x$with_libdbi" = "xyes"
 then
-  SAVE_CPPFLAGS="$CPPFLAGS"
-  CPPFLAGS="$CPPFLAGS $with_libstatgrab_cflags"
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libdbi_cppflags"
+       LDFLAGS="$LDFLAGS $with_libdbi_ldflags"
 
-  AC_CHECK_HEADERS(statgrab.h,
-                  [with_libstatgrab="yes"],
-                  [with_libstatgrab="no (statgrab.h not found)"])
+       AC_CHECK_LIB(dbi, dbi_initialize, [with_libdbi="yes"], [with_libdbi="no (Symbol 'dbi_initialize' not found)"])
 
-  CPPFLAGS="$SAVE_CPPFLAGS"
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
 fi
-
-if test "x$with_libstatgrab" = "xyes"
+if test "x$with_libdbi" = "xyes"
 then
-  SAVE_CFLAGS="$CFLAGS"
-  SAVE_LDFLAGS="$LDFLAGS"
-
-  CFLAGS="$CFLAGS $with_libstatgrab_cflags"
-  LDFLAGS="$LDFLAGS $with_libstatgrab_ldflags"
-
-  AC_CHECK_LIB(statgrab, sg_init,
-              [with_libstatgrab="yes"],
-              [with_libstatgrab="no (symbol sg_init not found)"])
-
-  CFLAGS="$SAVE_CFLAGS"
-  LDFLAGS="$SAVE_LDFLAGS"
+       BUILD_WITH_LIBDBI_CPPFLAGS="$with_libdbi_cppflags"
+       BUILD_WITH_LIBDBI_LDFLAGS="$with_libdbi_ldflags"
+       BUILD_WITH_LIBDBI_LIBS="-ldbi"
+       AC_SUBST(BUILD_WITH_LIBDBI_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBDBI_LDFLAGS)
+       AC_SUBST(BUILD_WITH_LIBDBI_LIBS)
 fi
+AM_CONDITIONAL(BUILD_WITH_LIBDBI, test "x$with_libdbi" = "xyes")
+# }}}
 
-AM_CONDITIONAL(BUILD_WITH_LIBSTATGRAB, test "x$with_libstatgrab" = "xyes")
-if test "x$with_libstatgrab" = "xyes"
+# --with-libesmtp {{{
+AC_ARG_WITH(libesmtp, [AS_HELP_STRING([--with-libesmtp@<:@=PREFIX@:>@], [Path to libesmtp.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include -D_THREAD_SAFE"
+               with_libesmtp="yes"
+       else
+               with_libesmtp="$withval"
+       fi
+],
+[
+       with_libesmtp="yes"
+])
+if test "x$with_libesmtp" = "xyes"
 then
-  AC_DEFINE(HAVE_LIBSTATGRAB, 1, [Define to 1 if you have the 'statgrab' library (-lstatgrab)])
-  BUILD_WITH_LIBSTATGRAB_CFLAGS="$with_libstatgrab_cflags"
-  BUILD_WITH_LIBSTATGRAB_LDFLAGS="$with_libstatgrab_ldflags"
-  AC_SUBST(BUILD_WITH_LIBSTATGRAB_CFLAGS)
-  AC_SUBST(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
+       AC_CHECK_LIB(esmtp, smtp_create_session,
+       [
+               AC_DEFINE(HAVE_LIBESMTP, 1, [Define to 1 if you have the esmtp library (-lesmtp).])
+       ], [with_libesmtp="no (libesmtp not found)"])
 fi
-
-with_libkvm="no"
-AC_CHECK_LIB(kvm, kvm_getprocs, [with_kvm_getprocs="yes"], [with_kvm_getprocs="no"])
-if test "x$with_kvm_getprocs" = "xyes"
+if test "x$with_libesmtp" = "xyes"
 then
-       AC_DEFINE(HAVE_LIBKVM_GETPROCS, 1,
-                 [Define to 1 if you have the 'kvm' library with the 'kvm_getprocs' symbol (-lkvm)])
-       with_libkvm="yes"
-fi
-AM_CONDITIONAL(BUILD_WITH_LIBKVM_GETPROCS, test "x$with_kvm_getprocs" = "xyes")
-
-AC_CHECK_LIB(kvm, kvm_getswapinfo, [with_kvm_getswapinfo="yes"], [with_kvm_getswapinfo="no"])
-if test "x$with_kvm_getswapinfo" = "xyes"
-then
-       AC_DEFINE(HAVE_LIBKVM_GETSWAPINFO, 1,
-                 [Define to 1 if you have the 'kvm' library with the 'kvm_getswapinfo' symbol (-lkvm)])
-       with_libkvm="yes"
-fi
-AM_CONDITIONAL(BUILD_WITH_LIBKVM_GETSWAPINFO, test "x$with_kvm_getswapinfo" = "xyes")
-
-AC_CHECK_LIB(kvm, kvm_nlist, [with_kvm_nlist="yes"], [with_kvm_nlist="no"])
-if test "x$with_kvm_nlist" = "xyes"
-then
-       AC_DEFINE(HAVE_LIBKVM_NLIST, 1,
-                 [Define to 1 if you have the 'kvm' library with the 'kvm_nlist' symbol (-lkvm)])
-       with_libkvm="yes"
+       AC_CHECK_HEADERS(libesmtp.h,
+       [
+               AC_DEFINE(HAVE_LIBESMTP_H, 1, [Define to 1 if you have the <libesmtp.h> header file.])
+       ], [with_libesmtp="no (libesmtp.h not found)"])
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBKVM_NLIST, test "x$with_kvm_nlist" = "xyes")
-
-AC_CHECK_LIB(kvm, kvm_openfiles, [with_kvm_openfiles="yes"], [with_kvm_openfiles="no"])
-if test "x$with_kvm_openfiles" = "xyes"
+if test "x$with_libesmtp" = "xyes"
 then
-       AC_DEFINE(HAVE_LIBKVM_NLIST, 1,
-                 [Define to 1 if you have the 'kvm' library with the 'kvm_openfiles' symbol (-lkvm)])
-       with_libkvm="yes"
+       collect_libesmtp=1
+else
+       collect_libesmtp=0
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBKVM_OPENFILES, test "x$with_kvm_openfiles" = "xyes")
+AC_DEFINE_UNQUOTED(COLLECT_LIBESMTP, [$collect_libesmtp],
+       [Wether or not to use the esmtp library])
+AM_CONDITIONAL(BUILD_WITH_LIBESMTP, test "x$with_libesmtp" = "xyes")
+# }}}
 
-with_sensors_cflags=""
-with_sensors_ldflags=""
-AC_ARG_WITH(lm-sensors, [AS_HELP_STRING([--with-lm-sensors@<:@=PREFIX@:>@], [Path to lm_sensors.])],
+# --with-libiptc {{{
+with_own_libiptc="no"
+AC_ARG_WITH(libiptc, [AS_HELP_STRING([--with-libiptc@<:@=PREFIX@:>@], [Path to libiptc.])],
 [
-       if test "x$withval" = "xno"
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
        then
-               with_lm_sensors="no"
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include"
+               with_libiptc="yes"
        else
-               with_lm_sensors="yes"
-               if test "x$withval" != "xyes"
-               then
-                       with_sensors_cflags="-I$withval/include"
-                       with_sensors_ldflags="-L$withval/lib"
-                       with_lm_sensors="yes"
-               fi
+               with_libiptc="$withval"
        fi
 ],
 [
        if test "x$ac_system" = "xLinux"
        then
-               with_lm_sensors="yes"
+               with_libiptc="yes"
        else
-               with_lm_sensors="no (Linux only library)"
+               with_libiptc="no (Linux only)"
        fi
 ])
-if test "x$with_lm_sensors" = "xyes"
+if test "x$with_libiptc" = "xyes"
 then
-       SAVE_CPPFLAGS="$CPPFLAGS"
-       CPPFLAGS="$CPPFLAGS $with_sensors_cflags"
-
-#      AC_CHECK_HEADERS(sensors/sensors.h,
-#      [
-#              AC_DEFINE(HAVE_SENSORS_SENSORS_H, 1, [Define to 1 if you have the <sensors/sensors.h> header file.])
-#      ],
-#      [with_lm_sensors="no (sensors/sensors.h not found)"])
-       AC_CHECK_HEADERS(sensors/sensors.h, [], [with_lm_sensors="no (sensors/sensors.h not found)"])
-
-       CPPFLAGS="$SAVE_CPPFLAGS"
+       AC_CHECK_LIB(iptc, iptc_init,
+       [
+               AC_DEFINE(HAVE_LIBIPTC, 1, [Define to 1 if you have the iptc library (-liptc).])
+       ],
+       [
+               with_libiptc="yes"
+               with_own_libiptc="yes"
+       ])
 fi
-if test "x$with_lm_sensors" = "xyes"
+if test "x$with_libiptc" = "xyes" -a "x$with_own_libiptc" != "xyes"
 then
-       SAVE_CPPFLAGS="$CPPFLAGS"
-       SAVE_LDFLAGS="$LDFLAGS"
-       CPPFLAGS="$CPPFLAGS $with_sensors_cflags"
-       LDFLAGS="$LDFLAGS $with_sensors_ldflags"
+       AC_CHECK_HEADERS(libiptc/libiptc.h,
+       [
+               AC_DEFINE(HAVE_LIBIPTC_LIBIPTC_H, 1, [Define to 1 if you have the <libiptc/libiptc.h> header file.])
+       ],
+       [
+               with_libiptc="yes"
+               with_own_libiptc="yes"
+       ])
+fi
+if test "x$with_libiptc" = "xyes"
+then
+       SAVE_CFLAGS=$CFLAGS
+       CFLAGS="$CFLAGS $KERNEL_CFLAGS"
 
-       AC_CHECK_LIB(sensors, sensors_init,
+       AC_CHECK_HEADERS(linux/netfilter_ipv4/ip_tables.h linux/netfilter_ipv6/ip6_tables.h, [],
        [
-               AC_DEFINE(HAVE_LIBSENSORS, 1, [Define to 1 if you have the sensors library (-lsensors).])
+               with_libiptc="no (Linux iptables headers not found - check KERNEL_DIR)"
+               with_own_libiptc="no"
        ],
-       [with_lm_sensors="no (libsensors not found)"])
+       [
+#include "$srcdir/src/libiptc/ipt_kernel_headers.h"
+       ])
 
-       CPPFLAGS="$SAVE_CPPFLAGS"
-       LDFLAGS="$SAVE_LDFLAGS"
+       CFLAGS=$SAVE_CFLAGS
 fi
-if test "x$with_lm_sensors" = "xyes"
+AM_CONDITIONAL(BUILD_WITH_LIBIPTC, test "x$with_libiptc" = "xyes")
+AM_CONDITIONAL(BUILD_WITH_OWN_LIBIPTC, test "x$with_own_libiptc" = "xyes")
+if test "x$with_own_libiptc" = "xyes"
 then
-       BUILD_WITH_LIBSENSORS_CFLAGS="$with_sensors_cflags"
-       BUILD_WITH_LIBSENSORS_LDFLAGS="$with_sensors_ldflags"
-       AC_SUBST(BUILD_WITH_LIBSENSORS_CFLAGS)
-       AC_SUBST(BUILD_WITH_LIBSENSORS_LDFLAGS)
+       AC_DEFINE(OWN_LIBIPTC, 1, [Define to 1 if we use the shipped iptc library.])
 fi
-AM_CONDITIONAL(BUILD_WITH_LM_SENSORS, test "x$with_lm_sensors" = "xyes")
+# }}}
 
+# --with-libmysql {{{
 with_mysql_config="mysql_config"
 with_mysql_cflags=""
 with_mysql_libs=""
@@ -1466,63 +1374,238 @@ then
        AC_SUBST(BUILD_WITH_LIBMYSQL_LIBS)
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBMYSQL, test "x$with_libmysql" = "xyes")
+# }}}
 
-with_own_liboconfig="no"
-liboconfig_LDFLAGS="$LDFLAGS"
-liboconfig_CPPFLAGS="$CPPFLAGS"
-AC_ARG_WITH(liboconfig, [AS_HELP_STRING([--with-liboconfig@<:@=PREFIX@:>@], [Path to liboconfig.])],
+# --with-libnetlink {{{
+with_libnetlink_cflags=""
+with_libnetlink_libs="-lnetlink"
+AC_ARG_WITH(libnetlink, [AS_HELP_STRING([--with-libnetlink@<:@=PREFIX@:>@], [Path to libnetlink.])],
 [
-       if test "x$withval" != "xno" && test "x$withval" != "xyes"
-       then
-               if test -d "$withval/lib"
-               then
-                       liboconfig_LDFLAGS="$LDFLAGS -L$withval/lib"
-               fi
-               if test -d "$withval/include"
-               then
-                       liboconfig_CPPFLAGS="$CPPFLAGS -I$withval/include"
-               fi
-       fi
-       if test "x$withval" = "xno"
-       then
-               AC_MSG_ERROR("liboconfig is required")
-       fi
+ echo "libnetlink: withval = $withval"
+ if test "x$withval" = "xyes"
+ then
+        with_libnetlink="yes"
+ else if test "x$withval" = "xno"
+ then
+        with_libnetlink="no"
+ else
+        if test -d "$withval/include"
+        then
+                with_libnetlink_cflags="-I$withval/include"
+                with_libnetlink_libs="-L$withval/lib -lnetlink"
+                with_libnetlink="yes"
+        else
+                AC_MSG_ERROR("no such directory: $withval/include")
+        fi
+ fi; fi
 ],
 [
-       with_liboconfig="yes"
+ if test "x$ac_system" = "xLinux"
+ then
+        with_libnetlink="yes"
+ else
+        with_libnetlink="no (Linux only library)"
+ fi
 ])
+if test "x$with_libnetlink" = "xyes"
+then
+       SAVE_CFLAGS=$CFLAGS
+       CFLAGS="$CFLAGS $with_libnetlink_cflags"
 
-save_LDFLAGS="$LDFLAGS"
-save_CPPFLAGS="$CPPFLAGS"
-LDFLAGS="$liboconfig_LDFLAGS"
-CPPFLAGS="$liboconfig_CPPFLAGS"
-AC_CHECK_LIB(oconfig, oconfig_parse_fh,
-[
-       with_liboconfig="yes"
-       with_own_liboconfig="no"
-],
-[
-       with_liboconfig="yes"
-       with_own_liboconfig="yes"
-       LDFLAGS="$save_LDFLAGS"
-       CPPFLAGS="$save_CPPFLAGS"
-])
+       with_libnetlink="no (libnetlink.h not found)"
 
-AM_CONDITIONAL(BUILD_WITH_OWN_LIBOCONFIG, test "x$with_own_liboconfig" = "xyes")
-if test "x$with_own_liboconfig" = "xyes"
+       AC_CHECK_HEADERS(libnetlink.h iproute/libnetlink.h linux/libnetlink.h,
+       [
+        with_libnetlink="yes"
+        break
+       ], [],
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>])
+       AC_CHECK_HEADERS(linux/gen_stats.h linux/pkt_sched.h, [], [],
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>])
+
+       AC_COMPILE_IFELSE(
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+int main (void)
+{
+       int retval = TCA_STATS2;
+       return (retval);
+}],
+       [AC_DEFINE([HAVE_TCA_STATS2], 1, [True if the enum-member TCA_STATS2 exists])]
+       []);
+
+       AC_COMPILE_IFELSE(
+[#include <stdio.h>
+#include <sys/types.h>
+#include <asm/types.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+int main (void)
+{
+       int retval = TCA_STATS;
+       return (retval);
+}],
+       [AC_DEFINE([HAVE_TCA_STATS], 1, [True if the enum-member TCA_STATS exists])]
+       []);
+
+       CFLAGS="$SAVE_CFLAGS"
+fi
+if test "x$with_libnetlink" = "xyes"
 then
-       with_liboconfig="yes (shipped version)"
+       AC_CHECK_LIB(netlink, rtnl_open,
+                    [with_libnetlink="yes"],
+                    [with_libnetlink="no (symbol 'rtnl_open' not found)"],
+                    [$with_libnetlink_libs])
+fi
+if test "x$with_libnetlink" = "xyes"
+then
+       BUILD_WITH_LIBNETLINK_CFLAGS="$with_libnetlink_cflags"
+       BUILD_WITH_LIBNETLINK_LIBS="$with_libnetlink_libs"
+       AC_SUBST(BUILD_WITH_LIBNETLINK_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBNETLINK_LIBS)
 fi
+AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
+# }}}
 
-#with_liboping="yes"
-with_own_liboping="no"
-liboping_LDFLAGS="$LDFLAGS"
-liboping_CPPFLAGS="$CPPFLAGS"
-AC_ARG_WITH(liboping, [AS_HELP_STRING([--with-liboping@<:@=PREFIX@:>@], [Path to liboping.])],
+# --with-libnetsnmp {{{
+with_snmp_config="net-snmp-config"
+with_snmp_cflags=""
+with_snmp_libs=""
+AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])],
 [
-       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       if test "x$withval" = "xno"
        then
-               if test -d "$withval/lib"
+               with_libnetsnmp="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libnetsnmp="yes"
+       else
+               if test -x "$withval"
+               then
+                       with_snmp_config="$withval"
+                       with_libnetsnmp="yes"
+               else
+                       with_snmp_config="$withval/bin/net-snmp-config"
+                       with_libnetsnmp="yes"
+               fi
+       fi; fi
+],
+[with_libnetsnmp="yes"])
+if test "x$with_libnetsnmp" = "xyes"
+then
+       with_snmp_cflags=`$with_snmp_config --cflags 2>/dev/null`
+       snmp_config_status=$?
+
+       if test $snmp_config_status -ne 0
+       then
+               with_libnetsnmp="no ($with_snmp_config failed)"
+       else
+               SAVE_CPPFLAGS="$CPPFLAGS"
+               CPPFLAGS="$CPPFLAGS $with_snmp_cflags"
+               
+               AC_CHECK_HEADERS(net-snmp/net-snmp-config.h, [], [with_libnetsnmp="no (net-snmp/net-snmp-config.h not found)"])
+
+               CPPFLAGS="$SAVE_CPPFLAGS"
+       fi
+fi
+if test "x$with_libnetsnmp" = "xyes"
+then
+       with_snmp_libs=`$with_snmp_config --libs 2>/dev/null`
+       snmp_config_status=$?
+
+       if test $snmp_config_status -ne 0
+       then
+               with_libnetsnmp="no ($with_snmp_config failed)"
+       else
+               AC_CHECK_LIB(netsnmp, init_snmp,
+               [with_libnetsnmp="yes"],
+               [with_libnetsnmp="no (libnetsnmp not found)"],
+               [$with_snmp_libs])
+       fi
+fi
+if test "x$with_libnetsnmp" = "xyes"
+then
+       BUILD_WITH_LIBSNMP_CFLAGS="$with_snmp_cflags"
+       BUILD_WITH_LIBSNMP_LIBS="$with_snmp_libs"
+       AC_SUBST(BUILD_WITH_LIBSNMP_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBSNMP_LIBS)
+fi
+AM_CONDITIONAL(BUILD_WITH_LIBNETSNMP, test "x$with_libnetsnmp" = "xyes")
+# }}}
+
+# --with-liboconfig {{{
+with_own_liboconfig="no"
+liboconfig_LDFLAGS="$LDFLAGS"
+liboconfig_CPPFLAGS="$CPPFLAGS"
+AC_ARG_WITH(liboconfig, [AS_HELP_STRING([--with-liboconfig@<:@=PREFIX@:>@], [Path to liboconfig.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               if test -d "$withval/lib"
+               then
+                       liboconfig_LDFLAGS="$LDFLAGS -L$withval/lib"
+               fi
+               if test -d "$withval/include"
+               then
+                       liboconfig_CPPFLAGS="$CPPFLAGS -I$withval/include"
+               fi
+       fi
+       if test "x$withval" = "xno"
+       then
+               AC_MSG_ERROR("liboconfig is required")
+       fi
+],
+[
+       with_liboconfig="yes"
+])
+
+save_LDFLAGS="$LDFLAGS"
+save_CPPFLAGS="$CPPFLAGS"
+LDFLAGS="$liboconfig_LDFLAGS"
+CPPFLAGS="$liboconfig_CPPFLAGS"
+AC_CHECK_LIB(oconfig, oconfig_parse_fh,
+[
+       with_liboconfig="yes"
+       with_own_liboconfig="no"
+],
+[
+       with_liboconfig="yes"
+       with_own_liboconfig="yes"
+       LDFLAGS="$save_LDFLAGS"
+       CPPFLAGS="$save_CPPFLAGS"
+])
+
+AM_CONDITIONAL(BUILD_WITH_OWN_LIBOCONFIG, test "x$with_own_liboconfig" = "xyes")
+if test "x$with_own_liboconfig" = "xyes"
+then
+       with_liboconfig="yes (shipped version)"
+fi
+# }}}
+
+# --with-liboping {{{
+with_own_liboping="no"
+liboping_LDFLAGS="$LDFLAGS"
+liboping_CPPFLAGS="$CPPFLAGS"
+AC_ARG_WITH(liboping, [AS_HELP_STRING([--with-liboping@<:@=PREFIX@:>@], [Path to liboping.])],
+[
+       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       then
+               if test -d "$withval/lib"
                then
                        liboping_LDFLAGS="$LDFLAGS -L$withval/lib"
                fi
@@ -1564,7 +1647,78 @@ then
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBOPING, test "x$with_liboping" = "xyes")
 AM_CONDITIONAL(BUILD_WITH_OWN_LIBOPING, test "x$with_own_liboping" = "xyes")
+# }}}
+
+# --with-oracle {{{
+with_oracle_cppflags=""
+with_oracle_libs=""
+AC_ARG_WITH(oracle, [AS_HELP_STRING([--with-oracle@<:@=ORACLE_HOME@:>@], [Path to Oracle.])],
+[
+       if test "x$withval" = "xyes"
+       then
+               if test "x$ORACLE_HOME" = "x"
+               then
+                       AC_MSG_WARN([Use of the Oracle library has been forced, but the environment variable ORACLE_HOME is not set.])
+               fi
+               with_oracle="yes"
+       else if test "x$withval" = "xno"
+       then
+               with_oracle="no"
+       else
+               with_oracle="yes"
+               ORACLE_HOME="$withval"
+       fi; fi
+],
+[
+       if test "x$ORACLE_HOME" = "x"
+       then
+               with_oracle="no (ORACLE_HOME is not set)"
+       else
+               with_oracle="yes"
+       fi
+])
+if test "x$ORACLE_HOME" != "x"
+then
+       with_oracle_cppflags="-I$ORACLE_HOME/rdbms/public"
 
+       if test -e "$ORACLE_HOME/lib/ldflags"
+       then
+               with_oracle_libs=`cat "$ORACLE_HOME/lib/ldflags"`
+       fi
+       #with_oracle_libs="-L$ORACLE_HOME/lib $with_oracle_libs -lclntsh"
+       with_oracle_libs="-L$ORACLE_HOME/lib -lclntsh"
+fi
+if test "x$with_oracle" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_oracle_cppflags"
+
+       AC_CHECK_HEADERS(oci.h, [with_oracle="yes"], [with_oracle="no (oci.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_oracle" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_oracle_cppflags"
+       LDFLAGS="$LDFLAGS $with_oracle_libs"
+
+       AC_CHECK_FUNC(OCIEnvCreate, [with_oracle="yes"], [with_oracle="no (Symbol 'OCIEnvCreate' not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_oracle" = "xyes"
+then
+       BUILD_WITH_ORACLE_CFLAGS="$with_oracle_cppflags"
+       BUILD_WITH_ORACLE_LIBS="$with_oracle_libs"
+       AC_SUBST(BUILD_WITH_ORACLE_CFLAGS)
+       AC_SUBST(BUILD_WITH_ORACLE_LIBS)
+fi
+# }}}
+
+# --with-libowcapi {{{
 with_libowcapi_cppflags=""
 with_libowcapi_libs="-lowcapi"
 AC_ARG_WITH(libowcapi, [AS_HELP_STRING([--with-libowcapi@<:@=PREFIX@:>@], [Path to libowcapi.])],
@@ -1609,8 +1763,9 @@ then
        AC_SUBST(BUILD_WITH_LIBOWCAPI_CPPFLAGS)
        AC_SUBST(BUILD_WITH_LIBOWCAPI_LIBS)
 fi
+# }}}
 
-
+# --with-libpcap {{{
 AC_ARG_WITH(libpcap, [AS_HELP_STRING([--with-libpcap@<:@=PREFIX@:>@], [Path to libpcap.])],
 [
        if test "x$withval" != "xno" && test "x$withval" != "xyes"
@@ -1648,45 +1803,9 @@ fi
 AC_DEFINE_UNQUOTED(COLLECT_LIBPCAP, [$collect_libpcap],
        [Wether or not to use the pcap library])
 AM_CONDITIONAL(BUILD_WITH_LIBPCAP, test "x$with_libpcap" = "xyes")
+# }}}
 
-AC_ARG_WITH(libesmtp, [AS_HELP_STRING([--with-libesmtp@<:@=PREFIX@:>@], [Path to libesmtp.])],
-[
-       if test "x$withval" != "xno" && test "x$withval" != "xyes"
-       then
-               LDFLAGS="$LDFLAGS -L$withval/lib"
-               CPPFLAGS="$CPPFLAGS -I$withval/include -D_THREAD_SAFE"
-               with_libesmtp="yes"
-       else
-               with_libesmtp="$withval"
-       fi
-],
-[
-       with_libesmtp="yes"
-])
-if test "x$with_libesmtp" = "xyes"
-then
-       AC_CHECK_LIB(esmtp, smtp_create_session,
-       [
-               AC_DEFINE(HAVE_LIBESMTP, 1, [Define to 1 if you have the esmtp library (-lesmtp).])
-       ], [with_libesmtp="no (libesmtp not found)"])
-fi
-if test "x$with_libesmtp" = "xyes"
-then
-       AC_CHECK_HEADERS(libesmtp.h,
-       [
-               AC_DEFINE(HAVE_LIBESMTP_H, 1, [Define to 1 if you have the <libesmtp.h> header file.])
-       ], [with_libesmtp="no (libesmtp.h not found)"])
-fi
-if test "x$with_libesmtp" = "xyes"
-then
-       collect_libesmtp=1
-else
-       collect_libesmtp=0
-fi
-AC_DEFINE_UNQUOTED(COLLECT_LIBESMTP, [$collect_libesmtp],
-       [Wether or not to use the esmtp library])
-AM_CONDITIONAL(BUILD_WITH_LIBESMTP, test "x$with_libesmtp" = "xyes")
-
+# --with-libperl {{{
 perl_interpreter="perl"
 AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to libperl.])],
 [
@@ -1804,611 +1923,653 @@ then
        LDFLAGS=$SAVE_LDFLAGS
 fi
 
-with_own_libiptc="no"
-AC_ARG_WITH(libiptc, [AS_HELP_STRING([--with-libiptc@<:@=PREFIX@:>@], [Path to libiptc.])],
-[
-       if test "x$withval" != "xno" && test "x$withval" != "xyes"
-       then
-               LDFLAGS="$LDFLAGS -L$withval/lib"
-               CPPFLAGS="$CPPFLAGS -I$withval/include"
-               with_libiptc="yes"
-       else
-               with_libiptc="$withval"
-       fi
-],
-[
-       if test "x$ac_system" = "xLinux"
-       then
-               with_libiptc="yes"
-       else
-               with_libiptc="no (Linux only)"
-       fi
-])
-if test "x$with_libiptc" = "xyes"
-then
-       AC_CHECK_LIB(iptc, iptc_init,
-       [
-               AC_DEFINE(HAVE_LIBIPTC, 1, [Define to 1 if you have the iptc library (-liptc).])
-       ],
-       [
-               with_libiptc="yes"
-               with_own_libiptc="yes"
-       ])
-fi
-if test "x$with_libiptc" = "xyes" -a "x$with_own_libiptc" != "xyes"
-then
-       AC_CHECK_HEADERS(libiptc/libiptc.h,
-       [
-               AC_DEFINE(HAVE_LIBIPTC_LIBIPTC_H, 1, [Define to 1 if you have the <libiptc/libiptc.h> header file.])
-       ],
-       [
-               with_libiptc="yes"
-               with_own_libiptc="yes"
-       ])
-fi
-if test "x$with_libiptc" = "xyes"
+c_cv_have_broken_perl_load_module="no"
+if test "x$with_libperl" = "xyes"
 then
        SAVE_CFLAGS=$CFLAGS
-       CFLAGS="$CFLAGS $KERNEL_CFLAGS"
+       SAVE_LDFLAGS=$LDFLAGS
+       # trigger an error if Perl_load_module*() uses __attribute__nonnull__(3)
+       # (see issues #41 and #42)
+       CFLAGS="$CFLAGS $PERL_CFLAGS -Wall -Werror"
+       LDFLAGS="$LDFLAGS $PERL_LDFLAGS"
 
-       AC_CHECK_HEADERS(linux/netfilter_ipv4/ip_tables.h linux/netfilter_ipv6/ip6_tables.h, [],
-       [
-               with_libiptc="no (Linux iptables headers not found - check KERNEL_DIR)"
-               with_own_libiptc="no"
-       ],
-       [
-#include "$srcdir/src/libiptc/ipt_kernel_headers.h"
-       ])
+       AC_CACHE_CHECK([for broken Perl_load_module()],
+               [c_cv_have_broken_perl_load_module],
+               AC_LINK_IFELSE(
+                       AC_LANG_PROGRAM(
+                       [[
+#define PERL_NO_GET_CONTEXT
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+                       ]],
+                       [[
+                        dTHX;
+                        load_module (PERL_LOADMOD_NOIMPORT,
+                            newSVpv ("Collectd::Plugin::FooBar", 24),
+                            Nullsv);
+                       ]]),
+                       [c_cv_have_broken_perl_load_module="no"],
+                       [c_cv_have_broken_perl_load_module="yes"]
+               )
+       )
 
        CFLAGS=$SAVE_CFLAGS
+       LDFLAGS=$SAVE_LDFLAGS
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBIPTC, test "x$with_libiptc" = "xyes")
-AM_CONDITIONAL(BUILD_WITH_OWN_LIBIPTC, test "x$with_own_libiptc" = "xyes")
-if test "x$with_own_libiptc" = "xyes"
+AM_CONDITIONAL(HAVE_BROKEN_PERL_LOAD_MODULE,
+               test "x$c_cv_have_broken_perl_load_module" = "xyes")
+
+if test "x$with_libperl" = "xyes"
 then
-       AC_DEFINE(OWN_LIBIPTC, 1, [Define to 1 if we use the shipped iptc library.])
+       SAVE_CFLAGS=$CFLAGS
+       SAVE_LDFLAGS=$LDFLAGS
+       CFLAGS="$CFLAGS $PERL_CFLAGS"
+       LDFLAGS="$LDFLAGS $PERL_LDFLAGS"
+
+       AC_CHECK_MEMBER(
+               [struct mgvtbl.svt_local],
+               [have_struct_mgvtbl_svt_local="yes"],
+               [have_struct_mgvtbl_svt_local="no"],
+               [
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+               ])
+
+       if test "x$have_struct_mgvtbl_svt_local" = "xyes"
+       then
+               AC_DEFINE(HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL, 1,
+                                 [Define if Perl's struct mgvtbl has member svt_local.])
+       fi
+
+       CFLAGS=$SAVE_CFLAGS
+       LDFLAGS=$SAVE_LDFLAGS
 fi
+# }}}
 
-with_snmp_config="net-snmp-config"
-with_snmp_cflags=""
-with_snmp_libs=""
-AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])],
+# --with-libpq {{{
+with_pg_config="pg_config"
+with_libpq_includedir=""
+with_libpq_libdir=""
+with_libpq_cppflags=""
+with_libpq_ldflags=""
+AC_ARG_WITH(libpq, [AS_HELP_STRING([--with-libpq@<:@=PREFIX@:>@],
+       [Path to libpq.])],
 [
        if test "x$withval" = "xno"
        then
-               with_libnetsnmp="no"
+               with_libpq="no"
        else if test "x$withval" = "xyes"
        then
-               with_libnetsnmp="yes"
+               with_libpq="yes"
        else
-               if test -x "$withval"
+               if test -f "$withval" && test -x "$withval";
                then
-                       with_snmp_config="$withval"
-                       with_libnetsnmp="yes"
-               else
-                       with_snmp_config="$withval/bin/net-snmp-config"
-                       with_libnetsnmp="yes"
-               fi
+                       with_pg_config="$withval"
+               else if test -x "$withval/bin/pg_config"
+               then
+                       with_pg_config="$withval/bin/pg_config"
+               fi; fi
+               with_libpq="yes"
        fi; fi
 ],
-[with_libnetsnmp="yes"])
-if test "x$with_libnetsnmp" = "xyes"
+[
+       with_libpq="yes"
+])
+if test "x$with_libpq" = "xyes"
 then
-       with_snmp_cflags=`$with_snmp_config --cflags 2>/dev/null`
-       snmp_config_status=$?
+       with_libpq_includedir=`$with_pg_config --includedir 2> /dev/null`
+       pg_config_status=$?
 
-       if test $snmp_config_status -ne 0
+       if test $pg_config_status -eq 0
        then
-               with_libnetsnmp="no ($with_snmp_config failed)"
+               if test -n "$with_libpq_includedir"; then
+                       for dir in $with_libpq_includedir; do
+                               with_libpq_cppflags="$with_libpq_cppflags -I$dir"
+                       done
+               fi
        else
-               SAVE_CPPFLAGS="$CPPFLAGS"
-               CPPFLAGS="$CPPFLAGS $with_snmp_cflags"
-               
-               AC_CHECK_HEADERS(net-snmp/net-snmp-config.h, [], [with_libnetsnmp="no (net-snmp/net-snmp-config.h not found)"])
-
-               CPPFLAGS="$SAVE_CPPFLAGS"
+               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
        fi
+
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libpq_cppflags"
+
+       AC_CHECK_HEADERS(libpq-fe.h, [],
+               [with_libpq="no (libpq-fe.h not found)"], [])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
 fi
-if test "x$with_libnetsnmp" = "xyes"
+if test "x$with_libpq" = "xyes"
 then
-       with_snmp_libs=`$with_snmp_config --libs 2>/dev/null`
-       snmp_config_status=$?
+       with_libpq_libdir=`$with_pg_config --libdir 2> /dev/null`
+       pg_config_status=$?
 
-       if test $snmp_config_status -ne 0
+       if test $pg_config_status -eq 0
        then
-               with_libnetsnmp="no ($with_snmp_config failed)"
+               if test -n "$with_libpq_libdir"; then
+                       for dir in $with_libpq_libdir; do
+                               with_libpq_ldflags="$with_libpq_ldflags -L$dir"
+                       done
+               fi
        else
-               AC_CHECK_LIB(netsnmp, init_snmp,
-               [with_libnetsnmp="yes"],
-               [with_libnetsnmp="no (libnetsnmp not found)"],
-               [$with_snmp_libs])
+               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
        fi
+
+       SAVE_LDFLAGS="$LDFLAGS"
+       LDFLAGS="$LDFLAGS $with_libpq_ldflags"
+
+       AC_CHECK_LIB(pq, PQconnectdb,
+               [with_libpq="yes"],
+               [with_libpq="no (symbol 'PQconnectdb' not found)"])
+
+       LDFLAGS="$SAVE_LDFLAGS"
 fi
-if test "x$with_libnetsnmp" = "xyes"
+if test "x$with_libpq" = "xyes"
 then
-       BUILD_WITH_LIBSNMP_CFLAGS="$with_snmp_cflags"
-       BUILD_WITH_LIBSNMP_LIBS="$with_snmp_libs"
-       AC_SUBST(BUILD_WITH_LIBSNMP_CFLAGS)
-       AC_SUBST(BUILD_WITH_LIBSNMP_LIBS)
+       BUILD_WITH_LIBPQ_CPPFLAGS="$with_libpq_cppflags"
+       BUILD_WITH_LIBPQ_LDFLAGS="$with_libpq_ldflags"
+       AC_SUBST(BUILD_WITH_LIBPQ_CPPFLAGS)
+       AC_SUBST(BUILD_WITH_LIBPQ_LDFLAGS)
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBNETSNMP, test "x$with_libnetsnmp" = "xyes")
-
-PKG_CHECK_MODULES([LIBNOTIFY], [libnotify],
-               [with_libnotify="yes"],
-               [with_libnotify="no ($LIBNOTIFY_PKG_ERRORS)"])
+AM_CONDITIONAL(BUILD_WITH_LIBPQ, test "x$with_libpq" = "xyes")
+# }}}
 
-with_libupsclient_config=""
-with_libupsclient_cflags=""
-with_libupsclient_libs=""
-AC_ARG_WITH(libupsclient, [AS_HELP_STRING([--with-libupsclient@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])],
-[
-       if test "x$withval" = "xno"
-       then
-               with_libupsclient="no"
-       else if test "x$withval" = "xyes"
+# --with-libpthread {{{
+AC_ARG_WITH(libpthread, [AS_HELP_STRING([--with-libpthread=@<:@=PREFIX@:>@], [Path to libpthread.])],
+[      if test "x$withval" != "xno" \
+               && test "x$withval" != "xyes"
        then
-               with_libupsclient="use_pkgconfig"
+               LDFLAGS="$LDFLAGS -L$withval/lib"
+               CPPFLAGS="$CPPFLAGS -I$withval/include"
+               with_libpthread="yes"
        else
-               if test -x "$withval"
-               then
-                       with_libupsclient_config="$withval"
-                       with_libupsclient="use_libupsclient_config"
-               else if test -x "$withval/bin/libupsclient-config"
+               if test "x$withval" = "xno"
                then
-                       with_libupsclient_config="$withval/bin/net-snmp-config"
-                       with_libupsclient="use_libupsclient_config"
-               else
-                       AC_MSG_NOTICE([Not checking for libupsclient: Manually configured])
-                       with_libupsclient_cflags="-I$withval/include"
-                       with_libupsclient_libs="-L$withval/lib -lupsclient"
-                       with_libupsclient="yes"
-               fi; fi
-       fi; fi
-],
-[with_libupsclient="use_pkgconfig"])
-
-# configure using libupsclient-config
-if test "x$with_libupsclient" = "xuse_libupsclient_config"
-then
-       AC_MSG_NOTICE([Checking for libupsclient using $with_libupsclient_config])
-       with_libupsclient_cflags="`$with_libupsclient_config --cflags`"
-       if test $? -ne 0
-       then
-               with_libupsclient="no ($with_libupsclient_config failed)"
-       fi
-       with_libupsclient_libs="`$with_libupsclient_config --libs`"
-       if test $? -ne 0
-       then
-               with_libupsclient="no ($with_libupsclient_config failed)"
+                       with_libpthread="no (disabled)"
+               fi
        fi
-fi
-if test "x$with_libupsclient" = "xuse_libupsclient_config"
+], [with_libpthread="yes"])
+if test "x$with_libpthread" = "xyes"
 then
-       with_libupsclient="yes"
+       AC_CHECK_LIB(pthread, pthread_create, [with_libpthread="yes"], [with_libpthread="no (libpthread not found)"], [])
 fi
 
-# configure using pkg-config
-if test "x$with_libupsclient" = "xuse_pkgconfig"
+if test "x$with_libpthread" = "xyes"
 then
-       if test "x$PKG_CONFIG" = "x"
-       then
-               with_libupsclient="no (Don't have pkg-config)"
-       fi
+       AC_CHECK_HEADERS(pthread.h,, [with_libpthread="no (pthread.h not found)"])
 fi
-if test "x$with_libupsclient" = "xuse_pkgconfig"
+if test "x$with_libpthread" = "xyes"
 then
-       AC_MSG_NOTICE([Checking for libupsclient using $PKG_CONFIG])
-       $PKG_CONFIG --exists 'libupsclient' 2>/dev/null
-       if test $? -ne 0
-       then
-               with_libupsclient="no (pkg-config doesn't know library)"
-       fi
+       collect_pthread=1
+else
+       collect_pthread=0
 fi
-if test "x$with_libupsclient" = "xuse_pkgconfig"
-then
-       with_libupsclient_cflags="`$PKG_CONFIG --cflags 'libupsclient'`"
-       if test $? -ne 0
-       then
-               with_libupsclient="no ($PKG_CONFIG failed)"
-       fi
-       with_libupsclient_libs="`$PKG_CONFIG --libs 'libupsclient'`"
-       if test $? -ne 0
+AC_DEFINE_UNQUOTED(HAVE_LIBPTHREAD, [$collect_pthread],
+       [Wether or not to use pthread (POSIX threads) library])
+AM_CONDITIONAL(BUILD_WITH_LIBPTHREAD, test "x$with_libpthread" = "xyes")
+# }}}
+
+# --with-librrd {{{
+# AC_ARG_WITH (package, help-string, [action-if-given], [action-if-not-given])
+librrd_cflags=""
+librrd_ldflags=""
+librrd_threadsafe="yes"
+librrd_rrdc_update="no"
+AC_ARG_WITH(librrd, [AS_HELP_STRING([--with-librrd@<:@=PREFIX@:>@], [Path to rrdtool.])],
+[      if test "x$withval" != "xno" && test "x$withval" != "xyes"
        then
-               with_libupsclient="no ($PKG_CONFIG failed)"
+               librrd_cflags="-I$withval/include"
+               librrd_ldflags="-L$withval/lib"
+               with_librrd="yes"
+       else
+               with_librrd="$withval"
        fi
-fi
-if test "x$with_libupsclient" = "xuse_pkgconfig"
-then
-       with_libupsclient="yes"
-fi
-
-# with_libupsclient_cflags and with_libupsclient_libs are set up now, let's do
-# the actual checks.
-if test "x$with_libupsclient" = "xyes"
-then
-       SAVE_CPPFLAGS="$CPPFLAGS"
-       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
-
-       AC_CHECK_HEADERS(upsclient.h, [], [with_libupsclient="no (upsclient.h not found)"])
-
-       CPPFLAGS="$SAVE_CPPFLAGS"
-fi
-if test "x$with_libupsclient" = "xyes"
+], [with_librrd="yes"])
+if test "x$with_librrd" = "xyes"
 then
        SAVE_CPPFLAGS="$CPPFLAGS"
        SAVE_LDFLAGS="$LDFLAGS"
 
-       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
-       LDFLAGS="$LDFLAGS $with_libupsclient_libs"
+       CPPFLAGS="$CPPFLAGS $librrd_cflags"
+       LDFLAGS="$LDFLAGS $librrd_ldflags"
 
-       AC_CHECK_LIB(upsclient, upscli_connect,
-                    [with_libupsclient="yes"],
-                    [with_libupsclient="no (symbol upscli_connect not found)"])
+       AC_CHECK_HEADERS(rrd.h,, [with_librrd="no (rrd.h not found)"])
 
        CPPFLAGS="$SAVE_CPPFLAGS"
        LDFLAGS="$SAVE_LDFLAGS"
 fi
-if test "x$with_libupsclient" = "xyes"
+if test "x$with_librrd" = "xyes"
 then
        SAVE_CPPFLAGS="$CPPFLAGS"
-       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
+       SAVE_LDFLAGS="$LDFLAGS"
 
-       AC_CHECK_TYPES([UPSCONN_t, UPSCONN], [], [],
-[#include <stdlib.h>
-#include <stdio.h>
-#include <upsclient.h>])
+       CPPFLAGS="$CPPFLAGS $librrd_cflags"
+       LDFLAGS="$LDFLAGS $librrd_ldflags"
 
-       CPPFLAGS="$SAVE_CPPFLAGS"
-fi
-if test "x$with_libupsclient" = "xyes"
+       AC_CHECK_LIB(rrd_th, rrd_update_r,
+       [with_librrd="yes"
+        librrd_ldflags="$librrd_ldflags -lrrd_th -lm"
+       ],
+       [librrd_threadsafe="no"
+        AC_CHECK_LIB(rrd, rrd_update,
+        [with_librrd="yes"
+         librrd_ldflags="$librrd_ldflags -lrrd -lm"
+        ],
+        [with_librrd="no (symbol 'rrd_update' not found)"],
+        [-lm])
+       ],
+       [-lm])
+
+       if test "x$librrd_threadsafe" = "xyes"
+       then
+               AC_CHECK_LIB(rrd_th, rrdc_update, [librrd_rrdc_update="yes"], [librrd_rrdc_update="no"])
+       else
+               AC_CHECK_LIB(rrd, rrdc_update, [librrd_rrdc_update="yes"], [librrd_rrdc_update="no"])
+       fi
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+if test "x$with_librrd" = "xyes"
 then
-       BUILD_WITH_LIBUPSCLIENT_CFLAGS="$with_libupsclient_cflags"
-       BUILD_WITH_LIBUPSCLIENT_LIBS="$with_libupsclient_libs"
-       AC_SUBST(BUILD_WITH_LIBUPSCLIENT_CFLAGS)
-       AC_SUBST(BUILD_WITH_LIBUPSCLIENT_LIBS)
+       BUILD_WITH_LIBRRD_CFLAGS="$librrd_cflags"
+       BUILD_WITH_LIBRRD_LDFLAGS="$librrd_ldflags"
+       AC_SUBST(BUILD_WITH_LIBRRD_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBRRD_LDFLAGS)
 fi
+if test "x$librrd_threadsafe" = "xyes"
+then
+       AC_DEFINE(HAVE_THREADSAFE_LIBRRD, 1, [Define to 1 if you have the threadsafe rrd library (-lrrd_th).])
+fi
+# }}}
 
-### BEGIN of check for libxmms ###
-with_xmms_config="xmms-config"
-with_xmms_cflags=""
-with_xmms_libs=""
-AC_ARG_WITH(libxmms, [AS_HELP_STRING([--with-libxmms@<:@=PREFIX@:>@], [Path to libxmms.])],
+# --with-libsensors {{{
+with_sensors_cflags=""
+with_sensors_ldflags=""
+AC_ARG_WITH(libsensors, [AS_HELP_STRING([--with-libsensors@<:@=PREFIX@:>@], [Path to lm_sensors.])],
 [
-       if test "x$withval" != "xno" \
-               && test "x$withval" != "xyes"
-       then
-               if test -f "$withval" && test -x "$withval";
-               then
-                       with_xmms_config="$withval"
-               else if test -x "$withval/bin/xmms-config"
-               then
-                       with_xmms_config="$withval/bin/xmms-config"
-               fi; fi
-               with_libxmms="yes"
-       else if test "x$withval" = "xno"
+       if test "x$withval" = "xno"
        then
-               with_libxmms="no"
+               with_libsensors="no"
        else
-               with_libxmms="yes"
-       fi; fi
+               with_libsensors="yes"
+               if test "x$withval" != "xyes"
+               then
+                       with_sensors_cflags="-I$withval/include"
+                       with_sensors_ldflags="-L$withval/lib"
+                       with_libsensors="yes"
+               fi
+       fi
 ],
 [
-       with_libxmms="yes"
-])
-if test "x$with_libxmms" = "xyes"
-then
-       with_xmms_cflags=`$with_xmms_config --cflags 2>/dev/null`
-       xmms_config_status=$?
-
-       if test $xmms_config_status -ne 0
+       if test "x$ac_system" = "xLinux"
        then
-               with_libxmms="no"
+               with_libsensors="yes"
+       else
+               with_libsensors="no (Linux only library)"
        fi
-fi
-if test "x$with_libxmms" = "xyes"
+])
+if test "x$with_libsensors" = "xyes"
 then
-       with_xmms_libs=`$with_xmms_config --libs 2>/dev/null`
-       xmms_config_status=$?
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_sensors_cflags"
 
-       if test $xmms_config_status -ne 0
-       then
-               with_libxmms="no"
-       fi
+#      AC_CHECK_HEADERS(sensors/sensors.h,
+#      [
+#              AC_DEFINE(HAVE_SENSORS_SENSORS_H, 1, [Define to 1 if you have the <sensors/sensors.h> header file.])
+#      ],
+#      [with_libsensors="no (sensors/sensors.h not found)"])
+       AC_CHECK_HEADERS(sensors/sensors.h, [], [with_libsensors="no (sensors/sensors.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
 fi
-if test "x$with_libxmms" = "xyes"
+if test "x$with_libsensors" = "xyes"
 then
-       AC_CHECK_LIB(xmms, xmms_remote_get_info,
-       [
-               BUILD_WITH_LIBXMMS_CFLAGS="$with_xmms_cflags"
-               BUILD_WITH_LIBXMMS_LIBS="$with_xmms_libs"
-               AC_SUBST(BUILD_WITH_LIBXMMS_CFLAGS)
-               AC_SUBST(BUILD_WITH_LIBXMMS_LIBS)
-       ],
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       SAVE_LDFLAGS="$LDFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_sensors_cflags"
+       LDFLAGS="$LDFLAGS $with_sensors_ldflags"
+
+       AC_CHECK_LIB(sensors, sensors_init,
        [
-               with_libxmms="no"
+               AC_DEFINE(HAVE_LIBSENSORS, 1, [Define to 1 if you have the sensors library (-lsensors).])
        ],
-       [$with_xmms_libs])
+       [with_libsensors="no (libsensors not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
 fi
-with_libxmms_numeric=0
-if test "x$with_libxmms" = "xyes"
+if test "x$with_libsensors" = "xyes"
 then
-       with_libxmms_numeric=1
+       BUILD_WITH_LIBSENSORS_CFLAGS="$with_sensors_cflags"
+       BUILD_WITH_LIBSENSORS_LDFLAGS="$with_sensors_ldflags"
+       AC_SUBST(BUILD_WITH_LIBSENSORS_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBSENSORS_LDFLAGS)
 fi
-AC_DEFINE_UNQUOTED(HAVE_LIBXMMS, [$with_libxmms_numeric], [Define to 1 if you have the 'xmms' library (-lxmms).])
-AM_CONDITIONAL(BUILD_WITH_LIBXMMS, test "x$with_libxmms" = "xyes")
-### END of check for libxmms ###
+AM_CONDITIONAL(BUILD_WITH_LM_SENSORS, test "x$with_libsensors" = "xyes")
+# }}}
 
-with_libnetlink_cflags=""
-with_libnetlink_libs="-lnetlink"
-AC_ARG_WITH(libnetlink, [AS_HELP_STRING([--with-libnetlink@<:@=PREFIX@:>@], [Path to libnetlink.])],
-[
- echo "libnetlink: withval = $withval"
- if test "x$withval" = "xyes"
- then
-        with_libnetlink="yes"
- else if test "x$withval" = "xno"
- then
-        with_libnetlink="no"
- else
-        if test -d "$withval/include"
-        then
-                with_libnetlink_cflags="-I$withval/include"
-                with_libnetlink_libs="-L$withval/lib -lnetlink"
-                with_libnetlink="yes"
-        else
-                AC_MSG_ERROR("no such directory: $withval/include")
-        fi
- fi; fi
-],
+# --with-libstatgrab {{{
+with_libstatgrab_cflags=""
+with_libstatgrab_ldflags=""
+AC_ARG_WITH(libstatgrab, [AS_HELP_STRING([--with-libstatgrab@<:@=PREFIX@:>@], [Path to libstatgrab.])],
 [
- if test "x$ac_system" = "xLinux"
+ if test "x$withval" != "xno" \
+   && test "x$withval" != "xyes"
  then
-        with_libnetlink="yes"
+   with_libstatgrab_cflags="-I$withval/include"
+   with_libstatgrab_ldflags="-L$withval/lib -lstatgrab"
+   with_libstatgrab="yes"
+   with_libstatgrab_pkg_config="no"
  else
-        with_libnetlink="no (Linux only library)"
+   with_libstatgrab="$withval"
+   with_libstatgrab_pkg_config="yes"
  fi
+ ],
+[
+ with_libstatgrab="yes"
+ with_libstatgrab_pkg_config="yes"
 ])
-if test "x$with_libnetlink" = "xyes"
-then
-       SAVE_CFLAGS=$CFLAGS
-       CFLAGS="$CFLAGS $with_libnetlink_cflags"
-
-       with_libnetlink="no (libnetlink.h not found)"
 
-       AC_CHECK_HEADERS(libnetlink.h iproute/libnetlink.h linux/libnetlink.h,
-       [
-        with_libnetlink="yes"
-        break
-       ], [],
-[#include <stdio.h>
-#include <sys/types.h>
-#include <asm/types.h>
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>])
-       AC_CHECK_HEADERS(linux/gen_stats.h linux/pkt_sched.h, [], [],
-[#include <stdio.h>
-#include <sys/types.h>
-#include <asm/types.h>
-#include <sys/socket.h>])
+if test "x$with_libstatgrab" = "xyes" \
+  && test "x$with_libstatgrab_pkg_config" = "xyes"
+then
+  if test "x$PKG_CONFIG" != "x"
+  then
+    AC_MSG_CHECKING([pkg-config for libstatgrab])
+    temp_result="found"
+    $PKG_CONFIG --exists libstatgrab 2>/dev/null
+    if test "$?" != "0"
+    then
+      with_libstatgrab_pkg_config="no"
+      with_libstatgrab="no ($PKG_CONFIG doesn't know libstatgrab)"
+      temp_result="not found"
+    fi
+    AC_MSG_RESULT([$temp_result])
+  else
+    AC_MSG_NOTICE([pkg-config not available, trying to guess flags for the statgrab library.])
+    with_libstatgrab_pkg_config="no"
+    with_libstatgrab_ldflags="$with_libstatgrab_ldflags -lstatgrab"
+  fi
+fi
 
-       AC_COMPILE_IFELSE(
-[#include <stdio.h>
-#include <sys/types.h>
-#include <asm/types.h>
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
+if test "x$with_libstatgrab" = "xyes" \
+  && test "x$with_libstatgrab_pkg_config" = "xyes" \
+  && test "x$with_libstatgrab_cflags" = "x"
+then
+  AC_MSG_CHECKING([for libstatgrab CFLAGS])
+  temp_result="`$PKG_CONFIG --cflags libstatgrab`"
+  if test "$?" = "0"
+  then
+    with_libstatgrab_cflags="$temp_result"
+  else
+    with_libstatgrab="no ($PKG_CONFIG --cflags libstatgrab failed)"
+    temp_result="$PKG_CONFIG --cflags libstatgrab failed"
+  fi
+  AC_MSG_RESULT([$temp_result])
+fi
 
-int main (void)
-{
-       int retval = TCA_STATS2;
-       return (retval);
-}],
-       [AC_DEFINE([HAVE_TCA_STATS2], 1, [True if the enum-member TCA_STATS2 exists])]
-       []);
+if test "x$with_libstatgrab" = "xyes" \
+  && test "x$with_libstatgrab_pkg_config" = "xyes" \
+  && test "x$with_libstatgrab_ldflags" = "x"
+then
+  AC_MSG_CHECKING([for libstatgrab LDFLAGS])
+  temp_result="`$PKG_CONFIG --libs libstatgrab`"
+  if test "$?" = "0"
+  then
+    with_libstatgrab_ldflags="$temp_result"
+  else
+    with_libstatgrab="no ($PKG_CONFIG --libs libstatgrab failed)"
+    temp_result="$PKG_CONFIG --libs libstatgrab failed"
+  fi
+  AC_MSG_RESULT([$temp_result])
+fi
 
-       AC_COMPILE_IFELSE(
-[#include <stdio.h>
-#include <sys/types.h>
-#include <asm/types.h>
-#include <sys/socket.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
+if test "x$with_libstatgrab" = "xyes"
+then
+  SAVE_CPPFLAGS="$CPPFLAGS"
+  CPPFLAGS="$CPPFLAGS $with_libstatgrab_cflags"
 
-int main (void)
-{
-       int retval = TCA_STATS;
-       return (retval);
-}],
-       [AC_DEFINE([HAVE_TCA_STATS], 1, [True if the enum-member TCA_STATS exists])]
-       []);
+  AC_CHECK_HEADERS(statgrab.h,
+                  [with_libstatgrab="yes"],
+                  [with_libstatgrab="no (statgrab.h not found)"])
 
-       CFLAGS="$SAVE_CFLAGS"
+  CPPFLAGS="$SAVE_CPPFLAGS"
 fi
-if test "x$with_libnetlink" = "xyes"
+
+if test "x$with_libstatgrab" = "xyes"
 then
-       AC_CHECK_LIB(netlink, rtnl_open,
-                    [with_libnetlink="yes"],
-                    [with_libnetlink="no (symbol 'rtnl_open' not found)"],
-                    [$with_libnetlink_libs])
+  SAVE_CFLAGS="$CFLAGS"
+  SAVE_LDFLAGS="$LDFLAGS"
+
+  CFLAGS="$CFLAGS $with_libstatgrab_cflags"
+  LDFLAGS="$LDFLAGS $with_libstatgrab_ldflags"
+
+  AC_CHECK_LIB(statgrab, sg_init,
+              [with_libstatgrab="yes"],
+              [with_libstatgrab="no (symbol sg_init not found)"])
+
+  CFLAGS="$SAVE_CFLAGS"
+  LDFLAGS="$SAVE_LDFLAGS"
 fi
-if test "x$with_libnetlink" = "xyes"
+
+AM_CONDITIONAL(BUILD_WITH_LIBSTATGRAB, test "x$with_libstatgrab" = "xyes")
+if test "x$with_libstatgrab" = "xyes"
 then
-       BUILD_WITH_LIBNETLINK_CFLAGS="$with_libnetlink_cflags"
-       BUILD_WITH_LIBNETLINK_LIBS="$with_libnetlink_libs"
-       AC_SUBST(BUILD_WITH_LIBNETLINK_CFLAGS)
-       AC_SUBST(BUILD_WITH_LIBNETLINK_LIBS)
+  AC_DEFINE(HAVE_LIBSTATGRAB, 1, [Define to 1 if you have the 'statgrab' library (-lstatgrab)])
+  BUILD_WITH_LIBSTATGRAB_CFLAGS="$with_libstatgrab_cflags"
+  BUILD_WITH_LIBSTATGRAB_LDFLAGS="$with_libstatgrab_ldflags"
+  AC_SUBST(BUILD_WITH_LIBSTATGRAB_CFLAGS)
+  AC_SUBST(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
+# }}}
 
-with_libopenipmipthread="yes"
-with_libopenipmipthread_cflags=""
-with_libopenipmipthread_libs=""
+# --with-libupsclient {{{
+with_libupsclient_config=""
+with_libupsclient_cflags=""
+with_libupsclient_libs=""
+AC_ARG_WITH(libupsclient, [AS_HELP_STRING([--with-libupsclient@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])],
+[
+       if test "x$withval" = "xno"
+       then
+               with_libupsclient="no"
+       else if test "x$withval" = "xyes"
+       then
+               with_libupsclient="use_pkgconfig"
+       else
+               if test -x "$withval"
+               then
+                       with_libupsclient_config="$withval"
+                       with_libupsclient="use_libupsclient_config"
+               else if test -x "$withval/bin/libupsclient-config"
+               then
+                       with_libupsclient_config="$withval/bin/net-snmp-config"
+                       with_libupsclient="use_libupsclient_config"
+               else
+                       AC_MSG_NOTICE([Not checking for libupsclient: Manually configured])
+                       with_libupsclient_cflags="-I$withval/include"
+                       with_libupsclient_libs="-L$withval/lib -lupsclient"
+                       with_libupsclient="yes"
+               fi; fi
+       fi; fi
+],
+[with_libupsclient="use_pkgconfig"])
 
-AC_MSG_CHECKING([for pkg-config])
-temp_result="no"
-if test "x$PKG_CONFIG" = "x"
+# configure using libupsclient-config
+if test "x$with_libupsclient" = "xuse_libupsclient_config"
 then
-       with_libopenipmipthread="no"
-       temp_result="no"
-else
-       temp_result="$PKG_CONFIG"
+       AC_MSG_NOTICE([Checking for libupsclient using $with_libupsclient_config])
+       with_libupsclient_cflags="`$with_libupsclient_config --cflags`"
+       if test $? -ne 0
+       then
+               with_libupsclient="no ($with_libupsclient_config failed)"
+       fi
+       with_libupsclient_libs="`$with_libupsclient_config --libs`"
+       if test $? -ne 0
+       then
+               with_libupsclient="no ($with_libupsclient_config failed)"
+       fi
+fi
+if test "x$with_libupsclient" = "xuse_libupsclient_config"
+then
+       with_libupsclient="yes"
 fi
-AC_MSG_RESULT([$temp_result])
 
-if test "x$with_libopenipmipthread" = "xyes"
+# configure using pkg-config
+if test "x$with_libupsclient" = "xuse_pkgconfig"
 then
-       AC_MSG_CHECKING([for libOpenIPMIpthread])
-       $PKG_CONFIG --exists OpenIPMIpthread 2>/dev/null
-       if test "$?" != "0"
+       if test "x$PKG_CONFIG" = "x"
        then
-               with_libopenipmipthread="no ($PKG_CONFIG doesn't know OpenIPMIpthread)"
+               with_libupsclient="no (Don't have pkg-config)"
        fi
-       AC_MSG_RESULT([$with_libopenipmipthread])
 fi
-
-if test "x$with_libopenipmipthread" = "xyes"
+if test "x$with_libupsclient" = "xuse_pkgconfig"
 then
-       AC_MSG_CHECKING([for libOpenIPMIpthread CFLAGS])
-       temp_result="`$PKG_CONFIG --cflags OpenIPMIpthread`"
-       if test "$?" = "0"
+       AC_MSG_NOTICE([Checking for libupsclient using $PKG_CONFIG])
+       $PKG_CONFIG --exists 'libupsclient' 2>/dev/null
+       if test $? -ne 0
        then
-               with_libopenipmipthread_cflags="$temp_result"
-       else
-               with_libopenipmipthread="no ($PKG_CONFIG --cflags OpenIPMIpthread failed)"
-               temp_result="$PKG_CONFIG --cflags OpenIPMIpthread failed"
+               with_libupsclient="no (pkg-config doesn't know library)"
        fi
-       AC_MSG_RESULT([$temp_result])
 fi
-
-if test "x$with_libopenipmipthread" = "xyes"
+if test "x$with_libupsclient" = "xuse_pkgconfig"
 then
-       AC_MSG_CHECKING([for libOpenIPMIpthread LDFLAGS])
-       temp_result="`$PKG_CONFIG --libs OpenIPMIpthread`"
-       if test "$?" = "0"
+       with_libupsclient_cflags="`$PKG_CONFIG --cflags 'libupsclient'`"
+       if test $? -ne 0
        then
-               with_libopenipmipthread_ldflags="$temp_result"
-       else
-               with_libopenipmipthread="no ($PKG_CONFIG --libs OpenIPMIpthread failed)"
-               temp_result="$PKG_CONFIG --libs OpenIPMIpthread failed"
+               with_libupsclient="no ($PKG_CONFIG failed)"
        fi
-       AC_MSG_RESULT([$temp_result])
+       with_libupsclient_libs="`$PKG_CONFIG --libs 'libupsclient'`"
+       if test $? -ne 0
+       then
+               with_libupsclient="no ($PKG_CONFIG failed)"
+       fi
+fi
+if test "x$with_libupsclient" = "xuse_pkgconfig"
+then
+       with_libupsclient="yes"
+fi
+
+# with_libupsclient_cflags and with_libupsclient_libs are set up now, let's do
+# the actual checks.
+if test "x$with_libupsclient" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
+
+       AC_CHECK_HEADERS(upsclient.h, [], [with_libupsclient="no (upsclient.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
 fi
-
-if test "x$with_libopenipmipthread" = "xyes"
+if test "x$with_libupsclient" = "xyes"
 then
        SAVE_CPPFLAGS="$CPPFLAGS"
-       CPPFLAGS="$CPPFLAGS $with_libopenipmipthread_cflags"
+       SAVE_LDFLAGS="$LDFLAGS"
 
-       AC_CHECK_HEADERS(OpenIPMI/ipmi_smi.h,
-                        [with_libopenipmipthread="yes"],
-                        [with_libopenipmipthread="no (OpenIPMI/ipmi_smi.h not found)"],
-[#include <OpenIPMI/ipmiif.h>
-#include <OpenIPMI/ipmi_err.h>
-#include <OpenIPMI/ipmi_posix.h>
-#include <OpenIPMI/ipmi_conn.h>
-])
+       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
+       LDFLAGS="$LDFLAGS $with_libupsclient_libs"
+
+       AC_CHECK_LIB(upsclient, upscli_connect,
+                    [with_libupsclient="yes"],
+                    [with_libupsclient="no (symbol upscli_connect not found)"])
 
        CPPFLAGS="$SAVE_CPPFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
 fi
+if test "x$with_libupsclient" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libupsclient_cflags"
 
-if test "x$with_libopenipmipthread" = "xyes"
+       AC_CHECK_TYPES([UPSCONN_t, UPSCONN], [], [],
+[#include <stdlib.h>
+#include <stdio.h>
+#include <upsclient.h>])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libupsclient" = "xyes"
 then
-       BUILD_WITH_OPENIPMI_CFLAGS="$with_libopenipmipthread_cflags"
-       BUILD_WITH_OPENIPMI_LIBS="$with_libopenipmipthread_ldflags"
-       AC_SUBST(BUILD_WITH_OPENIPMI_CFLAGS)
-       AC_SUBST(BUILD_WITH_OPENIPMI_LIBS)
+       BUILD_WITH_LIBUPSCLIENT_CFLAGS="$with_libupsclient_cflags"
+       BUILD_WITH_LIBUPSCLIENT_LIBS="$with_libupsclient_libs"
+       AC_SUBST(BUILD_WITH_LIBUPSCLIENT_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBUPSCLIENT_LIBS)
 fi
+# }}}
 
-dnl Check for libpq.
-with_pg_config="pg_config"
-with_libpq_includedir=""
-with_libpq_libdir=""
-with_libpq_cppflags=""
-with_libpq_ldflags=""
-AC_ARG_WITH(libpq, [AS_HELP_STRING([--with-libpq@<:@=PREFIX@:>@],
-       [Path to libpq.])],
+# --with-libxmms {{{
+with_xmms_config="xmms-config"
+with_xmms_cflags=""
+with_xmms_libs=""
+AC_ARG_WITH(libxmms, [AS_HELP_STRING([--with-libxmms@<:@=PREFIX@:>@], [Path to libxmms.])],
 [
-       if test "x$withval" = "xno"
-       then
-               with_libpq="no"
-       else if test "x$withval" = "xyes"
+       if test "x$withval" != "xno" \
+               && test "x$withval" != "xyes"
        then
-               with_libpq="yes"
-       else
                if test -f "$withval" && test -x "$withval";
                then
-                       with_pg_config="$withval"
-               else if test -x "$withval/bin/pg_config"
+                       with_xmms_config="$withval"
+               else if test -x "$withval/bin/xmms-config"
                then
-                       with_pg_config="$withval/bin/pg_config"
+                       with_xmms_config="$withval/bin/xmms-config"
                fi; fi
-               with_libpq="yes"
+               with_libxmms="yes"
+       else if test "x$withval" = "xno"
+       then
+               with_libxmms="no"
+       else
+               with_libxmms="yes"
        fi; fi
 ],
 [
-       with_libpq="yes"
+       with_libxmms="yes"
 ])
-if test "x$with_libpq" = "xyes"
+if test "x$with_libxmms" = "xyes"
 then
-       with_libpq_includedir=`$with_pg_config --includedir 2> /dev/null`
-       pg_config_status=$?
+       with_xmms_cflags=`$with_xmms_config --cflags 2>/dev/null`
+       xmms_config_status=$?
 
-       if test $pg_config_status -eq 0
+       if test $xmms_config_status -ne 0
        then
-               if test -n "$with_libpq_includedir"; then
-                       for dir in $with_libpq_includedir; do
-                               with_libpq_cppflags="$with_libpq_cppflags -I$dir"
-                       done
-               fi
-       else
-               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
+               with_libxmms="no"
        fi
-
-       SAVE_CPPFLAGS="$CPPFLAGS"
-       CPPFLAGS="$CPPFLAGS $with_libpq_cppflags"
-
-       AC_CHECK_HEADERS(libpq-fe.h, [],
-               [with_libpq="no (libpq-fe.h not found)"], [])
-
-       CPPFLAGS="$SAVE_CPPFLAGS"
 fi
-if test "x$with_libpq" = "xyes"
+if test "x$with_libxmms" = "xyes"
 then
-       with_libpq_libdir=`$with_pg_config --libdir 2> /dev/null`
-       pg_config_status=$?
+       with_xmms_libs=`$with_xmms_config --libs 2>/dev/null`
+       xmms_config_status=$?
 
-       if test $pg_config_status -eq 0
+       if test $xmms_config_status -ne 0
        then
-               if test -n "$with_libpq_libdir"; then
-                       for dir in $with_libpq_libdir; do
-                               with_libpq_ldflags="$with_libpq_ldflags -L$dir"
-                       done
-               fi
-       else
-               AC_MSG_WARN([$with_pg_config returned with status $pg_config_status])
+               with_libxmms="no"
        fi
-
-       SAVE_LDFLAGS="$LDFLAGS"
-       LDFLAGS="$LDFLAGS $with_libpq_ldflags"
-
-       AC_CHECK_LIB(pq, PQconnectdb,
-               [with_libpq="yes"],
-               [with_libpq="no (symbol 'PQconnectdb' not found)"])
-
-       LDFLAGS="$SAVE_LDFLAGS"
 fi
-if test "x$with_libpq" = "xyes"
+if test "x$with_libxmms" = "xyes"
 then
-       BUILD_WITH_LIBPQ_CPPFLAGS="$with_libpq_cppflags"
-       BUILD_WITH_LIBPQ_LDFLAGS="$with_libpq_ldflags"
-       AC_SUBST(BUILD_WITH_LIBPQ_CPPFLAGS)
-       AC_SUBST(BUILD_WITH_LIBPQ_LDFLAGS)
+       AC_CHECK_LIB(xmms, xmms_remote_get_info,
+       [
+               BUILD_WITH_LIBXMMS_CFLAGS="$with_xmms_cflags"
+               BUILD_WITH_LIBXMMS_LIBS="$with_xmms_libs"
+               AC_SUBST(BUILD_WITH_LIBXMMS_CFLAGS)
+               AC_SUBST(BUILD_WITH_LIBXMMS_LIBS)
+       ],
+       [
+               with_libxmms="no"
+       ],
+       [$with_xmms_libs])
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBPQ, test "x$with_libpq" = "xyes")
+with_libxmms_numeric=0
+if test "x$with_libxmms" = "xyes"
+then
+       with_libxmms_numeric=1
+fi
+AC_DEFINE_UNQUOTED(HAVE_LIBXMMS, [$with_libxmms_numeric], [Define to 1 if you have the 'xmms' library (-lxmms).])
+AM_CONDITIONAL(BUILD_WITH_LIBXMMS, test "x$with_libxmms" = "xyes")
+# }}}
 
-dnl Check for libvirt and libxml2 libraries.
+# pkg-config --exists 'libxml-2.0'; pkg-config --exists libvirt {{{
 with_libxml2="no (pkg-config isn't available)"
 with_libxml2_cflags=""
 with_libxml2_ldflags=""
@@ -2523,8 +2684,92 @@ if test "x$with_libvirt" = "xyes"; then
        AC_SUBST(BUILD_WITH_LIBVIRT_CFLAGS)
        AC_SUBST(BUILD_WITH_LIBVIRT_LIBS)
 fi
+# }}}
+
+# $PKG_CONFIG --exists OpenIPMIpthread {{{
+with_libopenipmipthread="yes"
+with_libopenipmipthread_cflags=""
+with_libopenipmipthread_libs=""
+
+AC_MSG_CHECKING([for pkg-config])
+temp_result="no"
+if test "x$PKG_CONFIG" = "x"
+then
+       with_libopenipmipthread="no"
+       temp_result="no"
+else
+       temp_result="$PKG_CONFIG"
+fi
+AC_MSG_RESULT([$temp_result])
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread])
+       $PKG_CONFIG --exists OpenIPMIpthread 2>/dev/null
+       if test "$?" != "0"
+       then
+               with_libopenipmipthread="no ($PKG_CONFIG doesn't know OpenIPMIpthread)"
+       fi
+       AC_MSG_RESULT([$with_libopenipmipthread])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread CFLAGS])
+       temp_result="`$PKG_CONFIG --cflags OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_cflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --cflags OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --cflags OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       AC_MSG_CHECKING([for libOpenIPMIpthread LDFLAGS])
+       temp_result="`$PKG_CONFIG --libs OpenIPMIpthread`"
+       if test "$?" = "0"
+       then
+               with_libopenipmipthread_ldflags="$temp_result"
+       else
+               with_libopenipmipthread="no ($PKG_CONFIG --libs OpenIPMIpthread failed)"
+               temp_result="$PKG_CONFIG --libs OpenIPMIpthread failed"
+       fi
+       AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libopenipmipthread_cflags"
+
+       AC_CHECK_HEADERS(OpenIPMI/ipmi_smi.h,
+                        [with_libopenipmipthread="yes"],
+                        [with_libopenipmipthread="no (OpenIPMI/ipmi_smi.h not found)"],
+[#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+       BUILD_WITH_OPENIPMI_CFLAGS="$with_libopenipmipthread_cflags"
+       BUILD_WITH_OPENIPMI_LIBS="$with_libopenipmipthread_ldflags"
+       AC_SUBST(BUILD_WITH_OPENIPMI_CFLAGS)
+       AC_SUBST(BUILD_WITH_OPENIPMI_LIBS)
+fi
+# }}}
 
-dnl End of check for libvirt and libxml2 libraries.
+PKG_CHECK_MODULES([LIBNOTIFY], [libnotify],
+               [with_libnotify="yes"],
+               [with_libnotify="no ($LIBNOTIFY_PKG_ERRORS)"])
 
 # Check for enabled/disabled features
 #
@@ -2638,6 +2883,7 @@ AC_COLLECTD([getifaddrs],[enable],  [feature], [getifaddrs under Linux])
 dependency_error="no"
 plugin_ascent="no"
 plugin_battery="no"
+plugin_bind="no"
 plugin_cpu="no"
 plugin_cpufreq="no"
 plugin_df="no"
@@ -2734,6 +2980,7 @@ fi
 if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"
 then
        plugin_ascent="yes"
+       plugin_bind="yes"
 fi
 
 if test "x$with_libopenipmipthread" = "xyes"
@@ -2745,6 +2992,11 @@ if test "x$have_processor_info" = "xyes"
 then
        plugin_cpu="yes"
 fi
+if test "x$have_sysctl" = "xyes"
+then
+       plugin_cpu="yes"
+       plugin_swap="yes"
+fi
 if test "x$have_sysctlbyname" = "xyes"
 then
        plugin_cpu="yes"
@@ -2833,6 +3085,11 @@ then
        plugin_swap="yes"
 fi
 
+if test "x$have_swapctl" = "xyes"
+then
+       plugin_swap="yes"
+fi
+
 if test "x$with_kvm_openfiles$with_kvm_nlist" = "xyesyes"
 then
        plugin_tcpconns="yes"
@@ -2847,8 +3104,6 @@ then
        plugin_users="yes"
 fi
 
-# FIXME: sysctl for swap plugin
-
 m4_divert_once([HELP_ENABLE], [
 collectd plugins:])
 
@@ -2857,10 +3112,12 @@ AC_PLUGIN([apcups],      [yes],                [Statistics of UPSes by APC])
 AC_PLUGIN([apple_sensors], [$with_libiokit],   [Apple's hardware sensors])
 AC_PLUGIN([ascent],      [$plugin_ascent],     [AscentEmu player statistics])
 AC_PLUGIN([battery],     [$plugin_battery],    [Battery statistics])
-AC_PLUGIN([cpu],         [$plugin_cpu],        [CPU usage statistics])
+AC_PLUGIN([bind],        [$plugin_bind],       [ISC Bind nameserver statistics])
 AC_PLUGIN([cpufreq],     [$plugin_cpufreq],    [CPU frequency statistics])
+AC_PLUGIN([cpu],         [$plugin_cpu],        [CPU usage statistics])
 AC_PLUGIN([csv],         [yes],                [CSV output plugin])
-AC_PLUGIN([notify_desktop], [$with_libnotify], [Desktop notifications])
+AC_PLUGIN([curl],        [$with_libcurl],      [CURL generic web statistics])
+AC_PLUGIN([dbi],         [$with_libdbi],       [General database statistics])
 AC_PLUGIN([df],          [$plugin_df],         [Filesystem usage statistics])
 AC_PLUGIN([disk],        [$plugin_disk],       [Disk usage statistics])
 AC_PLUGIN([dns],         [$with_libpcap],      [DNS traffic analysis])
@@ -2870,13 +3127,16 @@ AC_PLUGIN([exec],        [yes],                [Execution of external programs])
 AC_PLUGIN([filecount],   [yes],                [Count files in directories])
 AC_PLUGIN([hddtemp],     [yes],                [Query hddtempd])
 AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
-AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
 AC_PLUGIN([ipmi],        [$plugin_ipmi],       [IPMI sensor statistics])
+AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
 AC_PLUGIN([ipvs],        [$plugin_ipvs],       [IPVS connection statistics])
 AC_PLUGIN([irq],         [$plugin_irq],        [IRQ statistics])
 AC_PLUGIN([libvirt],     [$plugin_libvirt],    [Virtual machine statistics])
 AC_PLUGIN([load],        [$plugin_load],       [System load])
 AC_PLUGIN([logfile],     [yes],                [File logging plugin])
+AC_PLUGIN([match_regex], [yes],                [The regex match])
+AC_PLUGIN([match_timediff], [yes],             [The timediff match])
+AC_PLUGIN([match_value], [yes],                [The value match])
 AC_PLUGIN([mbmon],       [yes],                [Query mbmond])
 AC_PLUGIN([memcached],   [yes],                [memcached statistics])
 AC_PLUGIN([memory],      [$plugin_memory],     [Memory usage])
@@ -2886,23 +3146,30 @@ AC_PLUGIN([netlink],     [$with_libnetlink],   [Enhanced Linux network statistic
 AC_PLUGIN([network],     [yes],                [Network communication plugin])
 AC_PLUGIN([nfs],         [$plugin_nfs],        [NFS statistics])
 AC_PLUGIN([nginx],       [$with_libcurl],      [nginx statistics])
+AC_PLUGIN([notify_desktop], [$with_libnotify], [Desktop notifications])
 AC_PLUGIN([notify_email], [$with_libesmtp],    [Email notifier])
 AC_PLUGIN([ntpd],        [yes],                [NTPd statistics])
 AC_PLUGIN([nut],         [$with_libupsclient], [Network UPS tools statistics])
 AC_PLUGIN([onewire],     [$with_libowcapi],    [OneWire sensor statistics])
+AC_PLUGIN([openvpn],     [yes],                [OpenVPN client statistics])
+AC_PLUGIN([oracle],      [$with_oracle],       [Oracle plugin])
 AC_PLUGIN([perl],        [$plugin_perl],       [Embed a Perl interpreter])
 AC_PLUGIN([ping],        [$with_liboping],     [Network latency statistics])
 AC_PLUGIN([postgresql],  [$with_libpq],        [PostgreSQL database statistics])
 AC_PLUGIN([powerdns],    [yes],                [PowerDNS statistics])
 AC_PLUGIN([processes],   [$plugin_processes],  [Process statistics])
-AC_PLUGIN([rrdtool],     [$with_rrdtool],      [RRDTool output plugin])
-AC_PLUGIN([sensors],     [$with_lm_sensors],   [lm_sensors statistics])
+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])
 AC_PLUGIN([serial],      [$plugin_serial],     [serial port traffic])
 AC_PLUGIN([snmp],        [$with_libnetsnmp],   [SNMP querying plugin])
 AC_PLUGIN([swap],        [$plugin_swap],       [Swap usage statistics])
 AC_PLUGIN([syslog],      [$have_syslog],       [Syslog logging plugin])
 AC_PLUGIN([tail],        [yes],                [Parsing of logfiles])
 AC_PLUGIN([tape],        [$plugin_tape],       [Tape drive statistics])
+AC_PLUGIN([target_notification], [yes],        [The notification target])
+AC_PLUGIN([target_replace], [yes],             [The replace target])
+AC_PLUGIN([target_set],  [yes],                [The set target])
 AC_PLUGIN([tcpconns],    [$plugin_tcpconns],   [TCP connection statistics])
 AC_PLUGIN([teamspeak2],  [yes],                [TeamSpeak2 server statistics])
 AC_PLUGIN([thermal],     [$plugin_thermal],    [Linux ACPI thermal zone statistics])
@@ -2951,12 +3218,29 @@ fi
 AC_SUBST(PERL_BINDINGS)
 AC_SUBST(PERL_BINDINGS_OPTIONS)
 
-AC_OUTPUT(Makefile src/Makefile src/collectd.conf src/libiptc/Makefile src/liboconfig/Makefile src/liboping/Makefile bindings/Makefile)
+dnl libcollectdclient
+LCC_VERSION_MAJOR=`echo $PACKAGE_VERSION | cut -d'.' -f1`
+LCC_VERSION_MINOR=`echo $PACKAGE_VERSION | cut -d'.' -f2`
+LCC_VERSION_PATCH=`echo $PACKAGE_VERSION | cut -d'.' -f3`
+
+LCC_VERSION_EXTRA=`echo $PACKAGE_VERSION | cut -d'.' -f4-`
+
+LCC_VERSION_STRING="$LCC_VERSION_MAJOR.$LCC_VERSION_MINOR.$LCC_VERSION_PATCH"
 
-if test "x$with_rrdtool" = "xyes" \
+AC_SUBST(LCC_VERSION_MAJOR)
+AC_SUBST(LCC_VERSION_MINOR)
+AC_SUBST(LCC_VERSION_PATCH)
+AC_SUBST(LCC_VERSION_EXTRA)
+AC_SUBST(LCC_VERSION_STRING)
+
+AC_CONFIG_FILES(src/libcollectdclient/lcc_features.h)
+
+AC_OUTPUT(Makefile src/Makefile src/collectd.conf src/libiptc/Makefile src/libcollectdclient/Makefile src/libcollectdclient/libcollectdclient.pc src/liboconfig/Makefile src/liboping/Makefile bindings/Makefile)
+
+if test "x$with_librrd" = "xyes" \
        && test "x$librrd_threadsafe" != "xyes"
 then
-       with_rrdtool="yes (warning: librrd is not thread-safe)"
+       with_librrd="yes (warning: librrd is not thread-safe)"
 fi
 
 if test "x$with_liboping" = "xyes" \
@@ -2988,6 +3272,7 @@ cat <<EOF;
 Configuration:
   Libraries:
     libcurl . . . . . . . $with_libcurl
+    libdbi  . . . . . . . $with_libdbi
     libesmtp  . . . . . . $with_libesmtp
     libiokit  . . . . . . $with_libiokit
     libiptc . . . . . . . $with_libiptc
@@ -3002,15 +3287,16 @@ Configuration:
     liboping  . . . . . . $with_liboping
     libpcap . . . . . . . $with_libpcap
     libperl . . . . . . . $with_libperl
-    libpthread  . . . . . $with_libpthread
     libpq . . . . . . . . $with_libpq
-    librrd  . . . . . . . $with_rrdtool
-    libsensors  . . . . . $with_lm_sensors
+    libpthread  . . . . . $with_libpthread
+    librrd  . . . . . . . $with_librrd
+    libsensors  . . . . . $with_libsensors
     libstatgrab . . . . . $with_libstatgrab
     libupsclient  . . . . $with_libupsclient
     libvirt . . . . . . . $with_libvirt
     libxml2 . . . . . . . $with_libxml2
     libxmms . . . . . . . $with_libxmms
+    oracle  . . . . . . . $with_oracle
 
   Features:
     daemon mode . . . . . $enable_daemon
@@ -3025,9 +3311,12 @@ Configuration:
     apple_sensors . . . . $enable_apple_sensors
     ascent  . . . . . . . $enable_ascent
     battery . . . . . . . $enable_battery
+    bind  . . . . . . . . $enable_bind
     cpu . . . . . . . . . $enable_cpu
     cpufreq . . . . . . . $enable_cpufreq
     csv . . . . . . . . . $enable_csv
+    curl  . . . . . . . . $enable_curl
+    dbi . . . . . . . . . $enable_dbi
     df  . . . . . . . . . $enable_df
     disk  . . . . . . . . $enable_disk
     dns . . . . . . . . . $enable_dns
@@ -3037,13 +3326,16 @@ Configuration:
     filecount . . . . . . $enable_filecount
     hddtemp . . . . . . . $enable_hddtemp
     interface . . . . . . $enable_interface
-    iptables  . . . . . . $enable_iptables
     ipmi  . . . . . . . . $enable_ipmi
+    iptables  . . . . . . $enable_iptables
     ipvs  . . . . . . . . $enable_ipvs
     irq . . . . . . . . . $enable_irq
     libvirt . . . . . . . $enable_libvirt
     load  . . . . . . . . $enable_load
     logfile . . . . . . . $enable_logfile
+    match_regex . . . . . $enable_match_regex
+    match_timediff  . . . $enable_match_timediff
+    match_value . . . . . $enable_match_value
     mbmon . . . . . . . . $enable_mbmon
     memcached . . . . . . $enable_memcached
     memory  . . . . . . . $enable_memory
@@ -3058,11 +3350,14 @@ Configuration:
     ntpd  . . . . . . . . $enable_ntpd
     nut . . . . . . . . . $enable_nut
     onewire . . . . . . . $enable_onewire
+    openvpn . . . . . . . $enable_openvpn
+    oracle  . . . . . . . $enable_oracle
     perl  . . . . . . . . $enable_perl
     ping  . . . . . . . . $enable_ping
     postgresql  . . . . . $enable_postgresql
     powerdns  . . . . . . $enable_powerdns
     processes . . . . . . $enable_processes
+    rrdcached . . . . . . $enable_rrdcached
     rrdtool . . . . . . . $enable_rrdtool
     sensors . . . . . . . $enable_sensors
     serial  . . . . . . . $enable_serial
@@ -3071,6 +3366,9 @@ Configuration:
     syslog  . . . . . . . $enable_syslog
     tail  . . . . . . . . $enable_tail
     tape  . . . . . . . . $enable_tape
+    target_notification . $enable_target_notification
+    target_replace  . . . $enable_target_replace
+    target_set  . . . . . $enable_target_set
     tcpconns  . . . . . . $enable_tcpconns
     teamspeak2  . . . . . $enable_teamspeak2
     thermal . . . . . . . $enable_thermal
@@ -3087,3 +3385,5 @@ EOF
 if test "x$dependency_error" = "xyes"; then
        AC_MSG_ERROR("Some plugins are missing dependencies - see above summary for details")
 fi
+
+# vim: set fdm=marker :
index 1c34812..01b9e2c 100644 (file)
@@ -80,3 +80,8 @@ snmp-data.conf
 data that is defined in the IF-MIB, e. g. octet or packet counters, UPS-MIB and
 whatever people have send in. If you have some more definitions please send
 them in, so others can profit from it.
+
+solaris-smf
+-----------
+  Manifest file for the Solaris SMF system and detailed information on how to
+register collectd as a service with this system.
index c143aea..3f1f7c8 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/perl
 #
 # collectd - contrib/cussh.pl
-# Copyright (C) 2007-2008  Sebastian Harl
+# Copyright (C) 2007-2009  Sebastian Harl
 #
 # This program is free software; you can redistribute it and/or modify it
 # under the terms of the GNU General Public License as published by the
index 223a8a5..9163730 100755 (executable)
@@ -59,7 +59,7 @@ the C<nut> plugin above provides C<in> and C<out> which are both voltages, you
 can use a hyphen to add a "type instance" to the type.
 
 For a list of already defined "types" look at the F<types.db> file in
-collectd's library path, e.E<nbsp>g. F</usr/lib/collectd/>.
+collectd's shared data directory, e.E<nbsp>g. F</usr/share/collectd/>.
 
 =item B<Interval> I<Seconds>
 
index 89ce123..35eb8be 100644 (file)
@@ -232,7 +232,7 @@ exit 0
 %attr(0644,root,root) %{_libdir}/%{name}/wireless.so*
 %attr(0644,root,root) %{_libdir}/%{name}/wireless.la
 
-%attr(0644,root,root) %{_libdir}/%{name}/types.db
+%attr(0644,root,root) %{_datadir}/%{name}/types.db
 
 %dir /var/lib/collectd
 
diff --git a/contrib/oracle/create_schema.ddl b/contrib/oracle/create_schema.ddl
new file mode 100644 (file)
index 0000000..86b1e83
--- /dev/null
@@ -0,0 +1,264 @@
+-- collectd - contrib/oracle/create_schema.ddl
+-- Copyright (C) 2008,2009  Roman Klesel
+--
+-- 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:
+--   Roman Klesel <roman.klesel at noris.de>
+
+-- Description
+--------------
+-- This will create a schema to provide collectd with the required permissions
+-- and space for statistic data.
+-- The idea is to store the output of some expensive queries in static tables
+-- and fill these tables with dbms_scheduler jobs as often as necessary.
+-- collectd will then just read from the static tables. This will reduces the
+-- chance that your system will be killed by excessive monitoring queries and
+-- gives the dba control on the interval the information provided to collectd
+-- will be refreshed. You have to create a dbms_scheduler job for each of the
+-- schemas you what to monitor for object-space-usage. See the example below.
+--
+-- Requirements
+---------------
+-- make sure you have: 
+--             write permission in $PWD
+--             you have GID of oracle software owner
+--             set $ORACLE_HOME 
+--             set $ORACLE_SID
+--             DB is up an running in RW mode
+-- execute like this:
+-- sqlplus /nolog @ create_collectd-schema.dll
+
+spool create_collectd-schema.log
+connect / as sysdba
+
+-- Create user, tablespace and permissions
+CREATE TABLESPACE "COLLECTD-TBS" 
+       DATAFILE SIZE 30M 
+       AUTOEXTEND ON 
+       NEXT 10M 
+       MAXSIZE 300M
+       LOGGING 
+       EXTENT MANAGEMENT LOCAL 
+       SEGMENT SPACE MANAGEMENT AUTO 
+       DEFAULT NOCOMPRESS;
+
+CREATE ROLE "CREATE_COLLECTD_SCHEMA" NOT IDENTIFIED;
+GRANT CREATE JOB TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE SEQUENCE TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE SYNONYM TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE TABLE TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE VIEW TO "CREATE_COLLECTD_SCHEMA";
+GRANT CREATE PROCEDURE TO "CREATE_COLLECTD_SCHEMA";
+
+CREATE USER "COLLECTDU" 
+       PROFILE "DEFAULT" 
+       IDENTIFIED BY "Change_me-1st" 
+       PASSWORD EXPIRE 
+       DEFAULT TABLESPACE "COLLECTD-TBS"
+       TEMPORARY TABLESPACE "TEMP"
+       QUOTA UNLIMITED ON "COLLECTD-TBS"
+       ACCOUNT UNLOCK;
+
+GRANT "CONNECT" TO "COLLECTDU";
+GRANT "SELECT_CATALOG_ROLE" TO "COLLECTDU";
+GRANT "CREATE_COLLECTD_SCHEMA" TO "COLLECTDU";
+GRANT analyze any TO "COLLECTDU";
+GRANT select on dba_tables TO "COLLECTDU";
+GRANT select on dba_lobs TO "COLLECTDU";
+GRANT select on dba_indexes TO "COLLECTDU";
+GRANT select on dba_segments TO "COLLECTDU";
+GRANT select on dba_tab_columns TO "COLLECTDU";
+GRANT select on dba_free_space TO "COLLECTDU";
+GRANT select on dba_data_files TO "COLLECTDU";
+-- Create tables and indexes
+
+alter session set current_schema=collectdu;
+
+create table c_tbs_usage (
+       tablespace_name varchar2(30),
+       bytes_free number,
+    bytes_used  number,
+        CONSTRAINT "C_TBS_USAGE_UK1" UNIQUE ("TABLESPACE_NAME") USING INDEX
+        TABLESPACE "COLLECTD-TBS"  ENABLE)
+        TABLESPACE "COLLECTD-TBS";
+
+CREATE TABLE "COLLECTDU"."C_TBL_SIZE" (
+    "OWNER" VARCHAR2(30 BYTE), 
+       "TABLE_NAME" VARCHAR2(30 BYTE), 
+       "BYTES" NUMBER, 
+        CONSTRAINT "C_TBL_SIZE_UK1" UNIQUE ("OWNER", "TABLE_NAME")
+         USING INDEX TABLESPACE "COLLECTD-TBS"  ENABLE)
+         TABLESPACE "COLLECTD-TBS" ;
+
+create or replace PROCEDURE get_object_size(owner IN VARCHAR2) AS
+
+v_owner VARCHAR2(30) := owner;
+
+l_free_blks NUMBER;
+l_total_blocks NUMBER;
+l_total_bytes NUMBER;
+l_unused_blocks NUMBER;
+l_unused_bytes NUMBER;
+l_lastusedextfileid NUMBER;
+l_lastusedextblockid NUMBER;
+l_last_used_block NUMBER;
+
+CURSOR cur_tbl IS
+SELECT owner,
+  TABLE_NAME
+FROM dba_tables
+WHERE owner = v_owner;
+
+CURSOR cur_idx IS
+SELECT owner,
+  index_name,
+  TABLE_NAME
+FROM dba_indexes
+WHERE owner = v_owner;
+
+CURSOR cur_lob IS
+SELECT owner,
+  segment_name,
+  TABLE_NAME
+FROM dba_lobs
+WHERE owner = v_owner;
+
+BEGIN
+
+  DELETE FROM c_tbl_size
+  WHERE owner = v_owner;
+  COMMIT;
+
+  FOR r_tbl IN cur_tbl
+  LOOP
+    BEGIN
+      dbms_space.unused_space(segment_owner => r_tbl.owner,   segment_name => r_tbl.TABLE_NAME,   segment_type => 'TABLE',   total_blocks => l_total_blocks,   total_bytes => l_total_bytes,   unused_blocks => l_unused_blocks,   unused_bytes => l_unused_bytes,   last_used_extent_file_id => l_lastusedextfileid,   last_used_extent_block_id => l_lastusedextblockid,   last_used_block => l_last_used_block);
+
+    EXCEPTION
+    WHEN others THEN
+      DBMS_OUTPUT.PUT_LINE('tbl_name: ' || r_tbl.TABLE_NAME);
+    END;
+    INSERT
+    INTO c_tbl_size
+    VALUES(r_tbl.owner,   r_tbl.TABLE_NAME,   l_total_bytes -l_unused_bytes);
+  END LOOP;
+
+  COMMIT;
+
+  FOR r_idx IN cur_idx
+  LOOP
+    BEGIN
+      dbms_space.unused_space(segment_owner => r_idx.owner,   segment_name => r_idx.index_name,   segment_type => 'INDEX',   total_blocks => l_total_blocks,   total_bytes => l_total_bytes,   unused_blocks => l_unused_blocks,   unused_bytes => l_unused_bytes,   last_used_extent_file_id => l_lastusedextfileid,   last_used_extent_block_id => l_lastusedextblockid,   last_used_block => l_last_used_block);
+
+    EXCEPTION
+    WHEN others THEN
+      DBMS_OUTPUT.PUT_LINE('idx_name: ' || r_idx.index_name);
+    END;
+
+    UPDATE c_tbl_size
+    SET bytes = bytes + l_total_bytes -l_unused_bytes
+    WHERE owner = r_idx.owner
+     AND TABLE_NAME = r_idx.TABLE_NAME;
+  END LOOP;
+
+  COMMIT;
+
+  FOR r_lob IN cur_lob
+  LOOP
+    BEGIN
+      dbms_space.unused_space(segment_owner => r_lob.owner,   segment_name => r_lob.segment_name,   segment_type => 'LOB',   total_blocks => l_total_blocks,   total_bytes => l_total_bytes,   unused_blocks => l_unused_blocks,   unused_bytes => l_unused_bytes,   last_used_extent_file_id => l_lastusedextfileid,   last_used_extent_block_id => l_lastusedextblockid,   last_used_block => l_last_used_block);
+
+    EXCEPTION
+    WHEN others THEN
+      DBMS_OUTPUT.PUT_LINE('lob_name: ' || r_lob.segment_name);
+    END;
+
+    UPDATE c_tbl_size
+    SET bytes = bytes + l_total_bytes -l_unused_bytes
+    WHERE owner = r_lob.owner
+     AND TABLE_NAME = r_lob.TABLE_NAME;
+  END LOOP;
+
+  COMMIT;
+
+END get_object_size;
+/
+
+create or replace PROCEDURE get_tbs_size AS
+BEGIN
+
+execute immediate 'truncate table c_tbs_usage';
+
+insert into c_tbs_usage (
+select df.tablespace_name as tablespace_name, 
+       decode(df.maxbytes,
+               0,
+               sum(fs.bytes),
+               (df.maxbytes-(df.bytes-sum(fs.bytes)))) as bytes_free,
+       decode(df.maxbytes,
+               0,
+               round((df.bytes-sum(fs.bytes))),
+               round(df.maxbytes-(df.maxbytes-(df.bytes-sum(fs.bytes))))) as bytes_used
+from dba_free_space fs inner join 
+       (select 
+               tablespace_name, 
+               sum(bytes) bytes, 
+               sum(decode(maxbytes,0,bytes,maxbytes))  maxbytes
+        from dba_data_files
+        group by tablespace_name ) df          
+on fs.tablespace_name = df.tablespace_name
+group by df.tablespace_name,df.maxbytes,df.bytes);
+
+COMMIT;
+
+END get_tbs_size;
+/
+
+BEGIN
+sys.dbms_scheduler.create_job(
+job_name => '"COLLECTDU"."C_TBSSIZE_JOB"',
+job_type => 'PLSQL_BLOCK',
+job_action => 'begin
+   get_tbs_size();
+end;',
+repeat_interval => 'FREQ=MINUTELY;INTERVAL=5',
+start_date => systimestamp at time zone 'Europe/Berlin',
+job_class => '"DEFAULT_JOB_CLASS"',
+auto_drop => FALSE,
+enabled => TRUE);
+END;
+/
+
+BEGIN
+sys.dbms_scheduler.create_job(
+job_name => '"COLLECTDU"."C_TBLSIZE_COLLECTDU_JOB"',
+job_type => 'PLSQL_BLOCK',
+job_action => 'begin
+   get_object_size( owner => ''COLLECTDU'' );
+end;',
+repeat_interval => 'FREQ=HOURLY;INTERVAL=12',
+start_date => systimestamp at time zone 'Europe/Berlin',
+job_class => '"DEFAULT_JOB_CLASS"',
+auto_drop => FALSE,
+enabled => TRUE);
+END;
+/
+
+spool off
+quit
+
+-- vim: set syntax=sql :
diff --git a/contrib/oracle/db_systat.sql b/contrib/oracle/db_systat.sql
new file mode 100644 (file)
index 0000000..cf2a3c4
--- /dev/null
@@ -0,0 +1,55 @@
+-- Table sizes
+SELECT owner,
+  TABLE_NAME,
+  bytes
+FROM collectdu.c_tbl_size;
+
+-- Tablespace sizes
+SELECT tablespace_name,
+  bytes_free,
+  bytes_used
+FROM collectdu.c_tbs_usage;
+
+-- IO per Tablespace
+SELECT SUM(vf.phyblkrd) *8192 AS
+phy_blk_r,
+  SUM(vf.phyblkwrt) *8192 AS
+phy_blk_w,
+  'tablespace' AS
+i_prefix,
+  dt.tablespace_name
+FROM((dba_data_files dd JOIN v$filestat vf ON dd.file_id = vf.file#) JOIN dba_tablespaces dt ON dd.tablespace_name = dt.tablespace_name)
+GROUP BY dt.tablespace_name;
+
+-- Buffer Pool Hit Ratio:
+SELECT DISTINCT 100 *ROUND(1 -((MAX(decode(name,   'physical reads cache',   VALUE))) /(MAX(decode(name,   'db block gets from cache',   VALUE)) + MAX(decode(name,   'consistent gets from cache',   VALUE)))),   4) AS
+VALUE,
+  'BUFFER_CACHE_HIT_RATIO' AS
+buffer_cache_hit_ratio
+FROM v$sysstat;
+
+-- Shared Pool Hit Ratio:
+SELECT 
+  100.0 * sum(PINHITS) / sum(pins) as VALUE,
+  'SHAREDPOOL_HIT_RATIO' AS SHAREDPOOL_HIT_RATIO
+FROM V$LIBRARYCACHE;
+
+-- PGA Hit Ratio:
+SELECT VALUE,
+  'PGA_HIT_RATIO' AS
+pga_hit_ratio
+FROM v$pgastat
+WHERE name = 'cache hit percentage';
+
+-- DB Efficiency
+SELECT ROUND(SUM(decode(metric_name,   'Database Wait Time Ratio',   VALUE)),   2) AS
+database_wait_time_ratio,
+  ROUND(SUM(decode(metric_name,   'Database CPU Time Ratio',   VALUE)),   2) AS
+database_cpu_time_ratio,
+  'DB_EFFICIENCY' AS
+db_efficiency
+FROM sys.v_$sysmetric
+WHERE metric_name IN('Database CPU Time Ratio',   'Database Wait Time Ratio')
+ AND intsize_csec =
+  (SELECT MAX(intsize_csec)
+   FROM sys.v_$sysmetric);
index 7a026fa..0aaaee3 100644 (file)
@@ -9,7 +9,7 @@ FQDNLookup   true
 BaseDir     "/var/lib/collectd"
 PIDFile     "/var/run/collectd.pid"
 PluginDir   "/usr/lib/collectd"
-TypesDB     "/usr/lib/collectd/types.db"
+TypesDB     "/usr/share/collectd/types.db"
 Interval     10
 ReadThreads  5
 
index bce7647..62acc18 100644 (file)
@@ -85,7 +85,7 @@ cp contrib/redhat/collectd.conf $RPM_BUILD_ROOT/etc/collectd.conf
 sed -i 's:#BaseDir     "/usr/var/lib/collectd":BaseDir     "/var/lib/collectd":' $RPM_BUILD_ROOT/etc/collectd.conf
 sed -i 's:#PIDFile     "/usr/var/run/collectd.pid":PIDFile     "/var/run/collectd.pid":' $RPM_BUILD_ROOT/etc/collectd.conf
 sed -i 's:#PluginDir   "/usr/lib/collectd":PluginDir   "/usr/lib/collectd":' $RPM_BUILD_ROOT/etc/collectd.conf
-sed -i 's:#TypesDB     "/usr/lib/collectd/types.db":TypesDB     "/usr/lib/collectd/types.db":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#TypesDB     "/usr/share/collectd/types.db":TypesDB     "/usr/share/collectd/types.db":' $RPM_BUILD_ROOT/etc/collectd.conf
 sed -i 's:#Interval     10:Interval     10:' $RPM_BUILD_ROOT/etc/collectd.conf
 sed -i 's:#ReadThreads  5:ReadThreads  5:' $RPM_BUILD_ROOT/etc/collectd.conf
 ##Move config contribs
@@ -247,7 +247,7 @@ exit 0
 %attr(0644,root,root) %{_libdir}/%{name}/wireless.so*
 %attr(0644,root,root) %{_libdir}/%{name}/wireless.la
 
-%attr(0644,root,root) %{_libdir}/%{name}/types.db
+%attr(0644,root,root) %{_datadir}/%{name}/types.db
 
 %exclude %{_libdir}/perl5/5.8.8/%{_arch}-linux-thread-multi/perllocal.pod
 %attr(0644,root,root) %{_libdir}/perl5/site_perl/5.8.8/Collectd.pm
diff --git a/contrib/solaris-smf/README b/contrib/solaris-smf/README
new file mode 100644 (file)
index 0000000..dfd990b
--- /dev/null
@@ -0,0 +1,331 @@
+SMF is the way Solaris 10 and later preferably manages services. It is intended
+to replace the old init.d process and provides advances features, such as
+automatic restarting of failing services.
+
+The following blog entry by Piotr Hosowicz describes the process in more
+detail. The original entry can be found at
+<http://phosowicz.jogger.pl/2008/12/21/smf-izing-collectd/>.
+
+The files in this directory are:
+
+  README          This file
+  collectd        Start / stop script
+  collectd.xml    SMF manifest for collectd.
+
+------------------------------------------------------------------------
+
+      SMF-izing collectd <#>
+
+Wpis na 0. poziomie, wysłany 21 grudnia 2008 o 16:30:49.
+
+My two previous blog entries were about building and running collectd
+<http://collectd.org/> on Sun Solaris 10. After the first one Octo
+contacted me and was so kind as to release a packaged version for x86_64
+<http://collectd.org/download.shtml#solaris>. I have put aside the build
+I rolled on my own and decided to install and run the packaged one on
+the production servers. This blog entry is about SMF-izing the collectd
+daemon.
+
+A few words about the SMF – the Solaris'es Service Management Facility.
+I think it appeared in Solaris 10. From then on the good old |/etc/rcN.d
+|| /etc/init.d| services are called /legacy services/. They still can be
+run, but are not fully supported by SMF. SMF enables you to start and
+stop services in the unified way, can direct you to man pages in case a
+service enters maintenance mode, resolves dependencies between services,
+can store properties of services and so on. A nice feature is that SMF
+will take care of restarting services in case they terminate
+unexpectedly, we will use it at the end to check that things are working
+as they should.
+
+The 3V|L thing about SMF is that each service needs so called SMF
+manifest written in XML and a script or scripts that are executed, when
+the service needs to be stopped or started. It can be one script, which
+should accept respective parameters. Even more 3V|L is the fact that the
+manifest is imported into the SMF database and kept there in SQLite format.
+
+Below you will find collectd manifest and the script. I will post them
+to collectd mailing list in matter of minutes with this blog entry
+serving as a README. Please read all down to the bottom, including the
+remarks.
+
+Manifest (based on the work of Kangurek, thanks!), see the "collectd.xml"
+file:
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+<service_bundle type='manifest' name='collectd'>
+<service
+        name='application/collectd'
+        type='service'
+        version='1'>
+       <create_default_instance enabled='true' />
+       <single_instance/>
+        <dependency
+                name='network'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/milestone/network:default' />
+        </dependency> 
+        <dependency
+                name='filesystem-local'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/system/filesystem/local:default' />
+        </dependency> 
+        <exec_method
+                type='method'
+                name='start'
+                exec='/lib/svc/method/collectd start'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+        <exec_method
+                type='method'
+                name='stop'
+                exec='/lib/svc/method/collectd stop'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+        <stability value='Evolving' />
+</service>
+</service_bundle>
+
+Script, see the "collectd" file:
+
+#!/sbin/sh
+PIDFILE=/opt/collectd/var/run/collectd.pid
+DAEMON=/opt/collectd/sbin/collectd
+. /lib/svc/share/smf_include.sh
+case "$1" in
+  start)
+    if [ -f $PIDFILE ] ; then
+      echo "Already running. Stale PID file?"
+      PID=`cat $PIDFILE`
+      echo "$PIDFILE contains $PID"
+      ps -p $PID
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+    $DAEMON
+    if [ $? -ne 0 ] ; then
+      echo $DAEMON faild to start
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+  ;;
+  stop)
+    PID=`cat $PIDFILE 2>/dev/null`
+    kill -15 $PID 2>/dev/null
+    pwait $PID 1> /dev/null 2>/dev/null
+  ;;
+  restart)
+    $0 stop
+    $0 start
+  ;;
+  status)
+    ps -ef | grep collectd | grep -v status | grep -v grep
+  ;;
+  *)
+    echo "Usage: $0 [ start | stop | restart | status ]"
+    exit 1
+  ;;
+esac
+exit $SMF_EXIT_OK
+
+So you have two files: |collectd| script and |collectd.xml| manifest.
+What do you do with these files?
+
+First – before you begin – make sure that collectd is not running, close
+it down. My script above assumes that you are using the default place
+for PID file. Second: remove / move away collectd's |/etc/rcN.d| and
+|/etc/init.d| stuff, you won't need it from now on, because collectd
+will be SMF-ized. Tada!
+
+Next – install the script in place. It took me a minute or two to figure
+out why Solaris'es |install| tool does not work as expected. It turned
+out that the switches and parameters must be in exactly same order as in
+man page, especially the -c parameter must be first:
+
+|# install -c /lib/svc/method/ -m 755 -u root -g bin collectd|
+
+Now is the moment to test once again that the script is working OK. Try
+running:
+
+|# /lib/svc/method/collectd start
+# /lib/svc/method/collectd stop
+# /lib/svc/method/collectd restart
+|
+
+|pgrep| and |kill| are your friends here, also collectd logs. At last
+stop the collectd service and continue.
+
+Now is the time to /slurp/ attached XML manifest into the SMF database.
+This is done using the |svccfg| tool. Transcript follows:
+
+|# svccfg
+svc:> validate collectd.xml
+svc:> import collectd.xml
+svc:>
+|
+
+It is good to run |validate| command first, especially if you copied and
+pasted the XML manifest from this HTML document opened in your
+browser!!! Second thing worth noting is that |svccfg| starts the service
+immediately upon importing the manifest. It might be not what you want.
+For example it will start collecting data on remote collectd server if
+you use network plugin and it will do it under the hostname, that is not
+right. So be sure to configure collectd prior to running it from SMF.
+
+Now a few words about SMF tools. To see the state of all services issue
+|svcs -a| command. To see state of collectd service issue |svcs
+collectd| command. Quite normal states are enabled and disabled. If you
+see maintenance state then something is wrong. Be sure that you stopped
+all non-SMF collectd processes before you follow the procedure described
+here. To stop collectd the SMF way issue the |svcadm disable
+collectd|command. To start collectd the SMF way issue the |svcadm enable
+collectd|command. Be aware that setting it this way makes the change
+persistent across OS reboots, if you want to enable / disable the
+service only temporarily then add |-t| switch after the |enable| /
+|disable| keywords.
+
+And now is time for a grand finale – seeing if SMF can take care of
+collectd in case it crashes. See PID of collectd either using |pgrep| or
+seeing the contents of the PID file and kill it using |kill|. Then check
+with |svcs collectd| command that SMF has restarted collectd soon
+afterwards. You should see that the service is once again enabled in the
+first column, without your intervention.
+
+Things that could or should be clarified:
+
+    * How hard is it to correct mistakes in manifests? Does svccfg just
+      overwrite entries for specified service FMRI or does it require to
+      delete bad entry (and how to do it?!) and import the correct one?
+    * How does SMF know that a service has crashed / terminated? I
+      attended Sun's trainings and the trainer didn't know how to
+      explain this, we formed a hypothesis that it watches new PIDs
+      after starting a service or something like that. I think it is a
+      bad hypothesis, because SMF can watch PID for the startup script
+      itself but I think not the processes launched inside the script. I
+      have a slight idea that it is done based on so called contracts.
+
+------------------------------------------------------------------------
+
+
+        Komentarze do notki “SMF-izing collectd” <#comments>
+
+
+        Zostaw odpowiedź
+
+Nick
+
+------------------------------------------------------------------------
+
+
+    Archiwum
+
+    * Grudzień 2008 (5) </2008/12/>
+    * Październik 2008 (4) </2008/10/>
+    * Wrzesień 2008 (7) </2008/09/>
+    * Sierpień 2008 (5) </2008/08/>
+    * Lipiec 2008 (12) </2008/07/>
+    * Czerwiec 2008 (11) </2008/06/>
+    * Maj 2008 (3) </2008/05/>
+    * Kwiecień 2008 (10) </2008/04/>
+    * Marzec 2008 (3) </2008/03/>
+    * Luty 2008 (1) </2008/02/>
+    * Styczeń 2008 (1) </2008/01/>
+    * Listopad 2007 (4) </2007/11/>
+    * Październik 2007 (6) </2007/10/>
+    * Wrzesień 2007 (10) </2007/09/>
+    * Sierpień 2007 (3) </2007/08/>
+    * Lipiec 2007 (8) </2007/07/>
+    * Czerwiec 2007 (10) </2007/06/>
+    * Maj 2007 (12) </2007/05/>
+    * Kwiecień 2007 (17) </2007/04/>
+
+
+    Kategorie
+
+    * Film (8) <http://phosowicz.jogger.pl/kategoria/film/>
+    * Książki (35) <http://phosowicz.jogger.pl/kategoria/ksiazki/>
+    * Muzyka (15) <http://phosowicz.jogger.pl/kategoria/muzyka/>
+    * Ogólne (20) <http://phosowicz.jogger.pl/kategoria/ogolne/>
+    * Polityka (59) <http://phosowicz.jogger.pl/kategoria/polityka/>
+    * Sprzedam (5) <http://phosowicz.jogger.pl/kategoria/sprzedam/>
+    * Techblog (20) <http://phosowicz.jogger.pl/kategoria/techblog/>
+    * Technikalia (34) <http://phosowicz.jogger.pl/kategoria/technikalia/>
+    * Wystawy (3) <http://phosowicz.jogger.pl/kategoria/wystawy/>
+
+
+    Czytuję
+
+    * ArsTechnica.com <http://arstechnica.com/>
+    * Fund. Orientacja <http://www.abcnet.com.pl/>
+    * Techblog Jogger'a <http://techblog.pl/>
+    * The Register <http://www.theregister.co.uk/>
+
+
+    Inne blogi
+
+    * Bloody.Users <http://bloody.users.jogger.pl/>
+    * Dandys <http://dandys.jogger.pl/>
+    * Derin <http://derin.jogger.pl>
+    * Kefir87 <http://kefir87.jogger.pl/>
+    * Klisu <http://klisu.jogger.pl/>
+    * Kwantowe Krajobrazy <http://kwantowekrajobrazy.jogger.pl/>
+    * Paczor <http://paczor.fubar.pl/>
+    * Prestidigitator <http://prestidigitator.jogger.pl>
+    * Remiq <http://remiq.jogger.pl>
+    * Torero <http://torero.jogger.pl/>
+    * Zdzichubg <http://zdzichubg.jogger.pl/>
+
+
+    Inne moje strony
+
+    * www.delphi.org.pl <http://www.delphi.org.pl>
+    * www.hosowicz.com <http://www.hosowicz.com>
+
+
+    Pajacyk.pl
+
+    * Nakarm dzieci! <http://pajacyk.pl/>
+
+
+    Meta
+
+    * Panel administracyjny <https://login.jogger.pl/>
+
+
+------------------------------------------------------------------------
+
+phosowicz is powered by Jogger <http://jogger.pl> and K2
+<http://binarybonsai.com/k2/> by Michael <http://binarybonsai.com> and
+Chris <http://chrisjdavis.org>, ported by Patryk Zawadzki.
+<http://patrys.jogger.pl/>
+Notki w RSS <http://phosowicz.jogger.pl/rss/content/html/20>
+
diff --git a/contrib/solaris-smf/collectd b/contrib/solaris-smf/collectd
new file mode 100755 (executable)
index 0000000..5ffdb24
--- /dev/null
@@ -0,0 +1,42 @@
+#!/sbin/sh
+
+PIDFILE=/opt/collectd/var/run/collectd.pid
+DAEMON=/opt/collectd/sbin/collectd
+
+. /lib/svc/share/smf_include.sh
+
+case "$1" in
+  start)
+    if [ -f $PIDFILE ] ; then
+      echo "Already running. Stale PID file?"
+      PID=`cat $PIDFILE`
+      echo "$PIDFILE contains $PID"
+      ps -p $PID
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+    $DAEMON
+    if [ $? -ne 0 ] ; then
+      echo $DAEMON faild to start
+      exit $SMF_EXIT_ERR_FATAL
+    fi
+  ;;
+  stop)
+    PID=`cat $PIDFILE 2>/dev/null`
+    kill -15 $PID 2>/dev/null
+    pwait $PID 1> /dev/null 2>/dev/null
+  ;;
+  restart)
+    $0 stop
+    $0 start
+  ;;
+  status)
+    ps -ef | grep collectd | grep -v status | grep -v grep
+  ;;
+  *)
+    echo "Usage: $0 [ start | stop | restart | status ]"
+    exit 1
+  ;;
+esac
+
+
+exit $SMF_EXIT_OK
diff --git a/contrib/solaris-smf/collectd.xml b/contrib/solaris-smf/collectd.xml
new file mode 100644 (file)
index 0000000..d1ae3a4
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
+
+<service_bundle type='manifest' name='collectd'>
+<service
+        name='application/collectd'
+        type='service'
+        version='1'>
+
+       <create_default_instance enabled='true' />
+       
+       <single_instance/>
+
+        <dependency
+                name='network'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/milestone/network:default' />
+        </dependency> 
+
+        <dependency
+                name='filesystem-local'
+                grouping='require_all'
+                restart_on='none'
+                type='service'>
+                <service_fmri value='svc:/system/filesystem/local:default' />
+        </dependency> 
+
+        <exec_method
+                type='method'
+                name='start'
+                exec='/lib/svc/method/collectd start'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+
+
+        <exec_method
+                type='method'
+                name='stop'
+                exec='/lib/svc/method/collectd stop'
+                timeout_seconds='60'>
+           <method_context>
+               <method_credential user='root' group='root' />
+           </method_context>
+       </exec_method>
+
+        <stability value='Evolving' />
+
+</service>
+
+</service_bundle>
+
index 92627b9..3902795 100644 (file)
@@ -1,4 +1,4 @@
-SUBDIRS =
+SUBDIRS = libcollectdclient
 if BUILD_WITH_OWN_LIBIPTC
 SUBDIRS += libiptc
 endif
@@ -29,14 +29,18 @@ bin_PROGRAMS = collectd-nagios
 collectd_SOURCES = collectd.c collectd.h \
                   common.c common.h \
                   configfile.c configfile.h \
+                  filter_chain.c filter_chain.h \
+                  meta_data.c meta_data.h \
                   plugin.c plugin.h \
                   utils_avltree.c utils_avltree.h \
                   utils_cache.c utils_cache.h \
                   utils_complain.c utils_complain.h \
                   utils_ignorelist.c utils_ignorelist.h \
                   utils_llist.c utils_llist.h \
+                  utils_parse_option.c utils_parse_option.h \
                   utils_tail_match.c utils_tail_match.h \
                   utils_match.c utils_match.h \
+                  utils_subst.c utils_subst.h \
                   utils_tail.c utils_tail.h \
                   utils_threshold.c utils_threshold.h \
                   types_list.c types_list.h
@@ -89,6 +93,8 @@ collectd_nagios_LDFLAGS =
 if BUILD_WITH_LIBSOCKET
 collectd_nagios_LDFLAGS += -lsocket
 endif
+collectd_nagios_LDADD = libcollectdclient/libcollectdclient.la
+collectd_nagios_DEPENDENCIES = libcollectdclient/libcollectdclient.la
 
 pkglib_LTLIBRARIES = 
 
@@ -147,6 +153,17 @@ collectd_LDADD += "-dlopen" battery.la
 collectd_DEPENDENCIES += battery.la
 endif
 
+if BUILD_PLUGIN_BIND
+pkglib_LTLIBRARIES += bind.la
+bind_la_SOURCES = bind.c
+bind_la_LDFLAGS = -module -avoid-version
+bind_la_CFLAGS = $(AM_CFLAGS) \
+               $(BUILD_WITH_LIBCURL_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+bind_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+collectd_LDADD += "-dlopen" bind.la
+collectd_DEPENDENCIES += bind.la
+endif
+
 if BUILD_PLUGIN_CPU
 pkglib_LTLIBRARIES += cpu.la
 cpu_la_SOURCES = cpu.c
@@ -183,6 +200,31 @@ collectd_LDADD += "-dlopen" csv.la
 collectd_DEPENDENCIES += csv.la
 endif
 
+if BUILD_PLUGIN_CURL
+pkglib_LTLIBRARIES += curl.la
+curl_la_SOURCES = curl.c
+curl_la_LDFLAGS = -module -avoid-version
+curl_la_CFLAGS = $(AM_CFLAGS)
+curl_la_LIBADD =
+collectd_LDADD += "-dlopen" curl.la
+if BUILD_WITH_LIBCURL
+curl_la_CFLAGS += $(BUILD_WITH_LIBCURL_CFLAGS)
+curl_la_LIBADD += $(BUILD_WITH_LIBCURL_LIBS)
+endif
+collectd_DEPENDENCIES += curl.la
+endif
+
+if BUILD_PLUGIN_DBI
+pkglib_LTLIBRARIES += dbi.la
+dbi_la_SOURCES = dbi.c \
+                utils_db_query.c utils_db_query.h
+dbi_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBDBI_CPPFLAGS)
+dbi_la_LDFLAGS = -module -avoid-version $(BUILD_WITH_LIBDBI_LDFLAGS)
+dbi_la_LIBADD = $(BUILD_WITH_LIBDBI_LIBS)
+collectd_LDADD += "-dlopen" dbi.la
+collectd_DEPENDENCIES += dbi.la
+endif
+
 if BUILD_PLUGIN_DF
 pkglib_LTLIBRARIES += df.la
 df_la_SOURCES = df.c utils_mount.c utils_mount.h
@@ -244,7 +286,6 @@ endif
 if BUILD_PLUGIN_EXEC
 pkglib_LTLIBRARIES += exec.la
 exec_la_SOURCES = exec.c \
-                 utils_parse_option.h utils_parse_option.c \
                  utils_cmd_putnotif.c utils_cmd_putnotif.h \
                  utils_cmd_putval.c utils_cmd_putval.h
 exec_la_LDFLAGS = -module -avoid-version
@@ -369,6 +410,30 @@ collectd_LDADD += "-dlopen" logfile.la
 collectd_DEPENDENCIES += logfile.la
 endif
 
+if BUILD_PLUGIN_MATCH_REGEX
+pkglib_LTLIBRARIES += match_regex.la
+match_regex_la_SOURCES = match_regex.c
+match_regex_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_regex.la
+collectd_DEPENDENCIES += match_regex.la
+endif
+
+if BUILD_PLUGIN_MATCH_TIMEDIFF
+pkglib_LTLIBRARIES += match_timediff.la
+match_timediff_la_SOURCES = match_timediff.c
+match_timediff_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_timediff.la
+collectd_DEPENDENCIES += match_timediff.la
+endif
+
+if BUILD_PLUGIN_MATCH_VALUE
+pkglib_LTLIBRARIES += match_value.la
+match_value_la_SOURCES = match_value.c
+match_value_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" match_value.la
+collectd_DEPENDENCIES += match_value.la
+endif
+
 if BUILD_PLUGIN_MBMON
 pkglib_LTLIBRARIES += mbmon.la
 mbmon_la_SOURCES = mbmon.c
@@ -528,6 +593,27 @@ collectd_LDADD += "-dlopen" onewire.la
 collectd_DEPENDENCIES += onewire.la
 endif
 
+if BUILD_PLUGIN_OPENVPN
+pkglib_LTLIBRARIES += openvpn.la
+openvpn_la_SOURCES = openvpn.c
+openvpn_la_CFLAGS = $(AM_CFLAGS)
+openvpn_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" openvpn.la
+collectd_DEPENDENCIES += openvpn.la
+endif
+
+if BUILD_PLUGIN_ORACLE
+pkglib_LTLIBRARIES += oracle.la
+oracle_la_SOURCES = oracle.c \
+       utils_db_query.c utils_db_query.h
+oracle_la_CFLAGS = $(AM_CFLAGS)
+oracle_la_CPPFLAGS = $(BUILD_WITH_ORACLE_CFLAGS)
+oracle_la_LIBADD = $(BUILD_WITH_ORACLE_LIBS)
+oracle_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" oracle.la
+collectd_DEPENDENCIES += oracle.la
+endif
+
 if BUILD_PLUGIN_PERL
 pkglib_LTLIBRARIES += perl.la
 perl_la_SOURCES = perl.c
@@ -537,6 +623,11 @@ perl_la_CPPFLAGS = $(AM_CPPFLAGS) -DHAS_BOOL=1
 perl_la_CFLAGS  = $(AM_CFLAGS) \
                $(PERL_CFLAGS) \
                -DXS_VERSION=\"$(VERSION)\" -DVERSION=\"$(VERSION)\"
+# Work-around for issues #41 and #42 - Perl 5.10 incorrectly introduced
+# __attribute__nonnull__(3) for Perl_load_module().
+if HAVE_BROKEN_PERL_LOAD_MODULE
+perl_la_CFLAGS += -Wno-nonnull
+endif
 perl_la_LDFLAGS = -module -avoid-version \
                $(PERL_LDFLAGS)
 collectd_LDADD += "-dlopen" perl.la
@@ -559,7 +650,8 @@ endif
 
 if BUILD_PLUGIN_POSTGRESQL
 pkglib_LTLIBRARIES += postgresql.la
-postgresql_la_SOURCES = postgresql.c
+postgresql_la_SOURCES = postgresql.c \
+                utils_db_query.c utils_db_query.h
 postgresql_la_CPPFLAGS = $(AM_CPPFLAGS) $(BUILD_WITH_LIBPQ_CPPFLAGS)
 postgresql_la_LDFLAGS = -module -avoid-version \
                $(BUILD_WITH_LIBPQ_LDFLAGS) -lpq
@@ -587,9 +679,19 @@ processes_la_LIBADD += -lkvm
 endif
 endif
 
+if BUILD_PLUGIN_RRDCACHED
+pkglib_LTLIBRARIES += rrdcached.la
+rrdcached_la_SOURCES = rrdcached.c utils_rrdcreate.c utils_rrdcreate.h
+rrdcached_la_LDFLAGS = -module -avoid-version
+rrdcached_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBRRD_CFLAGS)
+rrdcached_la_LIBADD = $(BUILD_WITH_LIBRRD_LDFLAGS)
+collectd_LDADD += "-dlopen" rrdcached.la
+collectd_DEPENDENCIES += rrdcached.la
+endif
+
 if BUILD_PLUGIN_RRDTOOL
 pkglib_LTLIBRARIES += rrdtool.la
-rrdtool_la_SOURCES = rrdtool.c
+rrdtool_la_SOURCES = rrdtool.c utils_rrdcreate.c utils_rrdcreate.h
 rrdtool_la_LDFLAGS = -module -avoid-version
 rrdtool_la_CFLAGS = $(AM_CFLAGS) $(BUILD_WITH_LIBRRD_CFLAGS)
 rrdtool_la_LIBADD = $(BUILD_WITH_LIBRRD_LDFLAGS)
@@ -679,6 +781,30 @@ collectd_LDADD += "-dlopen" tape.la
 collectd_DEPENDENCIES += tape.la
 endif
 
+if BUILD_PLUGIN_TARGET_NOTIFICATION
+pkglib_LTLIBRARIES += target_notification.la
+target_notification_la_SOURCES = target_notification.c
+target_notification_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_notification.la
+collectd_DEPENDENCIES += target_notification.la
+endif
+
+if BUILD_PLUGIN_TARGET_REPLACE
+pkglib_LTLIBRARIES += target_replace.la
+target_replace_la_SOURCES = target_replace.c
+target_replace_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_replace.la
+collectd_DEPENDENCIES += target_replace.la
+endif
+
+if BUILD_PLUGIN_TARGET_SET
+pkglib_LTLIBRARIES += target_set.la
+target_set_la_SOURCES = target_set.c
+target_set_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" target_set.la
+collectd_DEPENDENCIES += target_set.la
+endif
+
 if BUILD_PLUGIN_TCPCONNS
 pkglib_LTLIBRARIES += tcpconns.la
 tcpconns_la_SOURCES = tcpconns.c
@@ -710,7 +836,6 @@ endif
 if BUILD_PLUGIN_UNIXSOCK
 pkglib_LTLIBRARIES += unixsock.la
 unixsock_la_SOURCES = unixsock.c \
-                     utils_parse_option.h utils_parse_option.c \
                      utils_cmd_flush.h utils_cmd_flush.c \
                      utils_cmd_getval.h utils_cmd_getval.c \
                      utils_cmd_listval.h utils_cmd_listval.c \
@@ -817,7 +942,7 @@ install-exec-hook:
        else \
                $(INSTALL) -m 0640 collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf; \
        fi; \
-       cp -f $(srcdir)/types.db $(DESTDIR)$(pkglibdir)/;
        $(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
+       $(INSTALL) -m 0644 $(srcdir)/types.db $(DESTDIR)$(pkgdatadir)/types.db;
        $(INSTALL) -m 0644 $(srcdir)/postgresql_default.conf \
                $(DESTDIR)$(pkgdatadir)/postgresql_default.conf;
index c6bf8ad..4fa7aa1 100644 (file)
@@ -55,7 +55,7 @@ static const char *config_keys[] =
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 static size_t apache_curl_callback (void *buf, size_t size, size_t nmemb,
-               void *stream)
+               void __attribute__((unused)) *stream)
 {
        size_t len = size * nmemb;
 
@@ -148,7 +148,7 @@ static int init (void)
 
                status = ssnprintf (credentials, sizeof (credentials), "%s:%s",
                                user, (pass == NULL) ? "" : pass);
-               if (status >= sizeof (credentials))
+               if ((status < 0) || ((size_t) status >= sizeof (credentials)))
                {
                        ERROR ("apache plugin: init: Returning an error "
                                        "because the credentials have been "
@@ -197,7 +197,6 @@ static void submit_counter (const char *type, const char *type_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "apache", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
@@ -220,7 +219,6 @@ static void submit_gauge (const char *type, const char *type_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "apache", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
index d20bad3..a0629d5 100644 (file)
@@ -364,7 +364,6 @@ static void apc_submit_generic (char *type, char *type_inst, double value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "apcups", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
index d312027..bdba0ff 100644 (file)
@@ -87,7 +87,6 @@ static void as_submit (const char *type, const char *type_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "apple_sensors", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
index 6285176..8829e51 100644 (file)
@@ -126,7 +126,6 @@ static int ascent_submit_gauge (const char *plugin_instance, /* {{{ */
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "ascent", sizeof (vl.plugin));
 
@@ -144,7 +143,7 @@ static int ascent_submit_gauge (const char *plugin_instance, /* {{{ */
 } /* }}} int ascent_submit_gauge */
 
 static size_t ascent_curl_callback (void *buf, size_t size, size_t nmemb, /* {{{ */
-    void *stream)
+    void __attribute__((unused)) *stream)
 {
   size_t len = size * nmemb;
 
@@ -175,7 +174,7 @@ static size_t ascent_curl_callback (void *buf, size_t size, size_t nmemb, /* {{{
 
 static int ascent_submit_players (player_stats_t *ps) /* {{{ */
 {
-  int i;
+  size_t i;
   gauge_t value;
 
   for (i = 0; i < RACES_LIST_LENGTH; i++)
@@ -214,7 +213,7 @@ static int ascent_account_player (player_stats_t *ps, /* {{{ */
 {
   if (pi->race >= 0)
   {
-    if ((pi->race >= RACES_LIST_LENGTH)
+    if (((size_t) pi->race >= RACES_LIST_LENGTH)
         || (races_list[pi->race] == NULL))
       ERROR ("ascent plugin: Ignoring invalid numeric race %i.", pi->race);
     else
@@ -223,7 +222,7 @@ static int ascent_account_player (player_stats_t *ps, /* {{{ */
 
   if (pi->class >= 0)
   {
-    if ((pi->class >= CLASSES_LIST_LENGTH)
+    if (((size_t) pi->class >= CLASSES_LIST_LENGTH)
         || (classes_list[pi->class] == NULL))
       ERROR ("ascent plugin: Ignoring invalid numeric class %i.", pi->class);
     else
@@ -232,7 +231,7 @@ static int ascent_account_player (player_stats_t *ps, /* {{{ */
 
   if (pi->gender >= 0)
   {
-    if ((pi->gender >= GENDERS_LIST_LENGTH)
+    if (((size_t) pi->gender >= GENDERS_LIST_LENGTH)
         || (genders_list[pi->gender] == NULL))
       ERROR ("ascent plugin: Ignoring invalid numeric gender %i.",
           pi->gender);
@@ -550,7 +549,7 @@ static int ascent_init (void) /* {{{ */
 
     status = ssnprintf (credentials, sizeof (credentials), "%s:%s",
         user, (pass == NULL) ? "" : pass);
-    if (status >= sizeof (credentials))
+    if ((status < 0) || ((size_t) status >= sizeof (credentials)))
     {
       ERROR ("ascent plugin: ascent_init: Returning an error because the "
           "credentials have been truncated.");
index 70d0486..b62ad81 100644 (file)
@@ -102,7 +102,6 @@ static void battery_submit (const char *plugin_instance, const char *type, doubl
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "battery", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
@@ -319,8 +318,8 @@ static void get_via_generic_iokit (double *ret_charge,
 #endif /* HAVE_IOKIT_IOKITLIB_H */
 
 #if KERNEL_LINUX
-static int battery_read_acpi (const char *dir, const char *name,
-               void *user_data)
+static int battery_read_acpi (const char __attribute__((unused)) *dir,
+               const char *name, void __attribute__((unused)) *user_data)
 {
        double  current = INVALID_VALUE;
        double  voltage = INVALID_VALUE;
@@ -441,7 +440,7 @@ static int battery_read (void)
 /* #endif HAVE_IOKIT_IOKITLIB_H || HAVE_IOKIT_PS_IOPOWERSOURCES_H */
 
 #elif KERNEL_LINUX
-       static c_complain_t acpi_dir_complaint = C_COMPLAIN_INIT;
+       static c_complain_t acpi_dir_complaint = C_COMPLAIN_INIT_STATIC;
 
        FILE *fh;
        char buffer[1024];
diff --git a/src/bind.c b/src/bind.c
new file mode 100644 (file)
index 0000000..44efcfd
--- /dev/null
@@ -0,0 +1,1446 @@
+/**
+ * collectd - src/bind.c
+ * Copyright (C) 2009  Bruno Prémont
+ * Copyright (C) 2009  Florian 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:
+ *   Bruno Prémont <bonbons at linux-vserver.org>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+#define _XOPEN_SOURCE 600 /* glibc2 needs this for strptime */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+/* Some versions of libcurl don't include this themselves and then don't have
+ * fd_set available. */
+#if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include <curl/curl.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#ifndef BIND_DEFAULT_URL
+# define BIND_DEFAULT_URL "http://localhost:8053/"
+#endif
+
+/* 
+ * Some types used for the callback functions. `translation_table_ptr_t' and
+ * `list_info_ptr_t' are passed to the callbacks in the `void *user_data'
+ * pointer.
+ */
+typedef int (*list_callback_t) (const char *name, value_t value,
+    time_t current_time, void *user_data);
+
+struct cb_view_s
+{
+  char *name;
+
+  int qtypes;
+  int resolver_stats;
+  int cacherrsets;
+
+  char **zones;
+  size_t zones_num;
+};
+typedef struct cb_view_s cb_view_t;
+
+struct translation_info_s
+{
+  const char *xml_name;
+  const char *type;
+  const char *type_instance;
+};
+typedef struct translation_info_s translation_info_t;
+
+struct translation_table_ptr_s
+{
+  const translation_info_t *table;
+  size_t table_length;
+  const char *plugin_instance;
+};
+typedef struct translation_table_ptr_s translation_table_ptr_t;
+
+struct list_info_ptr_s
+{
+  const char *plugin_instance;
+  const char *type;
+};
+typedef struct list_info_ptr_s list_info_ptr_t;
+
+static char *url                   = NULL;
+static int global_opcodes          = 1;
+static int global_qtypes           = 1;
+static int global_server_stats     = 1;
+static int global_zone_maint_stats = 1;
+static int global_resolver_stats   = 0;
+static int global_memory_stats     = 1;
+
+static cb_view_t *views = NULL;
+static size_t     views_num = 0;
+
+static CURL *curl = NULL;
+
+static char  *bind_buffer = NULL;
+static size_t bind_buffer_size = 0;
+static size_t bind_buffer_fill = 0;
+static char   bind_curl_error[CURL_ERROR_SIZE];
+
+/* Translation table for the `nsstats' values. */
+static const translation_info_t nsstats_translation_table[] = /* {{{ */
+{
+  /* Requests */
+  { "Requestv4",       "dns_request",  "IPv4"        },
+  { "Requestv6",       "dns_request",  "IPv6"        },
+  { "ReqEdns0",        "dns_request",  "EDNS0"       },
+  { "ReqBadEDNSVer",   "dns_request",  "BadEDNSVer"  },
+  { "ReqTSIG",         "dns_request",  "TSIG"        },
+  { "ReqSIG0",         "dns_request",  "SIG0"        },
+  { "ReqBadSIG",       "dns_request",  "BadSIG"      },
+  { "ReqTCP",          "dns_request",  "TCP"         },
+  /* Rejects */
+  { "AuthQryRej",      "dns_reject",   "authorative" },
+  { "RecQryRej",       "dns_reject",   "recursive"   },
+  { "XfrRej",          "dns_reject",   "transfer"    },
+  { "UpdateRej",       "dns_reject",   "update"      },
+  /* Responses */
+  { "Response",        "dns_response", "normal"      },
+  { "TruncatedResp",   "dns_response", "truncated"   },
+  { "RespEDNS0",       "dns_response", "EDNS0"       },
+  { "RespTSIG",        "dns_response", "TSIG"        },
+  { "RespSIG0",        "dns_response", "SIG0"        },
+  /* Queries */
+  { "QryAuthAns",      "dns_query",    "authorative" },
+  { "QryNoauthAns",    "dns_query",    "nonauth"     },
+  { "QryReferral",     "dns_query",    "referral"    },
+  { "QryRecursion",    "dns_query",    "recursion"   },
+  { "QryDuplicate",    "dns_query",    "dupliate"    },
+  { "QryDropped",      "dns_query",    "dropped"     },
+  { "QryFailure",      "dns_query",    "failure"     },
+  /* Response codes */
+  { "QrySuccess",      "dns_rcode",    "tx-NOERROR"  },
+  { "QryNxrrset",      "dns_rcode",    "tx-NXRRSET"  },
+  { "QrySERVFAIL",     "dns_rcode",    "tx-SERVFAIL" },
+  { "QryFORMERR",      "dns_rcode",    "tx-FORMERR"  },
+  { "QryNXDOMAIN",     "dns_rcode",    "tx-NXDOMAIN" }
+#if 0
+  { "XfrReqDone",      "type", "type_instance"       },
+  { "UpdateReqFwd",    "type", "type_instance"       },
+  { "UpdateRespFwd",   "type", "type_instance"       },
+  { "UpdateFwdFail",   "type", "type_instance"       },
+  { "UpdateDone",      "type", "type_instance"       },
+  { "UpdateFail",      "type", "type_instance"       },
+  { "UpdateBadPrereq", "type", "type_instance"       },
+#endif
+};
+static int nsstats_translation_table_length =
+  STATIC_ARRAY_SIZE (nsstats_translation_table);
+/* }}} */
+
+/* Translation table for the `zonestats' values. */
+static const translation_info_t zonestats_translation_table[] = /* {{{ */
+{
+  /* Notify's */
+  { "NotifyOutv4",     "dns_notify",   "tx-IPv4"     },
+  { "NotifyOutv6",     "dns_notify",   "tx-IPv6"     },
+  { "NotifyInv4",      "dns_notify",   "rx-IPv4"     },
+  { "NotifyInv6",      "dns_notify",   "rx-IPv6"     },
+  { "NotifyRej",       "dns_notify",   "rejected"    },
+  /* SOA/AXFS/IXFS requests */
+  { "SOAOutv4",        "dns_opcode",   "SOA-IPv4"    },
+  { "SOAOutv6",        "dns_opcode",   "SOA-IPv6"    },
+  { "AXFRReqv4",       "dns_opcode",   "AXFR-IPv4"   },
+  { "AXFRReqv6",       "dns_opcode",   "AXFR-IPv6"   },
+  { "IXFRReqv4",       "dns_opcode",   "IXFR-IPv4"   },
+  { "IXFRReqv6",       "dns_opcode",   "IXFR-IPv6"   },
+  /* Domain transfers */
+  { "XfrSuccess",      "dns_transfer", "success"     },
+  { "XfrFail",         "dns_transfer", "failure"     }
+};
+static int zonestats_translation_table_length =
+  STATIC_ARRAY_SIZE (zonestats_translation_table);
+/* }}} */
+
+/* Translation table for the `resstats' values. */
+static const translation_info_t resstats_translation_table[] = /* {{{ */
+{
+  /* Generic resolver information */
+  { "Queryv4",         "dns_query",    "IPv4"        },
+  { "Queryv6",         "dns_query",    "IPv6"        },
+  { "Responsev4",      "dns_response", "IPv4"        },
+  { "Responsev6",      "dns_response", "IPv6"        },
+  /* Received response codes */
+  { "NXDOMAIN",        "dns_rcode",    "rx-NXDOMAIN" },
+  { "SERVFAIL",        "dns_rcode",    "rx-SERVFAIL" },
+  { "FORMERR",         "dns_rcode",    "rx-FORMERR"  },
+  { "OtherError",      "dns_rcode",    "rx-OTHER"    },
+  { "EDNS0Fail",       "dns_rcode",    "rx-EDNS0Fail"},
+  /* Received responses */
+  { "Mismatch",        "dns_response", "mismatch"    },
+  { "Truncated",       "dns_response", "truncated"   },
+  { "Lame",            "dns_response", "lame"        },
+  { "Retry",           "dns_query",    "retry"       },
+#if 0
+  { "GlueFetchv4",     "type", "type_instance" },
+  { "GlueFetchv6",     "type", "type_instance" },
+  { "GlueFetchv4Fail", "type", "type_instance" },
+  { "GlueFetchv6Fail", "type", "type_instance" },
+#endif
+  /* DNSSEC information */
+  { "ValAttempt",      "dns_resolver", "DNSSEC-attempt" },
+  { "ValOk",           "dns_resolver", "DNSSEC-okay"    },
+  { "ValNegOk",        "dns_resolver", "DNSSEC-negokay" },
+  { "ValFail",         "dns_resolver", "DNSSEC-fail"    }
+};
+static int resstats_translation_table_length =
+  STATIC_ARRAY_SIZE (resstats_translation_table);
+/* }}} */
+
+/* Translation table for the `memory/summary' values. */
+static const translation_info_t memsummary_translation_table[] = /* {{{ */
+{
+  { "TotalUse",        "memory",       "TotalUse"    },
+  { "InUse",           "memory",       "InUse"       },
+  { "BlockSize",       "memory",       "BlockSize"   },
+  { "ContextSize",     "memory",       "ContextSize" },
+  { "Lost",            "memory",       "Lost"        }
+};
+static int memsummary_translation_table_length =
+  STATIC_ARRAY_SIZE (memsummary_translation_table);
+/* }}} */
+
+static void remove_special (char *buffer, size_t buffer_size) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < buffer_size; i++)
+  {
+    if (buffer[i] == 0)
+      return;
+    if ((!isalnum ((int) buffer[i])) && (buffer[i] != '-'))
+      buffer[i] = '_';
+  }
+} /* }}} void remove_special */
+
+static void submit (time_t ts, const char *plugin_instance, /* {{{ */
+    const char *type, const char *type_instance, value_t value)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0] = value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  vl.time = ts;
+  sstrncpy(vl.host, hostname_g, sizeof(vl.host));
+  sstrncpy(vl.plugin, "bind", sizeof(vl.plugin));
+  if (plugin_instance) {
+    sstrncpy(vl.plugin_instance, plugin_instance,
+        sizeof(vl.plugin_instance));
+    remove_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+  }
+  sstrncpy(vl.type, type, sizeof(vl.type));
+  if (type_instance) {
+    sstrncpy(vl.type_instance, type_instance,
+        sizeof(vl.type_instance));
+    remove_special (vl.plugin_instance, sizeof (vl.plugin_instance));
+  }
+  plugin_dispatch_values(&vl);
+} /* }}} void submit */
+
+static size_t bind_curl_callback (void *buf, size_t size, /* {{{ */
+    size_t nmemb, void __attribute__((unused)) *stream)
+{
+  size_t len = size * nmemb;
+
+  if (len <= 0)
+    return (len);
+
+  if ((bind_buffer_fill + len) >= bind_buffer_size)
+  {
+    char *temp;
+
+    temp = realloc(bind_buffer, bind_buffer_fill + len + 1);
+    if (temp == NULL)
+    {
+      ERROR ("bind plugin: realloc failed.");
+      return (0);
+    }
+    bind_buffer = temp;
+    bind_buffer_size = bind_buffer_fill + len + 1;
+  }
+
+  memcpy (bind_buffer + bind_buffer_fill, (char *) buf, len);
+  bind_buffer_fill += len;
+  bind_buffer[bind_buffer_fill] = 0;
+
+  return (len);
+} /* }}} size_t bind_curl_callback */
+
+/*
+ * Callback, that's called with a translation table.
+ * (Plugin instance is fixed, type and type instance come from lookup table.)
+ */
+static int bind_xml_table_callback (const char *name, value_t value, /* {{{ */
+    time_t current_time, void *user_data)
+{
+  translation_table_ptr_t *table = (translation_table_ptr_t *) user_data;
+  size_t i;
+
+  if (table == NULL)
+    return (-1);
+
+  for (i = 0; i < table->table_length; i++)
+  {
+    if (strcmp (table->table[i].xml_name, name) != 0)
+      continue;
+
+    submit (current_time,
+        table->plugin_instance,
+        table->table[i].type,
+        table->table[i].type_instance,
+        value);
+    break;
+  }
+
+  return (0);
+} /* }}} int bind_xml_table_callback */
+
+/*
+ * Callback, that's used for lists.
+ * (Plugin instance and type are fixed, xml name is used as type instance.)
+ */
+static int bind_xml_list_callback (const char *name, /* {{{ */
+    value_t value, time_t current_time, void *user_data)
+{
+  list_info_ptr_t *list_info = (list_info_ptr_t *) user_data;
+
+  if (list_info == NULL)
+    return (-1);
+
+  submit (current_time,
+      list_info->plugin_instance,
+      list_info->type,
+      /* type instance = */ name,
+      value);
+
+  return (0);
+} /* }}} int bind_xml_list_callback */
+
+static int bind_xml_read_counter (xmlDoc *doc, xmlNode *node, /* {{{ */
+    counter_t *ret_value)
+{
+  char *str_ptr, *end_ptr;
+  long long int value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_counter: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  errno = 0;
+  value = strtoll (str_ptr, &end_ptr, 10);
+  xmlFree(str_ptr);
+  if (str_ptr == end_ptr || errno)
+  {
+    if (errno && (value < 0))
+      ERROR ("bind plugin: bind_xml_read_counter: strtoll failed with underflow.");
+    else if (errno && (value > 0))
+      ERROR ("bind plugin: bind_xml_read_counter: strtoll failed with overflow.");
+    else
+      ERROR ("bind plugin: bind_xml_read_counter: strtoll failed.");
+    return (-1);
+  }
+
+  *ret_value = value;
+  return (0);
+} /* }}} int bind_xml_read_counter */
+
+static int bind_xml_read_gauge (xmlDoc *doc, xmlNode *node, /* {{{ */
+    gauge_t *ret_value)
+{
+  char *str_ptr, *end_ptr;
+  double value;
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_gauge: xmlNodeListGetString failed.");
+    return (-1);
+  }
+
+  errno = 0;
+  value = strtod (str_ptr, &end_ptr);
+  xmlFree(str_ptr);
+  if (str_ptr == end_ptr || errno)
+  {
+    if (errno && (value < 0))
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed with underflow.");
+    else if (errno && (value > 0))
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed with overflow.");
+    else
+      ERROR ("bind plugin: bind_xml_read_gauge: strtod failed.");
+    return (-1);
+  }
+
+  *ret_value = (gauge_t) value;
+  return (0);
+} /* }}} int bind_xml_read_gauge */
+
+static int bind_xml_read_timestamp (const char *xpath_expression, /* {{{ */
+    xmlDoc *doc, xmlXPathContext *xpathCtx, time_t *ret_value)
+{
+  xmlXPathObject *xpathObj = NULL;
+  xmlNode *node;
+  char *str_ptr;
+  char *tmp;
+  struct tm tm;
+
+  xpathObj = xmlXPathEvalExpression (BAD_CAST xpath_expression, xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR ("bind plugin: Unable to evaluate XPath expression `%s'.",
+        xpath_expression);
+    return (-1);
+  }
+
+  if ((xpathObj->nodesetval == NULL) || (xpathObj->nodesetval->nodeNr < 1))
+  {
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  if (xpathObj->nodesetval->nodeNr != 1)
+  {
+    NOTICE ("bind plugin: Evaluating the XPath expression `%s' returned "
+        "%i nodes. Only handling the first one.",
+        xpath_expression, xpathObj->nodesetval->nodeNr);
+  }
+
+  node = xpathObj->nodesetval->nodeTab[0];
+
+  if (node->xmlChildrenNode == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_timestamp: "
+        "node->xmlChildrenNode == NULL");
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+  if (str_ptr == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_timestamp: xmlNodeListGetString failed.");
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  memset (&tm, 0, sizeof(tm));
+  tmp = strptime (str_ptr, "%Y-%m-%dT%T", &tm);
+  xmlFree(str_ptr);
+  if (tmp == NULL)
+  {
+    ERROR ("bind plugin: bind_xml_read_timestamp: strptime failed.");
+    xmlXPathFreeObject (xpathObj);
+    return (-1);
+  }
+
+  *ret_value = mktime(&tm);
+
+  xmlXPathFreeObject (xpathObj);
+  return (0);
+} /* }}} int bind_xml_read_timestamp */
+
+/* 
+ * bind_parse_generic_name_value
+ *
+ * Reads statistics in the form:
+ * <foo>
+ *   <name>QUERY</name>
+ *   <counter>123</counter>
+ * </foo>
+ */
+static int bind_parse_generic_name_value (const char *xpath_expression, /* {{{ */
+    list_callback_t list_callback,
+    void *user_data,
+    xmlDoc *doc, xmlXPathContext *xpathCtx,
+    time_t current_time, int ds_type)
+{
+  xmlXPathObject *xpathObj = NULL;
+  int num_entries;
+  int i;
+
+  xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
+        xpath_expression);
+    return (-1);
+  }
+
+  num_entries = 0;
+  /* Iterate over all matching nodes. */
+  for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
+  {
+    xmlNode *name_node = NULL;
+    xmlNode *counter = NULL;
+    xmlNode *parent;
+    xmlNode *child;
+
+    parent = xpathObj->nodesetval->nodeTab[i];
+    DEBUG ("bind plugin: bind_parse_generic_name_value: parent->name = %s;",
+        (char *) parent->name);
+
+    /* Iterate over all child nodes. */
+    for (child = parent->xmlChildrenNode;
+        child != NULL;
+        child = child->next)
+    {
+      if (child->type != XML_ELEMENT_NODE)
+        continue;
+
+      if (xmlStrcmp (BAD_CAST "name", child->name) == 0)
+        name_node = child;
+      else if (xmlStrcmp (BAD_CAST "counter", child->name) == 0)
+        counter = child;
+    }
+
+    if ((name_node != NULL) && (counter != NULL))
+    {
+      char *name = (char *) xmlNodeListGetString (doc,
+          name_node->xmlChildrenNode, 1);
+      value_t value;
+      int status;
+
+      if (ds_type == DS_TYPE_GAUGE)
+        status = bind_xml_read_gauge (doc, counter, &value.gauge);
+      else
+        status = bind_xml_read_counter (doc, counter, &value.counter);
+      if (status != 0)
+        continue;
+
+      status = (*list_callback) (name, value, current_time, user_data);
+      if (status == 0)
+        num_entries++;
+
+      xmlFree (name);
+    }
+  }
+
+  DEBUG ("bind plugin: Found %d %s for XPath expression `%s'",
+      num_entries, (num_entries == 1) ? "entry" : "entries",
+      xpath_expression);
+
+  xmlXPathFreeObject(xpathObj);
+
+  return (0);
+} /* }}} int bind_parse_generic_name_value */
+
+/* 
+ * bind_parse_generic_value_list
+ *
+ * Reads statistics in the form:
+ * <foo>
+ *   <name0>123</name0>
+ *   <name1>234</name1>
+ *   <name2>345</name2>
+ *   :
+ * </foo>
+ */
+static int bind_parse_generic_value_list (const char *xpath_expression, /* {{{ */
+    list_callback_t list_callback,
+    void *user_data,
+    xmlDoc *doc, xmlXPathContext *xpathCtx,
+    time_t current_time, int ds_type)
+{
+  xmlXPathObject *xpathObj = NULL;
+  int num_entries;
+  int i;
+
+  xpathObj = xmlXPathEvalExpression(BAD_CAST xpath_expression, xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR("bind plugin: Unable to evaluate XPath expression `%s'.",
+        xpath_expression);
+    return (-1);
+  }
+
+  num_entries = 0;
+  /* Iterate over all matching nodes. */
+  for (i = 0; xpathObj->nodesetval && (i < xpathObj->nodesetval->nodeNr); i++)
+  {
+    xmlNode *child;
+
+    /* Iterate over all child nodes. */
+    for (child = xpathObj->nodesetval->nodeTab[i]->xmlChildrenNode;
+        child != NULL;
+        child = child->next)
+    {
+      char *node_name;
+      value_t value;
+      int status;
+
+      if (child->type != XML_ELEMENT_NODE)
+        continue;
+
+      node_name = (char *) child->name;
+
+      if (ds_type == DS_TYPE_GAUGE)
+        status = bind_xml_read_gauge (doc, child, &value.gauge);
+      else
+        status = bind_xml_read_counter (doc, child, &value.counter);
+      if (status != 0)
+        continue;
+
+      status = (*list_callback) (node_name, value, current_time, user_data);
+      if (status == 0)
+        num_entries++;
+    }
+  }
+
+  DEBUG ("bind plugin: Found %d %s for XPath expression `%s'",
+      num_entries, (num_entries == 1) ? "entry" : "entries",
+      xpath_expression);
+
+  xmlXPathFreeObject(xpathObj);
+
+  return (0);
+} /* }}} int bind_parse_generic_value_list */
+
+static int bind_xml_stats_handle_zone (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *path_ctx, xmlNode *node, cb_view_t *view,
+    time_t current_time)
+{
+  xmlXPathObject *path_obj;
+  char *zone_name = NULL;
+  int i;
+  size_t j;
+
+  path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
+  if (path_obj == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+    return (-1);
+  }
+
+  for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+  {
+    zone_name = (char *) xmlNodeListGetString (doc,
+        path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
+    if (zone_name != NULL)
+      break;
+  }
+
+  if (zone_name == NULL)
+  {
+    ERROR ("bind plugin: Could not determine zone name.");
+    xmlXPathFreeObject (path_obj);
+    return (-1);
+  }
+
+  for (j = 0; j < view->zones_num; j++)
+  {
+    if (strcasecmp (zone_name, view->zones[j]) == 0)
+      break;
+  }
+
+  xmlFree (zone_name);
+  zone_name = NULL;
+
+  if (j >= views_num)
+  {
+    xmlXPathFreeObject (path_obj);
+    return (0);
+  }
+
+  zone_name = view->zones[j];
+
+  DEBUG ("bind plugin: bind_xml_stats_handle_zone: Found zone `%s'.",
+      zone_name);
+
+  { /* Parse the <counters> tag {{{ */
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    translation_table_ptr_t table_ptr =
+    { 
+      nsstats_translation_table,
+      nsstats_translation_table_length,
+      plugin_instance
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-zone-%s",
+        view->name, zone_name);
+
+    bind_parse_generic_value_list (/* xpath = */ "counters",
+        /* callback = */ bind_xml_table_callback,
+        /* user_data = */ &table_ptr,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+  } /* }}} */
+
+  xmlXPathFreeObject (path_obj);
+
+  return (0);
+} /* }}} int bind_xml_stats_handle_zone */
+
+static int bind_xml_stats_search_zones (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *path_ctx, xmlNode *node, cb_view_t *view,
+    time_t current_time)
+{
+  xmlXPathObject *zone_nodes = NULL;
+  xmlXPathContext *zone_path_context;
+  int i;
+
+  zone_path_context = xmlXPathNewContext (doc);
+  if (zone_path_context == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathNewContext failed.");
+    return (-1);
+  }
+
+  zone_nodes = xmlXPathEvalExpression (BAD_CAST "zones/zone", path_ctx);
+  if (zone_nodes == NULL)
+  {
+    ERROR ("bind plugin: Cannot find any <view> tags.");
+    xmlXPathFreeContext (zone_path_context);
+    return (-1);
+  }
+
+  for (i = 0; i < zone_nodes->nodesetval->nodeNr; i++)
+  {
+    xmlNode *node;
+
+    node = zone_nodes->nodesetval->nodeTab[i];
+    assert (node != NULL);
+
+    zone_path_context->node = node;
+
+    bind_xml_stats_handle_zone (version, doc, zone_path_context, node, view,
+        current_time);
+  }
+
+  xmlXPathFreeObject (zone_nodes);
+  xmlXPathFreeContext (zone_path_context);
+  return (0);
+} /* }}} int bind_xml_stats_search_zones */
+
+static int bind_xml_stats_handle_view (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *path_ctx, xmlNode *node, time_t current_time)
+{
+  xmlXPathObject *path_obj;
+  char *view_name = NULL;
+  cb_view_t *view;
+  int i;
+  size_t j;
+
+  path_obj = xmlXPathEvalExpression (BAD_CAST "name", path_ctx);
+  if (path_obj == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+    return (-1);
+  }
+
+  for (i = 0; path_obj->nodesetval && (i < path_obj->nodesetval->nodeNr); i++)
+  {
+    view_name = (char *) xmlNodeListGetString (doc,
+        path_obj->nodesetval->nodeTab[i]->xmlChildrenNode, 1);
+    if (view_name != NULL)
+      break;
+  }
+
+  if (view_name == NULL)
+  {
+    ERROR ("bind plugin: Could not determine view name.");
+    xmlXPathFreeObject (path_obj);
+    return (-1);
+  }
+
+  for (j = 0; j < views_num; j++)
+  {
+    if (strcasecmp (view_name, views[j].name) == 0)
+      break;
+  }
+
+  xmlFree (view_name);
+  xmlXPathFreeObject (path_obj);
+
+  view_name = NULL;
+  path_obj = NULL;
+
+  if (j >= views_num)
+    return (0);
+
+  view = views + j;
+
+  DEBUG ("bind plugin: bind_xml_stats_handle_view: Found view `%s'.",
+      view->name);
+
+  if (view->qtypes != 0) /* {{{ */
+  {
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    list_info_ptr_t list_info =
+    {
+      plugin_instance,
+      /* type = */ "dns_qtype_gauge"
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-qtypes",
+        view->name);
+
+    bind_parse_generic_name_value (/* xpath = */ "rdtype",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+  } /* }}} */
+
+  if (view->resolver_stats != 0) /* {{{ */
+  {
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    translation_table_ptr_t table_ptr =
+    { 
+      resstats_translation_table,
+      resstats_translation_table_length,
+      plugin_instance
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance),
+        "%s-resolver_stats", view->name);
+
+    bind_parse_generic_name_value ("resstat",
+        /* callback = */ bind_xml_table_callback,
+        /* user_data = */ &table_ptr,
+        doc, path_ctx, current_time, DS_TYPE_COUNTER);
+  } /* }}} */
+
+  if (view->cacherrsets != 0) /* {{{ */
+  {
+    char plugin_instance[DATA_MAX_NAME_LEN];
+    list_info_ptr_t list_info =
+    {
+      plugin_instance,
+      /* type = */ "dns_qtype_gauge"
+    };
+
+    ssnprintf (plugin_instance, sizeof (plugin_instance), "%s-cache_rr_sets",
+        view->name);
+
+    bind_parse_generic_name_value (/* xpath = */ "cache/rrset",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, path_ctx, current_time, DS_TYPE_GAUGE);
+  } /* }}} */
+
+  if (view->zones_num > 0)
+    bind_xml_stats_search_zones (version, doc, path_ctx, node, view,
+        current_time);
+
+  return (0);
+} /* }}} int bind_xml_stats_handle_view */
+
+static int bind_xml_stats_search_views (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *xpathCtx, xmlNode *statsnode, time_t current_time)
+{
+  xmlXPathObject *view_nodes = NULL;
+  xmlXPathContext *view_path_context;
+  int i;
+
+  view_path_context = xmlXPathNewContext (doc);
+  if (view_path_context == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathNewContext failed.");
+    return (-1);
+  }
+
+  view_nodes = xmlXPathEvalExpression (BAD_CAST "views/view", xpathCtx);
+  if (view_nodes == NULL)
+  {
+    ERROR ("bind plugin: Cannot find any <view> tags.");
+    xmlXPathFreeContext (view_path_context);
+    return (-1);
+  }
+
+  for (i = 0; i < view_nodes->nodesetval->nodeNr; i++)
+  {
+    xmlNode *node;
+
+    node = view_nodes->nodesetval->nodeTab[i];
+    assert (node != NULL);
+
+    view_path_context->node = node;
+
+    bind_xml_stats_handle_view (version, doc, view_path_context, node,
+        current_time);
+  }
+
+  xmlXPathFreeObject (view_nodes);
+  xmlXPathFreeContext (view_path_context);
+  return (0);
+} /* }}} int bind_xml_stats_search_views */
+
+static int bind_xml_stats (int version, xmlDoc *doc, /* {{{ */
+    xmlXPathContext *xpathCtx, xmlNode *statsnode)
+{
+  time_t current_time = 0;
+  int status;
+
+  xpathCtx->node = statsnode;
+
+  /* TODO: Check `server/boot-time' to recognize server restarts. */
+
+  status = bind_xml_read_timestamp ("server/current-time",
+      doc, xpathCtx, &current_time);
+  if (status != 0)
+  {
+    ERROR ("bind plugin: Reading `server/current-time' failed.");
+    return (-1);
+  }
+  DEBUG ("bind plugin: Current server time is %i.", (int) current_time);
+
+  /* XPath:     server/requests/opcode
+   * Variables: QUERY, IQUERY, NOTIFY, UPDATE, ...
+   * Layout:
+   *   <opcode>
+   *     <name>A</name>
+   *     <counter>1</counter>
+   *   </opcode>
+   *   :
+   */
+  if (global_opcodes != 0)
+  {
+    list_info_ptr_t list_info =
+    {
+      /* plugin instance = */ "global-opcodes",
+      /* type = */ "dns_opcode"
+    };
+
+    bind_parse_generic_name_value (/* xpath = */ "server/requests/opcode",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
+
+  /* XPath:     server/queries-in/rdtype
+   * Variables: RESERVED0, A, NS, CNAME, SOA, MR, PTR, HINFO, MX, TXT, RP,
+   *            X25, PX, AAAA, LOC, SRV, NAPTR, A6, DS, RRSIG, NSEC, DNSKEY,
+   *            SPF, TKEY, IXFR, AXFR, ANY, ..., Others
+   * Layout:
+   *   <rdtype>
+   *     <name>A</name>
+   *     <counter>1</counter>
+   *   </rdtype>
+   *   :
+   */
+  if (global_qtypes != 0)
+  {
+    list_info_ptr_t list_info =
+    {
+      /* plugin instance = */ "global-qtypes",
+      /* type = */ "dns_qtype"
+    };
+
+    bind_parse_generic_name_value (/* xpath = */ "server/queries-in/rdtype",
+        /* callback = */ bind_xml_list_callback,
+        /* user_data = */ &list_info,
+        doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+  }
+  
+  /* XPath:     server/nsstats, server/nsstat
+   * Variables: Requestv4, Requestv6, ReqEdns0, ReqBadEDNSVer, ReqTSIG,
+   *            ReqSIG0, ReqBadSIG, ReqTCP, AuthQryRej, RecQryRej, XfrRej,
+   *            UpdateRej, Response, TruncatedResp, RespEDNS0, RespTSIG,
+   *            RespSIG0, QrySuccess, QryAuthAns, QryNoauthAns, QryReferral,
+   *            QryNxrrset, QrySERVFAIL, QryFORMERR, QryNXDOMAIN, QryRecursion,
+   *            QryDuplicate, QryDropped, QryFailure, XfrReqDone, UpdateReqFwd,
+   *            UpdateRespFwd, UpdateFwdFail, UpdateDone, UpdateFail,
+   *            UpdateBadPrereq
+   * Layout v1:
+   *   <nsstats>
+   *     <Requestv4>1</Requestv4>
+   *     <Requestv6>0</Requestv6>
+   *     :
+   *   </nsstats>
+   * Layout v2:
+   *   <nsstat>
+   *     <name>Requestv4</name>
+   *     <counter>1</counter>
+   *   </nsstat>
+   *   <nsstat>
+   *     <name>Requestv6</name>
+   *     <counter>0</counter>
+   *   </nsstat>
+   *   :
+   */
+  if (global_server_stats)
+  {
+    translation_table_ptr_t table_ptr =
+    { 
+      nsstats_translation_table,
+      nsstats_translation_table_length,
+      /* plugin_instance = */ "global-server_stats"
+    };
+
+    if (version == 1)
+    {
+      bind_parse_generic_value_list ("server/nsstats",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_name_value ("server/nsstat",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+  }
+
+  /* XPath:     server/zonestats, server/zonestat
+   * Variables: NotifyOutv4, NotifyOutv6, NotifyInv4, NotifyInv6, NotifyRej,
+   *            SOAOutv4, SOAOutv6, AXFRReqv4, AXFRReqv6, IXFRReqv4, IXFRReqv6,
+   *            XfrSuccess, XfrFail
+   * Layout v1:
+   *   <zonestats>
+   *     <NotifyOutv4>0</NotifyOutv4>
+   *     <NotifyOutv6>0</NotifyOutv6>
+   *     :
+   *   </zonestats>
+   * Layout v2:
+   *   <zonestat>
+   *     <name>NotifyOutv4</name>
+   *     <counter>0</counter>
+   *   </zonestat>
+   *   <zonestat>
+   *     <name>NotifyOutv6</name>
+   *     <counter>0</counter>
+   *   </zonestat>
+   *   :
+   */
+  if (global_zone_maint_stats)
+  {
+    translation_table_ptr_t table_ptr =
+    { 
+      zonestats_translation_table,
+      zonestats_translation_table_length,
+      /* plugin_instance = */ "global-zone_maint_stats"
+    };
+
+    if (version == 1)
+    {
+      bind_parse_generic_value_list ("server/zonestats",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_name_value ("server/zonestat",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+  }
+
+  /* XPath:     server/resstats
+   * Variables: Queryv4, Queryv6, Responsev4, Responsev6, NXDOMAIN, SERVFAIL,
+   *            FORMERR, OtherError, EDNS0Fail, Mismatch, Truncated, Lame,
+   *            Retry, GlueFetchv4, GlueFetchv6, GlueFetchv4Fail,
+   *            GlueFetchv6Fail, ValAttempt, ValOk, ValNegOk, ValFail
+   * Layout v1:
+   *   <resstats>
+   *     <Queryv4>0</Queryv4>
+   *     <Queryv6>0</Queryv6>
+   *     :
+   *   </resstats>
+   * Layout v2:
+   *   <resstat>
+   *     <name>Queryv4</name>
+   *     <counter>0</counter>
+   *   </resstat>
+   *   <resstat>
+   *     <name>Queryv6</name>
+   *     <counter>0</counter>
+   *   </resstat>
+   *   :
+   */
+  if (global_resolver_stats != 0)
+  {
+    translation_table_ptr_t table_ptr =
+    { 
+      resstats_translation_table,
+      resstats_translation_table_length,
+      /* plugin_instance = */ "global-resolver_stats"
+    };
+
+    if (version == 1)
+    {
+      bind_parse_generic_value_list ("server/resstats",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+    else
+    {
+      bind_parse_generic_name_value ("server/resstat",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_COUNTER);
+    }
+  }
+
+  /* XPath:  memory/summary
+   * Variables: TotalUse, InUse, BlockSize, ContextSize, Lost
+   * Layout: v2:
+   *   <summary>
+   *     <TotalUse>6587096</TotalUse>
+   *     <InUse>1345424</InUse>
+   *     <BlockSize>5505024</BlockSize>
+   *     <ContextSize>3732456</ContextSize>
+   *     <Lost>0</Lost>
+   *   </summary>
+   */
+  if (global_memory_stats != 0)
+  {
+    translation_table_ptr_t table_ptr =
+    {
+      memsummary_translation_table,
+      memsummary_translation_table_length,
+      /* plugin_instance = */ "global-memory_stats"
+    };
+
+    bind_parse_generic_value_list ("memory/summary",
+          /* callback = */ bind_xml_table_callback,
+          /* user_data = */ &table_ptr,
+          doc, xpathCtx, current_time, DS_TYPE_GAUGE);
+  }
+
+  if (views_num > 0)
+    bind_xml_stats_search_views (version, doc, xpathCtx, statsnode,
+        current_time);
+
+  return 0;
+} /* }}} int bind_xml_stats */
+
+static int bind_xml (const char *data) /* {{{ */
+{
+  xmlDoc *doc = NULL;
+  xmlXPathContext *xpathCtx = NULL;
+  xmlXPathObject *xpathObj = NULL;
+  int ret = -1;
+  int i;
+
+  doc = xmlParseMemory (data, strlen (data));
+  if (doc == NULL)
+  {
+    ERROR ("bind plugin: xmlParseMemory failed.");
+    return (-1);
+  }
+
+  xpathCtx = xmlXPathNewContext (doc);
+  if (xpathCtx == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathNewContext failed.");
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  xpathObj = xmlXPathEvalExpression (BAD_CAST "/isc/bind/statistics", xpathCtx);
+  if (xpathObj == NULL)
+  {
+    ERROR ("bind plugin: Cannot find the <statistics> tag.");
+    xmlXPathFreeContext (xpathCtx);
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+  else if (xpathObj->nodesetval == NULL)
+  {
+    ERROR ("bind plugin: xmlXPathEvalExpression failed.");
+    xmlXPathFreeObject (xpathObj);
+    xmlXPathFreeContext (xpathCtx);
+    xmlFreeDoc (doc);
+    return (-1);
+  }
+
+  for (i = 0; i < xpathObj->nodesetval->nodeNr; i++)
+  {
+    xmlNode *node;
+    char *attr_version;
+    int parsed_version = 0;
+
+    node = xpathObj->nodesetval->nodeTab[i];
+    assert (node != NULL);
+
+    attr_version = (char *) xmlGetProp (node, BAD_CAST "version");
+    if (attr_version == NULL)
+    {
+      NOTICE ("bind plugin: Found <statistics> tag doesn't have a "
+          "`version' attribute.");
+      continue;
+    }
+    DEBUG ("bind plugin: Found: <statistics version=\"%s\">", attr_version);
+
+    /* At the time this plugin was written, version "1.0" was used by
+     * BIND 9.5.0, version "2.0" was used by BIND 9.5.1 and 9.6.0. We assume
+     * that "1.*" and "2.*" don't introduce structural changes, so we just
+     * check for the first two characters here. */
+    if (strncmp ("1.", attr_version, strlen ("1.")) == 0)
+      parsed_version = 1;
+    else if (strncmp ("2.", attr_version, strlen ("2.")) == 0)
+      parsed_version = 2;
+    else
+    {
+      /* TODO: Use the complaint mechanism here. */
+      NOTICE ("bind plugin: Found <statistics> tag with version `%s'. "
+          "Unfortunately I have no clue how to parse that. "
+          "Please open a bug report for this.", attr_version);
+      xmlFree (attr_version);
+      continue;
+    }
+
+    ret = bind_xml_stats (parsed_version,
+        doc, xpathCtx, node);
+
+    xmlFree (attr_version);
+    /* One <statistics> node ought to be enough. */
+    break;
+  }
+
+  xmlXPathFreeObject (xpathObj);
+  xmlXPathFreeContext (xpathCtx);
+  xmlFreeDoc (doc);
+
+  return (ret);
+} /* }}} int bind_xml */
+
+static int bind_config_set_bool (const char *name, int *var, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || ( ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("bind plugin: The `%s' option needs "
+        "exactly one boolean argument.", name);
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    *var = 1;
+  else
+    *var = 0;
+  return 0;
+} /* }}} int bind_config_set_bool */
+
+static int bind_config_add_view_zone (cb_view_t *view, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char **tmp;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("bind plugin: The `Zone' option needs "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  tmp = (char **) realloc (view->zones,
+      sizeof (char *) * (view->zones_num + 1));
+  if (tmp == NULL)
+  {
+    ERROR ("bind plugin: realloc failed.");
+    return (-1);
+  }
+  view->zones = tmp;
+
+  view->zones[view->zones_num] = strdup (ci->values[0].value.string);
+  if (view->zones[view->zones_num] == NULL)
+  {
+    ERROR ("bind plugin: strdup failed.");
+    return (-1);
+  }
+  view->zones_num++;
+
+  return (0);
+} /* }}} int bind_config_add_view_zone */
+
+static int bind_config_add_view (oconfig_item_t *ci) /* {{{ */
+{
+  cb_view_t *tmp;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("bind plugin: `View' blocks need exactly one string argument.");
+    return (-1);
+  }
+
+  tmp = (cb_view_t *) realloc (views, sizeof (*views) * (views_num + 1));
+  if (tmp == NULL)
+  {
+    ERROR ("bind plugin: realloc failed.");
+    return (-1);
+  }
+  views = tmp;
+  tmp = views + views_num;
+
+  memset (tmp, 0, sizeof (*tmp));
+  tmp->qtypes = 1;
+  tmp->resolver_stats = 1;
+  tmp->cacherrsets = 1;
+  tmp->zones = NULL;
+  tmp->zones_num = 0;
+
+  tmp->name = strdup (ci->values[0].value.string);
+  if (tmp->name == NULL)
+  {
+    ERROR ("bind plugin: strdup failed.");
+    free (tmp);
+    return (-1);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("QTypes", child->key) == 0)
+      bind_config_set_bool ("QTypes", &tmp->qtypes, child);
+    else if (strcasecmp ("ResolverStats", child->key) == 0)
+      bind_config_set_bool ("ResolverStats", &tmp->resolver_stats, child);
+    else if (strcasecmp ("CacheRRSets", child->key) == 0)
+      bind_config_set_bool ("CacheRRSets", &tmp->cacherrsets, child);
+    else if (strcasecmp ("Zone", child->key) == 0)
+      bind_config_add_view_zone (tmp, child);
+    else
+    {
+      WARNING ("bind plugin: Unknown configuration option "
+          "`%s' in view `%s' will be ignored.", child->key, tmp->name);
+    }
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  views_num++;
+  return (0);
+} /* }}} int bind_config_add_view */
+
+static int bind_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Url", child->key) == 0) {
+      if ((child->values_num != 1) || (child->values[0].type != OCONFIG_TYPE_STRING))
+      {
+        WARNING ("bind plugin: The `Url' option needs "
+                 "exactly one string argument.");
+        return (-1);
+      }
+
+      url = strdup (child->values[0].value.string);
+    } else if (strcasecmp ("OpCodes", child->key) == 0)
+      bind_config_set_bool ("OpCodes", &global_opcodes, child);
+    else if (strcasecmp ("QTypes", child->key) == 0)
+      bind_config_set_bool ("QTypes", &global_qtypes, child);
+    else if (strcasecmp ("ServerStats", child->key) == 0)
+      bind_config_set_bool ("ServerStats", &global_server_stats, child);
+    else if (strcasecmp ("ZoneMaintStats", child->key) == 0)
+      bind_config_set_bool ("ZoneMaintStats", &global_zone_maint_stats, child);
+    else if (strcasecmp ("ResolverStats", child->key) == 0)
+      bind_config_set_bool ("ResolverStats", &global_resolver_stats, child);
+    else if (strcasecmp ("MemoryStats", child->key) == 0)
+      bind_config_set_bool ("MemoryStats", &global_memory_stats, child);
+    else if (strcasecmp ("View", child->key) == 0)
+      bind_config_add_view (child);
+    else
+    {
+      WARNING ("bind plugin: Unknown configuration option "
+          "`%s' will be ignored.", child->key);
+    }
+  }
+
+  return (0);
+} /* }}} int bind_config */
+
+static int bind_init (void) /* {{{ */
+{
+  if (curl != NULL)
+    return (0);
+
+  curl = curl_easy_init ();
+  if (curl == NULL)
+  {
+    ERROR ("bind plugin: bind_init: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, bind_curl_callback);
+  curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, bind_curl_error);
+  curl_easy_setopt (curl, CURLOPT_URL, (url != NULL) ? url : BIND_DEFAULT_URL);
+
+  return (0);
+} /* }}} int bind_init */
+
+static int bind_read (void) /* {{{ */
+{
+  int status;
+
+  if (curl == NULL)
+  {
+    ERROR ("bind plugin: I don't have a CURL object.");
+    return (-1);
+  }
+
+  bind_buffer_fill = 0;
+  if (curl_easy_perform (curl) != 0)
+  {
+    ERROR ("bind plugin: curl_easy_perform failed: %s",
+        bind_curl_error);
+    return (-1);
+  }
+
+  status = bind_xml (bind_buffer);
+  if (status != 0)
+    return (-1);
+  else
+    return (0);
+} /* }}} int bind_read */
+
+static int bind_shutdown (void) /* {{{ */
+{
+  if (curl != NULL)
+  {
+    curl_easy_cleanup (curl);
+    curl = NULL;
+  }
+
+  return (0);
+} /* }}} int bind_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("bind", bind_config);
+  plugin_register_init ("bind", bind_init);
+  plugin_register_read ("bind", bind_read);
+  plugin_register_shutdown ("bind", bind_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 et fdm=marker : */
index d935c70..b95779d 100644 (file)
@@ -83,7 +83,7 @@ plugin within collectd. I<type> identifies the type and number of values
 data-sets is available in the B<types.db> file. See L<types.db(5)> for a
 description of the format of this file.
 
-The I<OptionList> is an optional list of I<Options>, where each option if a
+The I<OptionList> is an optional list of I<Options>, where each option is a
 key-value-pair. A list of currently understood options can be found below, all
 other options will be ignored. Values that contain spaces must be quoted with
 double quotes.
index ca72a68..0c480a0 100644 (file)
@@ -1,15 +1,50 @@
-#include "config.h"
+/**
+ * collectd-nagios - src/collectd-nagios.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Set to C99 and POSIX code */
+#if COLLECT_STANDARDS
+# include "standards.h"
+#endif /* COLLECT_STANDARDS */
+
+#if !defined(__GNUC__) || !__GNUC__
+# define __attribute__(x) /**/
+#endif
 
 #include <stdlib.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <errno.h>
 #include <string.h>
+#include <strings.h>
 #include <assert.h>
 
 #include <sys/socket.h>
 #include <sys/un.h>
 
+#include "libcollectdclient/client.h"
+
 /*
  * This is copied directly from collectd.h. Make changes there!
  */
@@ -46,6 +81,7 @@
 #define CON_NONE     0
 #define CON_AVERAGE  1
 #define CON_SUM      2
+#define CON_PERCENTAGE  3
 
 struct range_s
 {
@@ -69,19 +105,91 @@ static int consolitation_g = CON_NONE;
 static char **match_ds_g = NULL;
 static int    match_ds_num_g = 0;
 
-static int ignore_ds (const char *name)
+/* `strdup' is an XSI extension. I don't want to pull in all of XSI just for
+ * that, so here's an own implementation.. It's easy enough. The GCC attributes
+ * are supposed to get good performance..  -octo */
+__attribute__((malloc, nonnull (1)))
+static char *cn_strdup (const char *str) /* {{{ */
+{
+  size_t strsize;
+  char *ret;
+
+  strsize = strlen (str) + 1;
+  ret = (char *) malloc (strsize);
+  if (ret != NULL)
+    memcpy (ret, str, strsize);
+  return (ret);
+} /* }}} char *cn_strdup */
+
+static int filter_ds (size_t *values_num,
+               double **values, char ***values_names)
 {
-       int i;
+       gauge_t *new_values;
+       char   **new_names;
+
+       size_t i;
 
        if (match_ds_g == NULL)
-               return (0);
+               return (RET_OKAY);
+
+       new_values = (gauge_t *)calloc (match_ds_num_g, sizeof (*new_values));
+       if (new_values == NULL)
+       {
+               fprintf (stderr, "malloc failed: %s\n", strerror (errno));
+               return (RET_UNKNOWN);
+       }
+
+       new_names = (char **)calloc (match_ds_num_g, sizeof (*new_names));
+       if (new_names == NULL)
+       {
+               fprintf (stderr, "malloc failed: %s\n", strerror (errno));
+               free (new_values);
+               return (RET_UNKNOWN);
+       }
+
+       for (i = 0; i < (size_t) match_ds_num_g; i++)
+       {
+               size_t j;
+
+               /* match_ds_g keeps pointers into argv but the names will be freed */
+               new_names[i] = cn_strdup (match_ds_g[i]);
+               if (new_names[i] == NULL)
+               {
+                       fprintf (stderr, "cn_strdup failed: %s\n", strerror (errno));
+                       free (new_values);
+                       for (j = 0; j < i; j++)
+                               free (new_names[j]);
+                       free (new_names);
+                       return (RET_UNKNOWN);
+               }
+
+               for (j = 0; j < *values_num; j++)
+                       if (strcasecmp (new_names[i], (*values_names)[j]) == 0)
+                               break;
+
+               if (j == *values_num)
+               {
+                       printf ("ERROR: DS `%s' is not available.\n", new_names[i]);
+                       free (new_values);
+                       for (j = 0; j <= i; j++)
+                               free (new_names[j]);
+                       free (new_names);
+                       return (RET_CRITICAL);
+               }
+
+               new_values[i] = (*values)[j];
+       }
 
-       for (i = 0; i < match_ds_num_g; i++)
-               if (strcasecmp (match_ds_g[i], name) == 0)
-                       return (0);
+       free (*values);
+       for (i = 0; i < *values_num; i++)
+               free ((*values_names)[i]);
+       free (*values_names);
 
-       return (1);
-} /* int ignore_ds */
+       *values       = new_values;
+       *values_names = new_names;
+       *values_num   = match_ds_num_g;
+       return (RET_OKAY);
+} /* int filter_ds */
 
 static void parse_range (char *string, range_t *range)
 {
@@ -124,7 +232,7 @@ static void parse_range (char *string, range_t *range)
                range->max = atof (max_ptr);
 } /* void parse_range */
 
-int match_range (range_t *range, double value)
+static int match_range (range_t *range, double value)
 {
        int ret = 0;
 
@@ -134,164 +242,7 @@ int match_range (range_t *range, double value)
                ret = 1;
 
        return (((ret - range->invert) == 0) ? 0 : 1);
-}
-
-static int get_values (int *ret_values_num, double **ret_values,
-               char ***ret_values_names)
-{
-       struct sockaddr_un sa;
-       int status;
-       int fd;
-       FILE *fh_in, *fh_out;
-       char buffer[4096];
-
-       int values_num;
-       double *values;
-       char **values_names;
-
-       int i;
-       int j;
-
-       fd = socket (PF_UNIX, SOCK_STREAM, 0);
-       if (fd < 0)
-       {
-               fprintf (stderr, "socket failed: %s\n",
-                               strerror (errno));
-               return (-1);
-       }
-
-       memset (&sa, '\0', sizeof (sa));
-       sa.sun_family = AF_UNIX;
-       strncpy (sa.sun_path, socket_file_g,
-                       sizeof (sa.sun_path) - 1);
-
-       status = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
-       if (status != 0)
-       {
-               fprintf (stderr, "connect failed: %s\n",
-                               strerror (errno));
-               return (-1);
-       }
-
-       fh_in = fdopen (fd, "r");
-       if (fh_in == NULL)
-       {
-               fprintf (stderr, "fdopen failed: %s\n",
-                               strerror (errno));
-               close (fd);
-               return (-1);
-       }
-
-       fh_out = fdopen (fd, "w");
-       if (fh_out == NULL)
-       {
-               fprintf (stderr, "fdopen failed: %s\n",
-                               strerror (errno));
-               fclose (fh_in);
-               return (-1);
-       }
-
-       fprintf (fh_out, "GETVAL %s/%s\n", hostname_g, value_string_g);
-       fflush (fh_out);
-
-       if (fgets (buffer, sizeof (buffer), fh_in) == NULL)
-       {
-               fprintf (stderr, "fgets failed: %s\n",
-                               strerror (errno));
-               fclose (fh_in);
-               fclose (fh_out);
-               return (-1);
-       }
-
-       {
-               char *ptr = strchr (buffer, ' ');
-
-               if (ptr != NULL)
-                       *ptr = '\0';
-
-               values_num = atoi (buffer);
-               if (values_num < 1)
-                       return (-1);
-       }
-
-       values = (double *) malloc (values_num * sizeof (double));
-       if (values == NULL)
-       {
-               fprintf (stderr, "malloc failed: %s\n",
-                               strerror (errno));
-               return (-1);
-       }
-
-       values_names = (char **) malloc (values_num * sizeof (char *));
-       if (values_names == NULL)
-       {
-               fprintf (stderr, "malloc failed: %s\n",
-                               strerror (errno));
-               free (values);
-               return (-1);
-       }
-       memset (values_names, 0, values_num * sizeof (char *));
-
-       i = 0; /* index of the values returned by the server */
-       j = 0; /* number of values in `values_names' and `values' */
-       while (fgets (buffer, sizeof (buffer), fh_in) != NULL)
-       {
-               do /* while (0) */
-               {
-                       char *key;
-                       char *value;
-                       char *endptr;
-
-                       key = buffer;
-
-                       value = strchr (key, '=');
-                       if (value == NULL)
-                       {
-                               fprintf (stderr, "Cannot parse line: %s\n", buffer);
-                               break;
-                       }
-                       *value = 0;
-                       value++;
-
-                       if (ignore_ds (key) != 0)
-                               break;
-
-                       endptr = NULL;
-                       errno = 0;
-                       values[j] = strtod (value, &endptr);
-                       if ((endptr == value) || (errno != 0))
-                       {
-                               fprintf (stderr, "Could not parse buffer "
-                                               "as number: %s\n", value);
-                               break;
-                       }
-
-                       values_names[j] = strdup (key);
-                       if (values_names[j] == NULL)
-                       {
-                               fprintf (stderr, "strdup failed.\n");
-                               break;
-                       }
-                       j++;
-               } while (0);
-
-               i++;
-               if (i >= values_num)
-                       break;
-       }
-       /* Set `values_num' to the number of values actually stored in the
-        * array. */
-       values_num = j;
-
-       fclose (fh_in); fh_in = NULL; fd = -1;
-       fclose (fh_out); fh_out = NULL;
-
-       *ret_values_num = values_num;
-       *ret_values = values;
-       *ret_values_names = values_names;
-
-       return (0);
-} /* int get_values */
+} /* int match_range */
 
 static void usage (const char *name)
 {
@@ -304,7 +255,7 @@ static void usage (const char *name)
                        "  -d <ds>        Select the DS to examine. May be repeated to examine multiple\n"
                        "                 DSes. By default all DSes are used.\n"
                        "  -g <consol>    Method to use to consolidate several DSes.\n"
-                       "                 Valid arguments are `none', `average' and `sum'\n"
+                       "                 See below for a list of valid arguments.\n"
                        "  -H <host>      Hostname to query the values for.\n"
                        "  -c <range>     Critical range\n"
                        "  -w <range>     Warning range\n"
@@ -315,17 +266,21 @@ static void usage (const char *name)
                        "  average:       Calculate the average of all matching DSes and apply the\n"
                        "                 warning- and critical-ranges to the calculated average.\n"
                        "  sum:           Apply the ranges to the sum of all DSes.\n"
+                       "  percentage:    Apply the ranges to the ratio (in percent) of the first value\n"
+                       "                 and the sum of all values."
                        "\n", name);
        exit (1);
 } /* void usage */
 
-int do_check_con_none (int values_num, double *values, char **values_names)
+static int do_check_con_none (size_t values_num,
+               double *values, char **values_names)
 {
-       int i;
-
        int num_critical = 0;
        int num_warning  = 0;
        int num_okay = 0;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
+       size_t i;
 
        for (i = 0; i < values_num; i++)
        {
@@ -339,30 +294,49 @@ int do_check_con_none (int values_num, double *values, char **values_names)
                        num_okay++;
        }
 
-       printf ("%i critical, %i warning, %i okay",
+       if ((num_critical == 0) && (num_warning == 0) && (num_okay == 0))
+       {
+               printf ("WARNING: No defined values found\n");
+               return (RET_WARNING);
+       }
+       else if ((num_critical == 0) && (num_warning == 0))
+       {
+               status_str = "OKAY";
+               status_code = RET_OKAY;
+       }
+       else if (num_critical == 0)
+       {
+               status_str = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+
+       printf ("%s: %i critical, %i warning, %i okay", status_str,
                        num_critical, num_warning, num_okay);
        if (values_num > 0)
        {
                printf (" |");
                for (i = 0; i < values_num; i++)
-                       printf (" %s=%lf;;;;", values_names[i], values[i]);
+                       printf (" %s=%g;;;;", values_names[i], values[i]);
        }
        printf ("\n");
 
-       if ((num_critical != 0) || (values_num == 0))
-               return (RET_CRITICAL);
-       else if (num_warning != 0)
-               return (RET_WARNING);
-
-       return (RET_OKAY);
+       return (status_code);
 } /* int do_check_con_none */
 
-int do_check_con_average (int values_num, double *values, char **values_names)
+static int do_check_con_average (size_t values_num,
+               double *values, char **values_names)
 {
-       int i;
+       size_t i;
        double total;
        int total_num;
        double average;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
 
        total = 0.0;
        total_num = 0;
@@ -376,30 +350,45 @@ int do_check_con_average (int values_num, double *values, char **values_names)
        }
 
        if (total_num == 0)
-               average = NAN;
-       else
-               average = total / total_num;
-       printf ("%lf average |", average);
-       for (i = 0; i < values_num; i++)
-               printf (" %s=%lf;;;;", values_names[i], values[i]);
-
-       if (total_num == 0)
+       {
+               printf ("WARNING: No defined values found\n");
                return (RET_WARNING);
+       }
 
-       if (isnan (average)
-                       || match_range (&range_critical_g, average))
-               return (RET_CRITICAL);
+       average = total / total_num;
+
+       if (match_range (&range_critical_g, average) != 0)
+       {
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
        else if (match_range (&range_warning_g, average) != 0)
-               return (RET_WARNING);
+       {
+               status_str = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str = "OKAY";
+               status_code = RET_OKAY;
+       }
 
-       return (RET_OKAY);
+       printf ("%s: %g average |", status_str, average);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%g;;;;", values_names[i], values[i]);
+       printf ("\n");
+
+       return (status_code);
 } /* int do_check_con_average */
 
-int do_check_con_sum (int values_num, double *values, char **values_names)
+static int do_check_con_sum (size_t values_num,
+               double *values, char **values_names)
 {
-       int i;
+       size_t i;
        double total;
        int total_num;
+       const char *status_str = "UNKNOWN";
+       int status_code = RET_UNKNOWN;
 
        total = 0.0;
        total_num = 0;
@@ -420,47 +409,150 @@ int do_check_con_sum (int values_num, double *values, char **values_names)
 
        if (match_range (&range_critical_g, total) != 0)
        {
-               printf ("CRITICAL: Sum = %lf\n", total);
-               return (RET_CRITICAL);
+               status_str = "CRITICAL";
+               status_code = RET_CRITICAL;
        }
        else if (match_range (&range_warning_g, total) != 0)
        {
-               printf ("WARNING: Sum = %lf\n", total);
-               return (RET_WARNING);
+               status_str = "WARNING";
+               status_code = RET_WARNING;
        }
        else
        {
-               printf ("OKAY: Sum = %lf\n", total);
-               return (RET_OKAY);
+               status_str = "OKAY";
+               status_code = RET_OKAY;
        }
 
-       return (RET_UNKNOWN);
+       printf ("%s: %g sum |", status_str, total);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%g;;;;", values_names[i], values[i]);
+       printf ("\n");
+
+       return (status_code);
 } /* int do_check_con_sum */
 
-int do_check (void)
+static int do_check_con_percentage (size_t values_num,
+               double *values, char **values_names)
+{
+       size_t i;
+       double sum = 0.0;
+       double percentage;
+
+       const char *status_str  = "UNKNOWN";
+       int         status_code = RET_UNKNOWN;
+
+       if ((values_num < 1) || (isnan (values[0])))
+       {
+               printf ("WARNING: The first value is not defined\n");
+               return (RET_WARNING);
+       }
+
+       for (i = 0; i < values_num; i++)
+               if (!isnan (values[i]))
+                       sum += values[i];
+
+       if (sum == 0.0)
+       {
+               printf ("WARNING: Values sum up to zero\n");
+               return (RET_WARNING);
+       }
+
+       percentage = 100.0 * values[0] / sum;
+
+       if (match_range (&range_critical_g, percentage) != 0)
+       {
+               status_str  = "CRITICAL";
+               status_code = RET_CRITICAL;
+       }
+       else if (match_range (&range_warning_g, percentage) != 0)
+       {
+               status_str  = "WARNING";
+               status_code = RET_WARNING;
+       }
+       else
+       {
+               status_str  = "OKAY";
+               status_code = RET_OKAY;
+       }
+
+       printf ("%s: %lf percent |", status_str, percentage);
+       for (i = 0; i < values_num; i++)
+               printf (" %s=%lf;;;;", values_names[i], values[i]);
+       return (status_code);
+} /* int do_check_con_percentage */
+
+static int do_check (void)
 {
-       double  *values;
+       lcc_connection_t *connection;
+       gauge_t *values;
        char   **values_names;
-       int      values_num;
+       size_t   values_num;
+       char address[1024];
+       char ident_str[1024];
+       lcc_identifier_t ident;
+       size_t i;
+       int status;
+
+       snprintf (address, sizeof (address), "unix:%s", socket_file_g);
+       address[sizeof (address) - 1] = 0;
+
+       snprintf (ident_str, sizeof (ident_str), "%s/%s",
+                       hostname_g, value_string_g);
+       ident_str[sizeof (ident_str) - 1] = 0;
 
-       if (get_values (&values_num, &values, &values_names) != 0)
+       connection = NULL;
+       status = lcc_connect (address, &connection);
+       if (status != 0)
        {
-               fputs ("ERROR: Cannot get values from daemon\n", stdout);
+               printf ("ERROR: Connecting to daemon at %s failed.\n",
+                               socket_file_g);
                return (RET_CRITICAL);
        }
 
+       memset (&ident, 0, sizeof (ident));
+       status = lcc_string_to_identifier (connection, &ident, ident_str);
+       if (status != 0)
+       {
+               printf ("ERROR: Creating an identifier failed: %s.\n",
+                               lcc_strerror (connection));
+               LCC_DESTROY (connection);
+               return (RET_CRITICAL);
+       }
+
+       status = lcc_getval (connection, &ident,
+                       &values_num, &values, &values_names);
+       if (status != 0)
+       {
+               printf ("ERROR: Retrieving values from the daemon failed: %s.\n",
+                               lcc_strerror (connection));
+               LCC_DESTROY (connection);
+               return (RET_CRITICAL);
+       }
+
+       LCC_DESTROY (connection);
+
+       status = filter_ds (&values_num, &values, &values_names);
+       if (status != RET_OKAY)
+               return (status);
+
+       status = RET_UNKNOWN;
        if (consolitation_g == CON_NONE)
-               return (do_check_con_none (values_num, values, values_names));
+               status =  do_check_con_none (values_num, values, values_names);
        else if (consolitation_g == CON_AVERAGE)
-               return (do_check_con_average (values_num, values, values_names));
+               status =  do_check_con_average (values_num, values, values_names);
        else if (consolitation_g == CON_SUM)
-               return (do_check_con_sum (values_num, values, values_names));
+               status = do_check_con_sum (values_num, values, values_names);
+       else if (consolitation_g == CON_PERCENTAGE)
+               status = do_check_con_percentage (values_num, values, values_names);
 
        free (values);
-       free (values_names); /* FIXME? */
+       if (values_names != NULL)
+               for (i = 0; i < values_num; i++)
+                       free (values_names[i]);
+       free (values_names);
 
-       return (RET_UNKNOWN);
-}
+       return (status);
+} /* int do_check */
 
 int main (int argc, char **argv)
 {
@@ -504,8 +596,14 @@ int main (int argc, char **argv)
                                        consolitation_g = CON_AVERAGE;
                                else if (strcasecmp (optarg, "sum") == 0)
                                        consolitation_g = CON_SUM;
+                               else if (strcasecmp (optarg, "percentage") == 0)
+                                       consolitation_g = CON_PERCENTAGE;
                                else
+                               {
+                                       fprintf (stderr, "Unknown consolidation function `%s'.\n",
+                                                       optarg);
                                        usage (argv[0]);
+                               }
                                break;
                        case 'd':
                        {
@@ -520,10 +618,10 @@ int main (int argc, char **argv)
                                        return (RET_UNKNOWN);
                                }
                                match_ds_g = tmp;
-                               match_ds_g[match_ds_num_g] = strdup (optarg);
+                               match_ds_g[match_ds_num_g] = cn_strdup (optarg);
                                if (match_ds_g[match_ds_num_g] == NULL)
                                {
-                                       fprintf (stderr, "strdup failed: %s\n",
+                                       fprintf (stderr, "cn_strdup failed: %s\n",
                                                        strerror (errno));
                                        return (RET_UNKNOWN);
                                }
@@ -537,7 +635,10 @@ int main (int argc, char **argv)
 
        if ((socket_file_g == NULL) || (value_string_g == NULL)
                        || (hostname_g == NULL))
+       {
+               fprintf (stderr, "Missing required arguments.\n");
                usage (argv[0]);
+       }
 
        return (do_check ());
 } /* int main */
index 4980025..c6347ea 100644 (file)
@@ -63,6 +63,12 @@ The warning and critical ranges are applied to the average of all values.
 
 The warning and critical ranges are applied to the sum of all values.
 
+=item B<percentage>
+
+The warning and critical ranges are applied to the ratio (in percent) of the
+first value and the sum of all values. A warning is returned if the first
+value is not defined or if all values sum up to zero.
+
 =back
 
 =item B<-c> I<range>
@@ -96,7 +102,7 @@ As usual for Nagios plugins, this program writes a short, one line status
 message to STDOUT and signals success or failure with it's return value. It
 exits with a return value of B<0> for I<success>, B<1> for I<warning> and B<2>
 for I<critical>. If the values are not available or some other error occurred,
-it returns B<3> for I<unknown>. 
+it returns B<3> for I<unknown>.
 
 =head1 SEE ALSO
 
index c3fcb10..acb8abd 100644 (file)
@@ -198,6 +198,7 @@ layout looks like this:
   {
     values => [123, 0.5],
     time   => time (),
+    interval => $interval_g,
     host   => $hostname_g,
     plugin => 'myplugin',
     type   => 'myplugin',
@@ -208,7 +209,9 @@ layout looks like this:
 =item Notification
 
 A notification is one structure defining the severity, time and message of the
-status message as well as an identification of a data instance:
+status message as well as an identification of a data instance. Also, it
+includes an optional list of user-defined meta information represented as
+(name, value) pairs:
 
   {
     severity => NOTIF_FAILURE || NOTIF_WARNING || NOTIF_OKAY,
@@ -218,7 +221,30 @@ status message as well as an identification of a data instance:
     plugin   => 'myplugin',
     type     => 'mytype',
     plugin_instance => '',
-    type_instance   => ''
+    type_instance   => '',
+    meta     => [ { name => <name>, value => <value> }, ... ]
+  }
+
+=item Match-Proc
+
+A match-proc is one structure storing the callbacks of a "match" of the filter
+chain infrastructure. The general layout looks like this:
+
+  {
+    create  => 'my_create',
+    destroy => 'my_destroy',
+    match   => 'my_match'
+  }
+
+=item Target-Proc
+
+A target-proc is one structure storing the callbacks of a "target" of the
+filter chain infrastructure. The general layout looks like this:
+
+  {
+    create  => 'my_create',
+    destroy => 'my_destroy',
+    invoke  => 'my_invoke'
   }
 
 =back
@@ -341,12 +367,26 @@ as the first argument to B<plugin_register>. This syntax is still supported
 for backwards compatibility but has been deprecated and will be removed in
 some future version of collectd.
 
+=item B<plugin_write> ([B<plugins> => I<...>][, B<datasets> => I<...>],
+B<valuelists> => I<...>)
+
+Calls the write function of the given I<plugins> with the provided I<data
+sets> and I<value lists>. In contrast to B<plugin_dispatch_values>, it does
+not update collectd's internal cache and bypasses the filter mechanism (see
+L<collectd.conf(5)> for details). If the B<plugins> argument has been omitted,
+the values will be dispatched to all registered write plugins. If the
+B<datasets> argument has been omitted, the required data sets are looked up
+according to the C<type> member in the appropriate value list. The value of
+all three arguments may either be a single scalar or a reference to an array.
+If the B<datasets> argument has been specified, the number of data sets has to
+equal the number of specified value lists.
+
 =item B<plugin_flush> ([B<timeout> => I<timeout>][, B<plugins> => I<...>][,
 B<identifiers> => I<...>])
 
 Flush one or more 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 the undefined value. If the I<plugins>
+to C<-1>. The identifier defaults to the undefined value. If the B<plugins>
 argument has been specified, only named plugins will be flushed. The value of
 the B<plugins> and B<identifiers> arguments may either be a string or a
 reference to an array of strings.
@@ -385,6 +425,68 @@ B<LOG_NOTICE>, B<LOG_INFO> and B<LOG_DEBUG> respectively as I<log-level>.
 
 =back
 
+The following function provides the filter chain C-interface to Perl-modules.
+It is exported by the ":filter_chain" export tag (see the section "EXPORTS"
+below).
+
+=over 4
+
+=item B<fc_register> (I<type>, I<name>, I<proc>)
+
+Registers filter chain callbacks with collectd.
+
+I<type> may be any of:
+
+=over 4
+
+=item FC_MATCH
+
+=item FC_TARGET
+
+=back
+
+I<name> is the name of the match or target. By this name, the callbacks are
+identified in the configuration file when specifying a B<Match> or B<Target>
+block (see L<collectd.conf(5)> for details).
+
+I<proc> is a hash reference. The hash includes up to three callbacks: an
+optional constructor (B<create>) and destructor (B<destroy>) and a mandatory
+B<match> or B<invoke> callback. B<match> is called whenever processing an
+appropriate match, while B<invoke> is called whenever processing an
+appropriate target (see the section "FILTER CONFIGURATION" in
+L<collectd.conf(5)> for details). Just like any other callbacks, filter chain
+callbacks are identified by the function name rather than a function pointer
+because Perl does not support to share references to subroutines between
+threads. The following arguments are passed to the callbacks:
+
+=over 4
+
+=item create
+
+The arguments passed are I<config-item> and I<user-data>. See above for the
+layout of the config-item data-type. I<user-data> is a reference to a scalar
+value that may be used to store any information specific to this particular
+instance. The daemon does not care about this information at all. It's for the
+plugin's use only.
+
+=item destroy
+
+The only argument passed is I<user-data> which is a reference to the user data
+initialized in the B<create> callback. This callback may be used to cleanup
+instance-specific information and settings.
+
+=item match, invoke
+
+The arguments passed are I<data-set>, I<value-list>, I<meta> and I<user-data>.
+See above for the layout of the data-set and value-list data-types. I<meta> is
+a pointer to an array of meta information, just like the B<meta> member of the
+notification data-type (see above). I<user-data> is a reference to the user
+data initialized in the B<create> callback.
+
+=back
+
+=back
+
 =head1 GLOBAL VARIABLES
 
 =over 4
@@ -491,6 +593,34 @@ available (B<:all> will export all of them):
 
 =back
 
+=item B<:filter_chain>
+
+=over 4
+
+=item B<fc_register>
+
+=item B<FC_MATCH_NO_MATCH>
+
+=item B<FC_MATCH_MATCHES>
+
+=item B<FC_TARGET_CONTINUE>
+
+=item B<FC_TARGET_STOP>
+
+=item B<FC_TARGET_RETURN>
+
+=back
+
+=item B<:fc_types>
+
+=over 4
+
+=item B<FC_MATCH>
+
+=item B<FC_TARGET>
+
+=back
+
 =item B<:notif>
 
 =over 4
@@ -526,7 +656,7 @@ Any Perl plugin will start similar to:
 
   use Collectd qw( :all );
 
-A very simple read function will look like:
+A very simple read function might look like:
 
   sub foobar_read
   {
@@ -536,7 +666,7 @@ A very simple read function will look like:
     return 1;
   }
 
-A very simple write function will look like:
+A very simple write function might look like:
 
   sub foobar_write
   {
@@ -547,13 +677,27 @@ A very simple write function will look like:
     return 1;
   }
 
+A very simple match callback might look like:
+
+  sub foobar_match
+  {
+    my ($ds, $vl, $meta, $user_data) = @_;
+    if (matches($ds, $vl)) {
+      return FC_MATCH_MATCHES;
+    } else {
+      return FC_MATCH_NO_MATCH;
+    }
+  }
+
 To register those functions with collectd:
 
   plugin_register (TYPE_READ, "foobar", "foobar_read");
   plugin_register (TYPE_WRITE, "foobar", "foobar_write");
 
+  fc_register (FC_MATCH, "foobar", "foobar_match");
+
 See the section "DATA TYPES" above for a complete documentation of the data
-types used by the read and write functions.
+types used by the read, write and match functions.
 
 =head1 NOTES
 
index 9526ec9..576abef 100644 (file)
@@ -47,7 +47,7 @@ kstat_ctl_t *kc;
 
 static int loop = 0;
 
-static void *do_flush (void *arg)
+static void *do_flush (void __attribute__((unused)) *arg)
 {
        INFO ("Flushing all data.");
        plugin_flush (NULL, -1, NULL);
@@ -56,17 +56,17 @@ static void *do_flush (void *arg)
        return NULL;
 }
 
-static void sig_int_handler (int signal)
+static void sig_int_handler (int __attribute__((unused)) signal)
 {
        loop++;
 }
 
-static void sig_term_handler (int signal)
+static void sig_term_handler (int __attribute__((unused)) signal)
 {
        loop++;
 }
 
-static void sig_usr1_handler (int signal)
+static void sig_usr1_handler (int __attribute__((unused)) signal)
 {
        pthread_t      thread;
        pthread_attr_t attr;
@@ -253,6 +253,7 @@ static void exit_usage (int status)
                        "    -C <file>       Configuration file.\n"
                        "                    Default: "CONFIGFILE"\n"
                        "    -t              Test config and exit.\n"
+                       "    -T              Test plugin read and exit.\n"
                        "    -P <file>       PID-file.\n"
                        "                    Default: "PIDFILE"\n"
 #if COLLECT_DAEMON
@@ -300,6 +301,7 @@ static int do_loop (void)
 {
        struct timeval tv_now;
        struct timeval tv_next;
+       struct timeval tv_wait;
        struct timespec ts_wait;
 
        while (loop == 0)
@@ -330,14 +332,17 @@ static int do_loop (void)
                        return (-1);
                }
 
-               if (timeval_sub_timespec (&tv_next, &tv_now, &ts_wait) != 0)
+               if (timeval_cmp (tv_next, tv_now, &tv_wait) <= 0)
                {
-                       WARNING ("Not sleeping because "
-                                       "`timeval_sub_timespec' returned "
-                                       "non-zero!");
+                       WARNING ("Not sleeping because the next interval is "
+                                       "%i.%06i seconds in the past!",
+                                       (int) tv_wait.tv_sec, (int) tv_wait.tv_usec);
                        continue;
                }
 
+               ts_wait.tv_sec  = tv_wait.tv_sec;
+               ts_wait.tv_nsec = (long) (1000 * tv_wait.tv_usec);
+
                while ((loop == 0) && (nanosleep (&ts_wait, &ts_wait) == -1))
                {
                        if (errno != EINTR)
@@ -398,19 +403,21 @@ int main (int argc, char **argv)
        struct sigaction sig_pipe_action;
        char *configfile = CONFIGFILE;
        int test_config  = 0;
+       int test_readall = 0;
        const char *basedir;
 #if COLLECT_DAEMON
        struct sigaction sig_chld_action;
        pid_t pid;
        int daemonize    = 1;
 #endif
+       int exit_status = 0;
 
        /* read options */
        while (1)
        {
                int c;
 
-               c = getopt (argc, argv, "htC:"
+               c = getopt (argc, argv, "htTC:"
 #if COLLECT_DAEMON
                                "fP:"
 #endif
@@ -427,6 +434,13 @@ int main (int argc, char **argv)
                        case 't':
                                test_config = 1;
                                break;
+                       case 'T':
+                               test_readall = 1;
+                               global_option_set ("ReadThreads", "-1");
+#if COLLECT_DAEMON
+                               daemonize = 0;
+#endif /* COLLECT_DAEMON */
+                               break;
 #if COLLECT_DAEMON
                        case 'P':
                                global_option_set ("PIDFile", optarg);
@@ -580,10 +594,20 @@ int main (int argc, char **argv)
         * run the actual loops
         */
        do_init ();
-       do_loop ();
+
+       if (test_readall)
+       {
+               if (plugin_read_all_once () != 0)
+                       exit_status = 1;
+       }
+       else
+       {
+               INFO ("Initialization complete, entering read-loop.");
+               do_loop ();
+       }
 
        /* close syslog */
-       INFO ("Exiting normally");
+       INFO ("Exiting normally.");
 
        do_shutdown ();
 
@@ -592,5 +616,5 @@ int main (int argc, char **argv)
                pidfile_remove ();
 #endif /* COLLECT_DAEMON */
 
-       return (0);
+       return (exit_status);
 } /* int main */
index 9f14257..8a05912 100644 (file)
@@ -9,7 +9,7 @@ FQDNLookup   true
 #BaseDir     "@prefix@/var/lib/@PACKAGE_NAME@"
 #PIDFile     "@prefix@/var/run/@PACKAGE_NAME@.pid"
 #PluginDir   "@prefix@/lib/@PACKAGE_NAME@"
-#TypesDB     "@prefix@/lib/@PACKAGE_NAME@/types.db"
+#TypesDB     "@prefix@/share/@PACKAGE_NAME@/types.db"
 #Interval     10
 #ReadThreads  5
 
@@ -31,9 +31,12 @@ FQDNLookup   true
 @BUILD_PLUGIN_APPLE_SENSORS_TRUE@LoadPlugin apple_sensors
 @BUILD_PLUGIN_ASCENT_TRUE@LoadPlugin ascent
 @BUILD_PLUGIN_BATTERY_TRUE@LoadPlugin battery
+@BUILD_PLUGIN_BIND_TRUE@LoadPlugin bind
 @BUILD_PLUGIN_CPU_TRUE@LoadPlugin cpu
 @BUILD_PLUGIN_CPUFREQ_TRUE@LoadPlugin cpufreq
 @BUILD_PLUGIN_CSV_TRUE@LoadPlugin csv
+@BUILD_PLUGIN_CURL_TRUE@LoadPlugin curl
+@BUILD_PLUGIN_DBI_TRUE@LoadPlugin dbi
 @BUILD_PLUGIN_DF_TRUE@LoadPlugin df
 @BUILD_PLUGIN_DISK_TRUE@LoadPlugin disk
 @BUILD_PLUGIN_DNS_TRUE@LoadPlugin dns
@@ -63,11 +66,14 @@ FQDNLookup   true
 @BUILD_PLUGIN_NTPD_TRUE@LoadPlugin ntpd
 @BUILD_PLUGIN_NUT_TRUE@LoadPlugin nut
 @BUILD_PLUGIN_ONEWIRE_TRUE@LoadPlugin onewire
+@BUILD_PLUGIN_OPENVPN_TRUE@LoadPlugin openvpn
+@BUILD_PLUGIN_ORACLE_TRUE@LoadPlugin oracle
 @BUILD_PLUGIN_PERL_TRUE@LoadPlugin perl
 @BUILD_PLUGIN_PING_TRUE@LoadPlugin ping
 @BUILD_PLUGIN_POSTGRESQL_TRUE@LoadPlugin postgresql
 @BUILD_PLUGIN_POWERDNS_TRUE@LoadPlugin powerdns
 @BUILD_PLUGIN_PROCESSES_TRUE@LoadPlugin processes
+@BUILD_PLUGIN_RRDCACHED_TRUE@LoadPlugin rrdcached
 @BUILD_PLUGIN_RRDTOOL_TRUE@LoadPlugin rrdtool
 @BUILD_PLUGIN_SENSORS_TRUE@LoadPlugin sensors
 @BUILD_PLUGIN_SERIAL_TRUE@LoadPlugin serial
@@ -80,7 +86,7 @@ FQDNLookup   true
 @BUILD_PLUGIN_THERMAL_TRUE@LoadPlugin thermal
 @BUILD_PLUGIN_UNIXSOCK_TRUE@LoadPlugin unixsock
 @BUILD_PLUGIN_USERS_TRUE@LoadPlugin users
-@BUILD_PLUGIN_UUID_TRUE@LoadPlugin uuid
+#LoadPlugin uuid
 @BUILD_PLUGIN_VMEM_TRUE@LoadPlugin vmem
 @BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver
 @BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless
@@ -105,11 +111,65 @@ FQDNLookup   true
 #      CACert "/etc/ssl/ca.crt"
 #</Plugin>
 
+#<Plugin "bind">
+#  URL "http://localhost:8053/"
+#  OpCodes         true
+#  QTypes          true
+#
+#  ServerStats     true
+#  ZoneMaintStats  true
+#  ResolverStats   false
+#  MemoryStats     true
+#
+#  <View "_default">
+#    QTypes        true
+#    ResolverStats true
+#    CacheRRSets   true
+#
+#    Zone "127.in-addr.arpa/IN"
+#  </View>
+#</Plugin>
+
 #<Plugin csv>
 #      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/csv"
 #      StoreRates false
 #</Plugin>
 
+#<Plugin curl>
+#  <Page "stock_quotes">
+#    URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+#    User "foo"
+#    Password "bar"
+#    <Match>
+#      Regex "<span +class=\"pr\"[^>]*> *([0-9]*\\.[0-9]+) *</span>"
+#      DSType "GaugeAverage"
+#      Type "stock_value"
+#      Instance "AMD"
+#    </Match>
+#  </Page>
+#</Plugin>
+
+#<Plugin dbi>
+#      <Query "num_of_customers">
+#              Statement "SELECT 'customers' AS c_key, COUNT(*) AS c_value FROM customers_tbl"
+#              <Result>
+#                      Type "gauge"
+#                      InstancesFrom "c_key"
+#                      ValuesFrom "c_value"
+#              </Result>
+#      </Query>
+#      <Database "customers_db">
+#              Driver "mysql"
+#              DriverOption "host" "localhost"
+#              DriverOption "username" "collectd"
+#              DriverOption "password" "AeXohy0O"
+#              DriverOption "dbname" "custdb0"
+#              #SelectDB "custdb0"
+#              Query "num_of_customers"
+#              #Query "..."
+#      </Database>
+#</Plugin>
+
 #<Plugin df>
 #      Device "/dev/hda1"
 #      Device "192.168.0.2:/mnt/nfs"
@@ -146,6 +206,7 @@ FQDNLookup   true
 #              Name "*.conf"
 #              MTime "-5m"
 #              Size "+10k"
+#              Recursive true
 #      </Directory>
 #</Plugin>
 
@@ -258,6 +319,27 @@ FQDNLookup   true
 #      IgnoreSelected false
 #</Plugin>
 
+#<Plugin openvpn>
+#      StatusFile "/etc/openvpn/openvpn-status.log"
+#</Plugin>
+
+#<Plugin oracle>
+#  <Query "out_of_stock">
+#    Statement "SELECT category, COUNT(*) AS value FROM products WHERE in_stock = 0 GROUP BY category"
+#    <Result>
+#      Type "gauge"
+#      InstancesFrom "category"
+#      ValuesFrom "value"
+#    </Result>
+#  </Query>
+#  <Database "product_information">
+#    ConnectID "db01"
+#    Username "oracle"
+#    Password "secret"
+#    Query "out_of_stock"
+#  </Database>
+#</Plugin>
+
 #<Plugin perl>
 #      IncludeDir "/my/include/path"
 #      BaseName "Collectd::Plugin"
@@ -277,26 +359,41 @@ FQDNLookup   true
 
 #<Plugin postgresql>
 #      <Query magic>
-#              Query "SELECT magic, spells FROM wizard WHERE host = $1;"
+#              Statement "SELECT magic FROM wizard WHERE host = $1;"
 #              Param hostname
-#              Column gauge magic
-#              Column counter spells
+#              <Result>
+#                      Type gauge
+#                      InstancePrefix "magic"
+#                      ValuesFrom magic
+#              </Result>
+#      </Query>
+#      <Query rt36_tickets>
+#              Statement "SELECT COUNT(type) AS count, type \
+#                                FROM (SELECT CASE \
+#                                             WHEN resolved = 'epoch' THEN 'open' \
+#                                             ELSE 'resolved' END AS type \
+#                                             FROM tickets) type \
+#                                GROUP BY type;"
+#              <Result>
+#                      Type counter
+#                      InstancePrefix "rt36_tickets"
+#                      InstancesFrom "type"
+#                      ValuesFrom "count"
+#              </Result>
 #      </Query>
-#
 #      <Database foo>
 #              Host "hostname"
-#              Port 5432
+#              Port "5432"
 #              User "username"
 #              Password "secret"
-#
 #              SSLMode "prefer"
 #              KRBSrvName "kerberos_service_name"
-#
 #              Query magic
 #      </Database>
-#
 #      <Database bar>
 #              Service "service_name"
+#              Query backend # predefined
+#              Query rt36_tickets
 #      </Database>
 #</Plugin>
 
@@ -318,6 +415,13 @@ FQDNLookup   true
 #      Process "name"
 #</Plugin>
 
+#<Plugin rrdcached>
+#      DaemonAddress "unix:/tmp/rrdcached.sock"
+#      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/rrd"
+#      CreateFiles true
+#      CollectStatistics true
+#</Plugin>
+
 #<Plugin rrdtool>
 #      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/rrd"
 #      CacheTimeout 120
@@ -423,3 +527,27 @@ FQDNLookup   true
 #      Verbose false
 #</Plugin>
 
+# * * * * * * * * * * * * *
+# * FILTER CONFIGURATION  *
+# * * * * * * * * * * * * *
+
+# The following configures collectd's filtering mechanism. Before changing
+# anything in this section, please read the `FILTER CONFIGURATION' section in
+# the collectd.conf(5) manual page.
+
+# Load required matches:
+#@BUILD_PLUGIN_MATCH_REGEX_TRUE@LoadPlugin match_regex
+#@BUILD_PLUGIN_MATCH_VALUE_TRUE@LoadPlugin match_value
+#@BUILD_PLUGIN_MATCH_TIMEDIFF_TRUE@LoadPlugin match_timediff
+
+# Load required targets:
+#@BUILD_PLUGIN_TARGET_NOTIFICATION_TRUE@LoadPlugin target_notification
+#@BUILD_PLUGIN_TARGET_REPLACE_TRUE@LoadPlugin target_replace
+#@BUILD_PLUGIN_TARGET_SET_TRUE@LoadPlugin target_set
+# The following block demonstrates the default behavior if no filtering is
+# configured at all: All values will be sent to all available write plugins.
+
+#<Chain "Main">
+#  Target "write"
+#</Chain>
index 895a443..43946bd 100644 (file)
@@ -126,6 +126,14 @@ The sample config file that is installed with C<makeE<nbsp>install> includes a
 line which sets this option, though, so that default installations will have
 this setting enabled.
 
+=item B<PreCacheChain> I<ChainName>
+
+=item B<PostCacheChain> I<ChainName>
+
+Configure the name of the "pre-cache chain" and the "post-cache chain". Please
+see L<FILTER CONFIGURATION> below on information on chains and how these
+setting change the daemon's behavior.
+
 =back
 
 =head1 PLUGIN OPTIONS
@@ -257,6 +265,150 @@ and are checked by default depends on the distribution you use.
 
 =back
 
+=head2 Plugin C<bind>
+
+Starting with BIND 9.5.0, the most widely used DNS server software provides
+extensive statistics about queries, responses and lots of other information.
+The bind plugin retrieves this information that's encoded in XML and provided
+via HTTP and submits the values to collectd.
+
+To use this plugin, you first need to tell BIND to make this information
+available. This is done with the C<statistics-channels> configuration option:
+
+ statistics-channels {
+   inet localhost port 8053;
+ };
+
+The configuration follows the grouping that can be seen when looking at the
+data with an XSLT compatible viewer, such as a modern web browser. It's
+probably a good idea to make yourself familiar with the provided values, so you
+can understand what the collected statistics actually mean.
+
+Synopsis:
+
+ <Plugin "bind">
+   URL "http://localhost:8053/"
+   OpCodes         true
+   QTypes          true
+   ServerStats     true
+   ZoneMaintStats  true
+   ResolverStats   false
+   MemoryStats     true
+   <View "_default">
+     QTypes        true
+     ResolverStats true
+     CacheRRSets   true
+     Zone "127.in-addr.arpa/IN"
+   </View>
+ </Plugin>
+
+The bind plugin accepts the following configuration options:
+
+=over 4
+
+=item B<URL> I<URL>
+
+URL from which to retrieve the XML data. If not specified,
+C<http://localhost:8053/> will be used.
+
+=item B<OpCodes> I<true>|I<false>
+
+When enabled, statistics about the I<"OpCodes">, for example the number of
+C<QUERY> packets, are collected.
+
+Default: Enabled.
+
+=item B<QTypes> I<true>|I<false>
+
+When enabled, the number of I<incoming> queries by query types (for example
+C<A>, C<MX>, C<AAAA>) is collected.
+
+Default: Enabled.
+
+=item B<ServerStats> I<true>|I<false>
+
+Collect global server statistics, such as requests received over IPv4 and IPv6,
+successful queries, and failed updates.
+
+Default: Enabled.
+
+=item B<ZoneMaintStats> I<true>|I<false>
+
+Collect zone maintenance statistics, mostly information about notifications
+(zone updates) and zone transfers.
+
+Default: Enabled.
+
+=item B<ResolverStats> I<true>|I<false>
+
+Collect resolver statistics, i.E<nbsp>e. statistics about outgoing requests
+(e.E<nbsp>g. queries over IPv4, lame servers). Since the global resolver
+counters apparently were removed in BIND 9.5.1 and 9.6.0, this is disabled by
+default. Use the B<ResolverStats> option within a B<View "_default"> block
+instead for the same functionality.
+
+Default: Disabled.
+
+=item B<MemoryStats>
+
+Collect global memory statistics.
+
+Default: Enabled.
+
+=item B<View> I<Name>
+
+Collect statistics about a specific I<"view">. BIND can behave different,
+mostly depending on the source IP-address of the request. These different
+configurations are called "views". If you don't use this feature, you most
+likely are only interested in the C<_default> view.
+
+Within a E<lt>B<View>E<nbsp>I<name>E<gt> block, you can specify which
+information you want to collect about a view. If no B<View> block is
+configured, no detailed view statistics will be collected.
+
+=over 4
+
+=item B<QTypes> I<true>|I<false>
+
+If enabled, the number of I<outgoing> queries by query type (e.E<nbsp>g. C<A>,
+C<MX>) is collected.
+
+Default: Enabled.
+
+=item B<ResolverStats> I<true>|I<false>
+
+Collect resolver statistics, i.E<nbsp>e. statistics about outgoing requests
+(e.E<nbsp>g. queries over IPv4, lame servers).
+
+Default: Enabled.
+
+=item B<CacheRRSets> I<true>|I<false>
+
+If enabled, the number of entries (I<"RR sets">) in the view's cache by query
+type is collected. Negative entries (queries which resulted in an error, for
+example names that do not exist) are reported with a leading exclamation mark,
+e.E<nbsp>g. "!A".
+
+Default: Enabled.
+
+=item B<Zone> I<Name>
+
+When given, collect detailed information about the given zone in the view. The
+information collected if very similar to the global B<ServerStats> information
+(see above).
+
+You can repeat this option to collect detailed information about multiple
+zones.
+
+By default no detailed zone information is collected.
+
+=back
+
+=back
+
 =head2 Plugin C<cpufreq>
 
 This plugin doesn't have any options. It reads
@@ -273,6 +425,9 @@ installed and an "cpu governor" (that's a kernel module) is loaded.
 
 Set the directory to store CSV-files under. Per default CSV-files are generated
 beneath the daemon's working directory, i.E<nbsp>e. the B<BaseDir>.
+The special strings B<stdout> and B<stderr> can be used to write to the standard
+output and standard error channels, respectively. This, of course, only makes
+much sense when collectd is running in foreground- or non-daemon-mode.
 
 =item B<StoreRates> B<true|false>
 
@@ -282,6 +437,316 @@ number.
 
 =back
 
+=head2 Plugin C<curl>
+
+The curl plugin uses the B<libcurl> (L<http://curl.haxx.se/>) to read web pages
+and the match infrastructure (the same code used by the tail plugin) to use
+regular expressions with the received data.
+
+The following example will read the current value of AMD stock from google's
+finance page and dispatch the value to collectd.
+
+  <Plugin curl>
+    <Page "stock_quotes">
+      URL "http://finance.google.com/finance?q=NYSE%3AAMD"
+      User "foo"
+      Password "bar"
+      <Match>
+        Regex "<span +class=\"pr\"[^>]*> *([0-9]*\\.[0-9]+) *</span>"
+        DSType "GaugeAverage"
+        # Note: `stock_value' is not a standard type.
+        Type "stock_value"
+        Instance "AMD"
+      </Match>
+    </Page>
+  </Plugin>
+
+In the B<Plugin> block, there may be one or more B<Page> blocks, each defining
+a web page and one or more "matches" to be performed on the returned data. The
+string argument to the B<Page> block is used as plugin instance.
+
+The following options are valid within B<Page> blocks:
+
+=over 4
+
+=item B<URL> I<URL>
+
+URL of the web site to retrieve. Since a regular expression will be used to
+extract information from this data, non-binary data is a big plus here ;)
+
+=item B<User> I<Name>
+
+Username to use if authorization is required to read the page.
+
+=item B<Password> I<Password>
+
+Password to use if authorization is required to read the page.
+
+=item B<VerifyPeer> B<true>|B<false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true>|B<false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks if
+the C<Common Name> or a C<Subject Alternate Name> field of the SSL certificate
+matches the host name provided by the B<URL> option. If this identity check
+fails, the connection is aborted. Obviously, only works when connecting to a
+SSL enabled server. Enabled by default.
+
+=item B<CACert> I<file>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=item B<E<lt>MatchE<gt>>
+
+One or more B<Match> blocks that define how to match information in the data
+returned by C<libcurl>. The C<curl> plugin uses the same infrastructure that's
+used by the C<tail> plugin, so please see the documentation of the C<tail>
+plugin below on how matches are defined.
+
+=back
+
+=head2 Plugin C<dbi>
+
+This plugin uses the B<dbi> library (L<http://libdbi.sourceforge.net/>) to
+connect to various databases, execute I<SQL> statements and read back the
+results. I<dbi> is an acronym for "database interface" in case you were
+wondering about the name. You can configure how each column is to be
+interpreted and the plugin will generate one or more data sets from each row
+returned according to these rules.
+
+Because the plugin is very generic, the configuration is a little more complex
+than those of other plugins. It usually looks something like this:
+
+  <Plugin dbi>
+    <Query "out_of_stock">
+      Statement "SELECT category, COUNT(*) AS value FROM products WHERE in_stock = 0 GROUP BY category"
+      # Use with MySQL 5.0.0 or later
+      MinVersion 50000
+      <Result>
+        Type "gauge"
+        InstancePrefix "out_of_stock"
+        InstancesFrom "category"
+        ValuesFrom "value"
+      </Result>
+    </Query>
+    <Database "product_information">
+      Driver "mysql"
+      DriverOption "host" "localhost"
+      DriverOption "username" "collectd"
+      DriverOption "password" "aZo6daiw"
+      DriverOption "dbname" "prod_info"
+      SelectDB "prod_info"
+      Query "out_of_stock"
+    </Database>
+  </Plugin>
+
+The configuration above defines one query with one result and one database. The
+query is then linked to the database with the B<Query> option I<within> the
+B<E<lt>DatabaseE<gt>> block. You can have any number of queries and databases
+and you can also use the B<Include> statement to split up the configuration
+file in multiple, smaller files. However, the B<E<lt>QueryE<gt>> block I<must>
+precede the B<E<lt>DatabaseE<gt>> blocks, because the file is interpreted from
+top to bottom!
+
+The following is a complete list of options:
+
+=head3 B<Query> blocks
+
+Query blocks define I<SQL> statements and how the returned data should be
+interpreted. They are identified by the name that is given in the opening line
+of the block. Thus the name needs to be unique. Other than that, the name is
+not used in collectd. 
+
+In each B<Query> block, there is one or more B<Result> blocks. B<Result> blocks
+define which column holds which value or instance information. You can use
+multiple B<Result> blocks to create multiple values from one returned row. This
+is especially useful, when queries take a long time and sending almost the same
+query again and again is not desirable.
+
+Example:
+
+  <Query "environment">
+    Statement "select station, temperature, humidity from environment"
+    <Result>
+      Type "temperature"
+      # InstancePrefix "foo"
+      InstancesFrom "station"
+      ValuesFrom "temperature"
+    </Result>
+    <Result>
+      Type "humidity"
+      InstancesFrom "station"
+      ValuesFrom "humidity"
+    </Result>
+  </Query>
+
+The following options are accepted:
+
+=over 4
+
+=item B<Statement> I<SQL>
+
+Sets the statement that should be executed on the server. This is B<not>
+interpreted by collectd, but simply passed to the database server. Therefore,
+the SQL dialect that's used depends on the server collectd is connected to.
+
+The query has to return at least two columns, one for the instance and one
+value. You cannot omit the instance, even if the statement is guaranteed to
+always return exactly one line. In that case, you can usually specify something
+like this:
+
+  Statement "SELECT \"instance\", COUNT(*) AS value FROM table"
+
+(That works with MySQL but may not be valid SQL according to the spec. If you
+use a more strict database server, you may have to select from a dummy table or
+something.)
+
+Please note that some databases, for example B<Oracle>, will fail if you
+include a semicolon at the end of the statement.
+
+=item B<MinVersion> I<Version>
+
+=item B<MaxVersion> I<Value>
+
+Only use this query for the specified database version. You can use these
+options to provide multiple queries with the same name but with a slightly
+different syntax. The plugin will use only those queries, where the specified
+minimum and maximum versions fit the version of the database in use.
+
+The database version is determined by C<dbi_conn_get_engine_version>, see the 
+L<libdbi documentation|http://libdbi.sourceforge.net/docs/programmers-guide/reference-conn.html#DBI-CONN-GET-ENGINE-VERSION>
+for details. Basically, each part of the version is assumed to be in the range
+from B<00> to B<99> and all dots are removed. So version "4.1.2" becomes
+"40102", version "5.0.42" becomes "50042".
+
+B<Warning:> The plugin will use B<all> matching queries, so if you specify
+multiple queries with the same name and B<overlapping> ranges, weird stuff will
+happen. Don't to it! A valid example would be something along these lines:
+
+  MinVersion 40000
+  MaxVersion 49999
+  ...
+  MinVersion 50000
+  MaxVersion 50099
+  ...
+  MinVersion 50100
+  # No maximum
+
+In the above example, there are three ranges that don't overlap. The last one
+goes from version "5.1.0" to infinity, meaning "all later versions". Versions
+before "4.0.0" are not specified.
+
+=item B<Type> I<Type>
+
+The B<type> that's used for each line returned. See L<types.db(5)> for more
+details on how types are defined. In short: A type is a predefined layout of
+data and the number of values and type of values has to match the type
+definition.
+
+If you specify "temperature" here, you need exactly one gauge column. If you
+specify "if_octets", you will need two counter columns. See the B<ValuesFrom>
+setting below.
+
+There must be exactly one B<Type> option inside each B<Result> block.
+
+=item B<InstancePrefix> I<prefix>
+
+Prepends I<prefix> to the type instance. If B<InstancesFrom> (see below) is not
+given, the string is simply copied. If B<InstancesFrom> is given, I<prefix> and
+all strings returned in the appropriate columns are concatenated together,
+separated by dashes I<("-")>.
+
+=item B<InstancesFrom> I<column0> [I<column1> ...]
+
+Specifies the columns whose values will be used to create the "type-instance"
+for each row. If you specify more than one column, the value of all columns
+will be join together with the dashes I<("-")> as separation character.
+
+The plugin itself does not check whether or not all built instances are
+different. It's your responsibility to assure that each is unique. This is
+especially true, if you do not specify B<InstancesFrom>: B<You> have to make
+sure that only one row is returned in this case.
+
+If neither B<InstancePrefix> nor B<InstancesFrom> is given, the type-instance
+will be empty.
+
+=item B<ValuesFrom> I<column0> [I<column1> ...]
+
+Names the columns whose content is used as the actual data for the data sets
+that are dispatched to the daemon. How many such columns you need is determined
+by the B<Type> setting above. If you specify too many or not enough columns,
+the plugin will complain about that and no data will be submitted to the
+daemon.
+
+The actual data type in the columns is not that important. The plugin will
+automatically cast the values to the right type if it know how to do that. So
+it should be able to handle integer an floating point types, as well as strings
+(if they include a number at the beginning).
+
+There must be at least one B<ValuesFrom> option inside each B<Result> block.
+
+=back
+
+=head3 B<Database> blocks
+
+Database blocks define a connection to a database and which queries should be
+sent to that database. Since the used "dbi" library can handle a wide variety
+of databases, the configuration is very generic. If in doubt, refer to libdbi's
+documentationE<nbsp>- we stick as close to the terminology used there.
+
+Each database needs a "name" as string argument in the starting tag of the
+block. This name will be used as "PluginInstance" in the values submitted to
+the daemon. Other than that, that name is not used.
+
+=over 4
+
+=item B<Driver> I<Driver>
+
+Specifies the driver to use to connect to the database. In many cases those
+drivers are named after the database they can connect to, but this is not a
+technical necessity. These drivers are sometimes referred to as "DBD",
+B<D>ataB<B>ase B<D>river, and some distributions ship them in separate
+packages. Drivers for the "dbi" library are developed by the B<libdbi-drivers>
+project at L<http://libdbi-drivers.sourceforge.net/>.
+
+You need to give the driver name as expected by the "dbi" library here. You
+should be able to find that in the documentation for each driver. If you
+mistype the driver name, the plugin will dump a list of all known driver names
+to the log.
+
+=item B<DriverOption> I<Key> I<Value>
+
+Sets driver-specific options. What option a driver supports can be found in the
+documentation for each driver, somewhere at
+L<http://libdbi-drivers.sourceforge.net/>. However, the options "host",
+"username", "password", and "dbname" seem to be deE<nbsp>facto standards.
+
+Unfortunately, drivers are not too keen to report errors when an unknown option
+is passed to them, so invalid settings here may go unnoticed. This is not the
+plugin's fault, it will report errors if it gets them from the libraryE<nbsp>/
+the driver. If a driver complains about an option, the plugin will dump a
+complete list of all options understood by that driver to the log.
+
+=item B<SelectDB> I<Database>
+
+In some cases, the database name you connect with is not the database name you
+want to use for querying data. If this option is set, the plugin will "select"
+(switch to) that database after the connection is established.
+
+=item B<Query> I<QueryName>
+
+Associates the query named I<QueryName> with this database connection. The
+query needs to be defined I<before> this statement, i.E<nbsp>e. all query
+blocks you want to refer to must be placed above the database block you want to
+refer to them from.
+
+=back
+
 =head2 Plugin C<df>
 
 =over 4
@@ -491,6 +956,10 @@ description see above. Valid multipliers here are C<b> (byte), C<k> (kilobyte),
 C<m> (megabyte), C<g> (gigabyte), C<t> (terabyte), and C<p> (petabyte). Please
 note that there are 1000 bytes in a kilobyte, not 1024.
 
+=item B<Recursive> I<true>|I<false>
+
+Controls whether or not to recurse into subdirectories. Enabled by default.
+
 =back
 
 =head2 Plugin C<hddtemp>
@@ -719,6 +1188,10 @@ Prefix all lines printed by the current time. Defaults to B<true>.
 
 =back
 
+B<Note>: There is no need to notify the daemon after moving or removing the
+log file (e.E<nbsp>g. when rotating the logs). The plugin reopens the file
+for each line it writes.
+
 =head2 Plugin C<mbmon>
 
 The C<mbmon plugin> uses mbmon to retrieve temperature, voltage, etc.
@@ -799,6 +1272,23 @@ Password needed to log into the database.
 Select this database. Defaults to I<no database> which is a perfectly reasonable
 option for what this plugin does.
 
+=item B<Port> I<Port>
+
+TCP-port to connect to. The port must be specified in its numeric form, but it
+must be passed as a string nonetheless. For example:
+
+  Port "3306"
+
+If B<Host> is set to B<localhost> (the default), this setting has no effect.
+See the documentation for the C<mysql_real_connect> function for details.
+
+=item B<Socket> I<Socket>
+
+Specifies the path to the UNIX domain socket of the MySQL server. This option
+only has any effect, if B<Host> is set to B<localhost> (the default).
+Otherwise, use the B<Port> option above. See the documentation for the
+C<mysql_real_connect> function for details.
+
 =back
 
 =head2 Plugin C<netlink>
@@ -1151,11 +1641,99 @@ controller, but as soon as you throw in a couple more senors and maybe a hub
 or two, reading all values will take more than ten seconds (the default
 interval). We will probably add some separate thread for reading the sensors
 and some cache or something like that, but it's not done yet. We will try to
-maintain backwards compatibility in the future, but we can't probmise. So in
-short: If it works for you: Great! But kaap in mind that the config I<might>
+maintain backwards compatibility in the future, but we can't promise. So in
+short: If it works for you: Great! But keep in mind that the config I<might>
 change, though this is unlikely. Oh, and if you want to help improving this
 plugin, just send a short notice to the mailing list. ThanksE<nbsp>:)
 
+=head2 Plugin C<openvpn>
+
+The OpenVPN plugin reads a status file maintained by OpenVPN and gathers
+traffic statistics about connected clients.
+
+To set up OpenVPN to write to the status file periodically, use the
+B<--status> option of OpenVPN. Since OpenVPN can write two different formats,
+you need to set the required format, too. This is done by setting
+B<--status-version> to B<2>.
+
+So, in a nutshell you need:
+
+  openvpn $OTHER_OPTIONS \
+    --status "/var/run/openvpn-status" 10 \
+    --status-version 2
+
+Available options:
+
+=over 4
+
+=item B<StatusFile> I<File>
+
+Specifies the location of the status file.
+
+=back
+
+=head2 Plugin C<oracle>
+
+The "oracle" plugin uses the Oracle® Call Interface I<(OCI)> to connect to an
+Oracle® Database and lets you execute SQL statements there. It is very similar
+to the "dbi" plugin, because it was written around the same time. See the "dbi"
+plugin's documentation above for details.
+
+  <Plugin oracle>
+    <Query "out_of_stock">
+      Statement "SELECT category, COUNT(*) AS value FROM products WHERE in_stock = 0 GROUP BY category"
+      <Result>
+        Type "gauge"
+        # InstancePrefix "foo"
+        InstancesFrom "category"
+        ValuesFrom "value"
+      </Result>
+    </Query>
+    <Database "product_information">
+      ConnectID "db01"
+      Username "oracle"
+      Password "secret"
+      Query "out_of_stock"
+    </Database>
+  </Plugin>
+
+=head3 B<Query> blocks
+
+The Query blocks are handled identically to the Query blocks of the "dbi"
+plugin. Please see its documentation above for details on how to specify
+queries.
+
+=head3 B<Database> blocks
+
+Database blocks define a connection to a database and which queries should be
+sent to that database. Each database needs a "name" as string argument in the
+starting tag of the block. This name will be used as "PluginInstance" in the
+values submitted to the daemon. Other than that, that name is not used.
+
+=over 4
+
+=item B<ConnectID> I<ID>
+
+Defines the "database alias" or "service name" to connect to. Usually, these
+names are defined in the file named C<$ORACLE_HOME/network/admin/tnsnames.ora>.
+
+=item B<Username> I<Username>
+
+Username used for authentication.
+
+=item B<Password> I<Password>
+
+Password used for authentication.
+
+=item B<Query> I<QueryName>
+
+Associates the query named I<QueryName> with this database connection. The
+query needs to be defined I<before> this statement, i.E<nbsp>e. all query
+blocks you want to refer to must be placed above the database block you want to
+refer to them from.
+
+=back
+
 =head2 Plugin C<perl>
 
 This plugin embeds a Perl-interpreter into collectd and provides an interface
@@ -1199,10 +1777,28 @@ L<http://www.postgresql.org/docs/manuals/>.
 
   <Plugin postgresql>
     <Query magic>
-      Query "SELECT magic, spells FROM wizard WHERE host = $1;"
+      Statement "SELECT magic FROM wizard WHERE host = $1;"
       Param hostname
-      Column gauge magic
-      Column counter spells
+      <Result>
+        Type gauge
+        InstancePrefix "magic"
+        ValuesFrom magic
+      </Result>
+    </Query>
+
+    <Query rt36_tickets>
+      Statement "SELECT COUNT(type) AS count, type \
+                        FROM (SELECT CASE \
+                                     WHEN resolved = 'epoch' THEN 'open' \
+                                     ELSE 'resolved' END AS type \
+                                     FROM tickets) type \
+                        GROUP BY type;"
+      <Result>
+        Type counter
+        InstancePrefix "rt36_tickets"
+        InstancesFrom "type"
+        ValuesFrom "count"
+      </Result>
     </Query>
 
     <Database foo>
@@ -1214,23 +1810,33 @@ L<http://www.postgresql.org/docs/manuals/>.
       KRBSrvName "kerberos_service_name"
       Query magic
     </Database>
+
     <Database bar>
       Service "service_name"
+      Query backend # predefined
+      Query rt36_tickets
     </Database>
   </Plugin>
 
 The B<Query> block defines one database query which may later be used by a
 database definition. It accepts a single mandatory argument which specifies
-the name of the query. The names of all queries have to be unique. The
-following configuration options are available to define the query:
+the name of the query. The names of all queries have to be unique (see the
+B<MinPGVersion> and B<MaxPGVersion> options below for an exception to this
+rule). The following configuration options are available to define the query:
+
+In each B<Query> block, there is one or more B<Result> blocks. B<Result>
+blocks define how to handle the values returned from the query. They define
+which column holds which value and how to dispatch that value to the daemon.
+Multiple B<Result> blocks may be used to extract multiple values from a single
+query.
 
 =over 4
 
-=item B<Query> I<sql query>
+=item B<Statement> I<sql query statement>
 
-Specify the I<sql query> which the plugin should execute. The string may
-contain the tokens B<$1>, B<$2>, etc. which are used to reference the first,
-second, etc. parameter. The value of the parameters is specified by the
+Specify the I<sql query statement> which the plugin should execute. The string
+may contain the tokens B<$1>, B<$2>, etc. which are used to reference the
+first, second, etc. parameter. The value of the parameters is specified by the
 B<Param> configuration option - see below for details. To include a literal
 B<$> character followed by a number, surround it with single quotes (B<'>).
 
@@ -1238,7 +1844,14 @@ Any SQL command which may return data (such as C<SELECT> or C<SHOW>) is
 allowed. Note, however, that only a single command may be used. Semicolons are
 allowed as long as a single non-empty command has been specified only.
 
-=item B<Param> I<hostname>|I<database>|I<username>
+The returned lines will be handled separately one after another.
+
+=item B<Query> I<sql query statement>
+
+This is a deprecated synonym for B<Statement>. It will be removed in version 5
+of collectd.
+
+=item B<Param> I<hostname>|I<database>|I<username>|I<interval>
 
 Specify the parameters which should be passed to the SQL query. The parameters
 are referred to in the SQL query as B<$1>, B<$2>, etc. in the same order as
@@ -1260,17 +1873,72 @@ The name of the database of the current connection.
 
 The username used to connect to the database.
 
+=item I<interval>
+
+The interval collectd is using (as specified by the B<Interval> option).
+
 =back
 
 Please note that parameters are only supported by PostgreSQL's protocol
 version 3 and above which was introduced in version 7.4 of PostgreSQL.
 
+=item B<Type> I<type>
+
+The I<type> name to be used when dispatching the values. The type describes
+how to handle the data and where to store it. See L<types.db(5)> for more
+details on types and their configuration. The number and type of values (as
+selected by the B<ValuesFrom> option) has to match the type of the given name.
+
+This option is required inside a B<Result> block.
+
+=item B<InstancePrefix> I<prefix>
+
+=item B<InstancesFrom> I<column0> [I<column1> ...]
+
+Specify how to create the "TypeInstance" for each data set (i.E<nbsp>e. line).
+B<InstancePrefix> defines a static prefix that will be prepended to all type
+instances. B<InstancesFrom> defines the column names whose values will be used
+to create the type instance. Multiple values will be joined together using the
+hyphen (C<->) as separation character.
+
+The plugin itself does not check whether or not all built instances are
+different. It is your responsibility to assure that each is unique.
+
+Both options are optional. If none is specified, the type instance will be
+empty.
+
+=item B<ValuesFrom> I<column0> [I<column1> ...]
+
+Names the columns whose content is used as the actual data for the data sets
+that are dispatched to the daemon. How many such columns you need is
+determined by the B<Type> setting as explained above. If you specify too many
+or not enough columns, the plugin will complain about that and no data will be
+submitted to the daemon.
+
+The actual data type, as seen by PostgreSQL, is not that important as long as
+it represents numbers. The plugin will automatically cast the values to the
+right type if it know how to do that. For that, it uses the L<strtoll(3)> and
+L<strtod(3)> functions, so anything supported by those functions is supported
+by the plugin as well.
+
+This option is required inside a B<Result> block and may be specified multiple
+times. If multiple B<ValuesFrom> options are specified, the columns are read
+in the given order.
+
 =item B<Column> I<type> [I<type instance>]
 
-Specify the I<type> and optional I<type instance> used to dispatch the value
-of each result column. Detailed information about types and their
-configuration can be found in L<types.db(5)>. The number and order of the
-B<Column> options has to match the columns of the query result.
+This is a deprecated alternative to a B<Result> block. It will be removed in
+version 5 of collectd. It is equivalent to the following B<Result> block:
+
+  <Result>
+    Type I<type>
+    InstancePrefix I<type instance>
+    ValuesFrom I<name of the x. column>
+  </Result>
+
+The order of the B<Column> options defines which columns of the query result
+should be used. The first option specifies the data found in the first column,
+the second option that of the second column, and so on.
 
 =item B<MinPGVersion> I<version>
 
@@ -1555,6 +2223,65 @@ collected for these selected processes are size of the resident segment size
 (RSS), user- and system-time used, number of processes and number of threads,
 and minor and major pagefaults.
 
+=item B<ProcessMatch> I<name> I<regex>
+
+Similar to the B<Process> option this allows to select more detailed
+statistics of processes matching the specified I<regex> (see L<regex(7)> for
+details). The statistics of all matching processes are summed up and
+dispatched to the daemon using the specified I<name> as an identifier. This
+allows to "group" several processes together. I<name> must not contain
+slashes.
+
+=back
+
+=head2 Plugin C<rrdcached>
+
+The C<rrdcached> plugin uses the RRDTool accelerator daemon, L<rrdcached(1)>,
+to store values to RRD files in an efficient manner. The combination of the
+C<rrdcached> B<plugin> and the C<rrdcached> B<daemon> is very similar to the
+way the C<rrdtool> plugin works (see below). The added abstraction layer
+provides a number of benefits, though: Because the cache is not within
+C<collectd> anymore, it does not need to be flushed when C<collectd> is to be
+restarted. This results in much shorter (if any) gaps in graphs, especially
+under heavy load. Also, the C<rrdtool> command line utility is aware of the
+daemon so that it can flush values to disk automatically when needed. This
+allows to integrate automated flushing of values into graphing solutions much
+more easily.
+
+There are disadvantages, though: The daemon may reside on a different host, so
+it may not be possible for C<collectd> to create the appropriate RRD files
+anymore. And even if C<rrdcached> runs on the same host, it may run in a
+different base directory, so relative paths may do weird stuff if you're not
+careful.
+
+So the B<recommended configuration> is to let C<collectd> and C<rrdcached> run
+on the same host, communicating via a UNIX domain socket. The B<DataDir>
+setting should be set to an absolute path, so that a changed base directory
+does not result in RRD files being createdE<nbsp>/ expected in the wrong place.
+
+=over 4
+
+=item B<DaemonAddress> I<Address>
+
+Address of the daemon as understood by the C<rrdc_connect> function of the RRD
+library. See L<rrdcached(1)> for details. Example:
+
+  <Plugin "rrdcached">
+    DaemonAddress "unix:/var/run/rrdcached.sock"
+  </Plugin>
+
+=item B<DataDir> I<Directory>
+
+Set the base directory in which the RRD files reside. If this is a relative
+path, it is relative to the working base directory of the C<rrdcached> daemon!
+Use of an absolute path is recommended.
+
+=item B<CreateFiles> B<true>|B<false>
+
+Enables or disables the creation of RRD files. If the daemon is not running
+locally, or B<DataDir> is set to a relative path, this will not work as
+expected. Default is B<true>.
+
 =back
 
 =head2 Plugin C<rrdtool>
@@ -1634,7 +2361,7 @@ reduces IO-operations and thus lessens the load produced by updating the files.
 The trade off is that the graphs kind of "drag behind" and that more memory is
 used.
 
-=item B<WritesPerSecond> B<Updates>
+=item B<WritesPerSecond> I<Updates>
 
 When collecting many statistics with collectd and the C<rrdtool> plugin, you
 will run serious performance problems. The B<CacheFlush> setting and the
@@ -2005,6 +2732,12 @@ the F</proc/virtual> filesystem that is required by this plugin.
 
 The B<VServer> homepage can be found at L<http://linux-vserver.org/>.
 
+B<Note>: The traffic collected by this plugin accounts for the amount of
+traffic passing a socket which might be a lot less than the actual on-wire
+traffic (e.E<nbsp>g. due to headers and retransmission). If you want to
+collect on-wire traffic you could, for example, use the logging facilities of
+iptables to feed data for the guest IPs into the iptables plugin.
+
 =head1 THRESHOLD CONFIGURATION
 
 Starting with version C<4.3.0> collectd has support for B<monitoring>. By that
@@ -2132,6 +2865,651 @@ only one such notification is generated until the value appears again.
 
 =back
 
+=head1 FILTER CONFIGURATION
+
+Starting with collectd 4.6 there is a powerful filtering infrastructure
+implemented in the daemon. The concept has mostly been copied from
+I<ip_tables>, the packet filter infrastructure for Linux. We'll use a similar
+terminology, so that users that are familiar with iptables feel right at home.
+
+=head2 Terminology
+
+The following are the terms used in the remainder of the filter configuration
+documentation. For an ASCII-art schema of the mechanism, see
+L<"General structure"> below.
+
+=over 4
+
+=item B<Match>
+
+A I<match> is a criteria to select specific values. Examples are, of course, the
+name of the value or it's current value.
+
+Matches are implemented in plugins which you have to load prior to using the
+match. The name of such plugins starts with the "match_" prefix.
+
+=item B<Target>
+
+A I<target> is some action that is to be performed with data. Such actions
+could, for example, be to change part of the value's identifier or to ignore
+the value completely.
+
+Some of these targets are built into the daemon, see L<"Built-in targets">
+below. Other targets are implemented in plugins which you have to load prior to
+using the target. The name of such plugins starts with the "target_" prefix.
+
+=item B<Rule>
+
+The combination of any number of matches and at least one target is called a
+I<rule>. The target actions will be performed for all values for which B<all>
+matches apply. If the rule does not have any matches associated with it, the
+target action will be performed for all values.
+
+=item B<Chain>
+
+A I<chain> is a list of rules and possibly default targets. The rules are tried
+in order and if one matches, the associated target will be called. If a value
+is handled by a rule, it depends on the target whether or not any subsequent
+rules are considered or if traversal of the chain is aborted, see
+L<"Flow control"> below. After all rules have been checked, the default targets
+will be executed.
+
+=back
+
+=head2 General structure
+
+The following shows the resulting structure:
+
+ +---------+
+ ! Chain   !
+ +---------+
+      !
+      V
+ +---------+  +---------+  +---------+  +---------+
+ ! Rule    !->! Match   !->! Match   !->! Target  !
+ +---------+  +---------+  +---------+  +---------+
+      !
+      V
+ +---------+  +---------+  +---------+
+ ! Rule    !->! Target  !->! Target  !
+ +---------+  +---------+  +---------+
+      !
+      V
+      :
+      :
+      !
+      V
+ +---------+  +---------+  +---------+
+ ! Rule    !->! Match   !->! Target  !
+ +---------+  +---------+  +---------+
+      !
+      V
+ +---------+
+ ! Default !
+ ! Target  !
+ +---------+
+
+=head2 Flow control
+
+There are four ways to control which way a value takes through the filter
+mechanism:
+
+=over 4
+
+=item B<jump>
+
+The built-in B<jump> target can be used to "call" another chain, i.E<nbsp>e.
+process the value with another chain. When the called chain finishes, usually
+the next target or rule after the jump is executed.
+
+=item B<stop>
+
+The stop condition, signaled for example by the built-in target B<stop>, causes
+all processing of the value to be stopped immediately.
+
+=item B<return>
+
+Causes processing in the current chain to be aborted, but processing of the
+value generally will continue. This means that if the chain was called via
+B<Jump>, the next target or rule after the jump will be executed. If the chain
+was not called by another chain, control will be returned to the daemon and it
+may pass the value to another chain.
+
+=item B<continue>
+
+Most targets will signal the B<continue> condition, meaning that processing
+should continue normally. There is no special built-in target for this
+condition.
+
+=back
+
+=head2 Synopsis
+
+The configuration reflects this structure directly:
+
+ PostCacheChain "PostCache"
+ <Chain "PostCache">
+   <Rule "ignore_mysql_show">
+     <Match "regex">
+       Plugin "^mysql$"
+       Type "^mysql_command$"
+       TypeInstance "^show_"
+     </Match>
+     <Target "stop">
+     </Target>
+   </Rule>
+   <Target "write">
+     Plugin "rrdtool"
+   </Target>
+ </Chain>
+
+The above configuration example will ignore all values where the plugin field
+is "mysql", the type is "mysql_command" and the type instance begins with
+"show_". All other values will be sent to the C<rrdtool> write plugin via the
+default target of the chain. Since this chain is run after the value has been
+added to the cache, the MySQL C<show_*> command statistics will be available
+via the C<unixsock> plugin.
+
+=head2 List of configuration options
+
+=over 4
+
+=item B<PreCacheChain> I<ChainName>
+
+=item B<PostCacheChain> I<ChainName>
+
+Configure the name of the "pre-cache chain" and the "post-cache chain". The
+argument is the name of a I<chain> that should be executed before and/or after
+the values have been added to the cache.
+
+To understand the implications, it's important you know what is going on inside
+I<collectd>. The following diagram shows how values are passed from the
+read-plugins to the write-plugins:
+
+   +---------------+
+   !  Read-Plugin  !
+   +-------+-------+
+           !
+ + - - - - V - - - - +
+ : +---------------+ :
+ : !   Pre-Cache   ! :
+ : !     Chain     ! :
+ : +-------+-------+ :
+ :         !         :
+ :         V         :
+ : +-------+-------+ :  +---------------+
+ : !     Cache     !--->!  Value Cache  !
+ : !     insert    ! :  +---+---+-------+
+ : +-------+-------+ :      !   !
+ :         !   ,------------'   !
+ :         V   V     :          V
+ : +-------+---+---+ :  +-------+-------+
+ : !  Post-Cache   +--->! Write-Plugins !
+ : !     Chain     ! :  +---------------+
+ : +---------------+ :
+ :                   :
+ :  dispatch values  :
+ + - - - - - - - - - +
+
+After the values are passed from the "read" plugins to the dispatch functions,
+the pre-cache chain is run first. The values are added to the internal cache
+afterwards. The post-cache chain is run after the values have been added to the
+cache. So why is it such a huge deal if chains are run before or after the
+values have been added to this cache?
+
+Targets that change the identifier of a value list should be executed before
+the values are added to the cache, so that the name in the cache matches the
+name that is used in the "write" plugins. The C<unixsock> plugin, too, uses
+this cache to receive a list of all available values. If you change the
+identifier after the value list has been added to the cache, this may easily
+lead to confusion, but it's not forbidden of course.
+
+The cache is also used to convert counter values to rates. These rates are, for
+example, used by the C<value> match (see below). If you use the rate stored in
+the cache B<before> the new value is added, you will use the old, B<previous>
+rate. Write plugins may use this rate, too, see the C<csv> plugin, for example.
+The C<unixsock> plugin uses these rates too, to implement the C<GETVAL>
+command.
+
+Last but not last, the B<stop> target makes a difference: If the pre-cache
+chain returns the stop condition, the value will not be added to the cache and
+the post-cache chain will not be run.
+
+=item B<Chain> I<Name>
+
+Adds a new chain with a certain name. This name can be used to refer to a
+specific chain, for example to jump to it.
+
+Within the B<Chain> block, there can be B<Rule> blocks and B<Target> blocks.
+
+=item B<Rule> [I<Name>]
+
+Adds a new rule to the current chain. The name of the rule is optional and
+currently has no meaning for the daemon.
+
+Within the B<Rule> block, there may be any number of B<Match> blocks and there
+must be at least one B<Target> block.
+
+=item B<Match> I<Name>
+
+Adds a match to a B<Rule> block. The name specifies what kind of match should
+be performed. Available matches depend on the plugins that have been loaded.
+
+The arguments inside the B<Match> block are passed to the plugin implementing
+the match, so which arguments are valid here depends on the plugin being used.
+If you do not need any to pass any arguments to a match, you can use the
+shorter syntax:
+
+ Match "foobar"
+
+Which is equivalent to:
+
+ <Match "foobar">
+ </Match>
+
+=item B<Target> I<Name>
+
+Add a target to a rule or a default target to a chain. The name specifies what
+kind of target is to be added. Which targets are available depends on the
+plugins being loaded.
+
+The arguments inside the B<Target> block are passed to the plugin implementing
+the target, so which arguments are valid here depends on the plugin being used.
+If you do not need any to pass any arguments to a target, you can use the
+shorter syntax:
+
+ Target "stop"
+
+This is the same as writing:
+
+ <Target "stop">
+ </Target>
+
+=back
+
+=head2 Built-in targets 
+
+The following targets are built into the core daemon and therefore need no
+plugins to be loaded:
+
+=over 4
+
+=item B<return>
+
+Signals the "return" condition, see the L<"Flow control"> section above. This
+causes the current chain to stop processing the value and returns control to
+the calling chain. The calling chain will continue processing targets and rules
+just after the B<jump> target (see below). This is very similar to the
+B<RETURN> target of iptables, see L<iptables(8)>.
+
+This target does not have any options.
+
+Example:
+
+ Target "return"
+
+=item B<stop>
+
+Signals the "stop" condition, see the L<"Flow control"> section above. This
+causes processing of the value to be aborted immediately. This is similar to
+the B<DROP> target of iptables, see L<iptables(8)>.
+
+This target does not have any options.
+
+Example:
+
+ Target "stop"
+
+=item B<write>
+
+Sends the value to "write" plugins.
+
+Available options:
+
+=over 4
+
+=item B<Plugin> I<Name>
+
+Name of the write plugin to which the data should be sent. This option may be
+given multiple times to send the data to more than one write plugin.
+
+=back
+
+If no plugin is explicitly specified, the values will be sent to all available
+write plugins.
+
+Example:
+
+ <Target "write">
+   Plugin "rrdtool"
+ </Target>
+
+=item B<jump>
+
+Starts processing the rules of another chain, see L<"Flow control"> above. If
+the end of that chain is reached, or a stop condition is encountered,
+processing will continue right after the B<jump> target, i.E<nbsp>e. with the
+next target or the next rule. This is similar to the B<-j> command line option
+of iptables, see L<iptables(8)>.
+
+Available options:
+
+=over 4
+
+=item B<Chain> I<Name>
+
+Jumps to the chain I<Name>. This argument is required and may appear only once.
+
+=back
+
+Example:
+
+ <Target "jump">
+   Chain "foobar"
+ </Target>
+
+=back
+
+=head2 Available matches
+
+=over 4
+
+=item B<regex>
+
+Matches a value using regular expressions.
+
+Available options:
+
+=over 4
+
+=item B<Host> I<Regex>
+
+=item B<Plugin> I<Regex>
+
+=item B<PluginInstance> I<Regex>
+
+=item B<Type> I<Regex>
+
+=item B<TypeInstance> I<Regex>
+
+Match values where the given regular expressions match the various fields of
+the identifier of a value. If multiple regular expressions are given, B<all>
+regexen must match for a value to match.
+
+=back
+
+Example:
+
+ <Match "regex">
+   Host "customer[0-9]+"
+   Plugin "^foobar$"
+ </Match>
+
+=item B<timediff>
+
+Matches values that have a time which differs from the time on the server.
+
+This match is mainly intended for servers that receive values over the
+C<network> plugin and write them to disk using the C<rrdtool> plugin. RRDtool
+is very sensitive to the timestamp used when updating the RRD files. In
+particular, the time must be ever increasing. If a misbehaving client sends one
+packet with a timestamp far in the future, all further packets with a correct
+time will be ignored because of that one packet. What's worse, such corrupted
+RRD files are hard to fix.
+
+This match lets one match all values B<outside> a specified time range
+(relative to the server's time), so you can use the B<stop> target (see below)
+to ignore the value, for example.
+
+Available options:
+
+=over 4
+
+=item B<Future> I<Seconds>
+
+Matches all values that are I<ahead> of the server's time by I<Seconds> or more
+seconds. Set to zero for no limit. Either B<Future> or B<Past> must be
+non-zero.
+
+=item B<Past> I<Seconds>
+
+Matches all values that are I<behind> of the server's time by I<Seconds> or
+more seconds. Set to zero for no limit. Either B<Future> or B<Past> must be
+non-zero.
+
+=back
+
+Example:
+
+ <Match "timediff">
+   Future  300
+   Past   3600
+ </Match>
+
+This example matches all values that are five minutes or more ahead of the
+server or one hour (or more) lagging behind.
+
+=item B<value>
+
+Matches the actual value of data sources against given minimumE<nbsp>/ maximum
+values. If a data-set consists of more than one data-source, all data-sources
+must match the specified ranges for a positive match.
+
+Available options:
+
+=over 4
+
+=item B<Min> I<Value>
+
+Sets the smallest value which still results in a match. If unset, behaves like
+negative infinity.
+
+=item B<Max> I<Value>
+
+Sets the largest value which still results in a match. If unset, behaves like
+positive infinity.
+
+=item B<Invert> B<true>|B<false>
+
+Inverts the selection. If the B<Min> and B<Max> settings result in a match,
+no-match is returned and vice versa. Please note that the B<Invert> setting
+only effects how B<Min> and B<Max> are applied to a specific value. Especially
+the B<DataSource> and B<Satisfy> settings (see below) are not inverted.
+
+=item B<DataSource> I<DSName> [I<DSName> ...]
+
+Select one or more of the data sources. If no data source is configured, all
+data sources will be checked. If the type handled by the match does not have a
+data source of the specified name(s), this will always result in no match
+(independent of the B<Invert> setting).
+
+=item B<Satisfy> B<Any>|B<All>
+
+Specifies how checking with several data sources is performed. If set to
+B<Any>, the match succeeds if one of the data sources is in the configured
+range. If set to B<All> the match only succeeds if all data sources are within
+the configured range. Default is B<All>.
+
+Usually B<All> is used for positive matches, B<Any> is used for negative
+matches. This means that with B<All> you usually check that all values are in a
+"good" range, while with B<Any> you check if any value is within a "bad" range
+(or outside the "good" range).
+
+=back
+
+Either B<Min> or B<Max>, but not both, may be unset.
+
+Example:
+
+ # Match all values smaller than or equal to 100. Matches only if all data
+ # sources are below 100.
+ <Match "value">
+   Max 100
+   Satisfy "All"
+ </Match>
+ # Match if the value of any data source is outside the range of 0 - 100.
+ <Match "value">
+   Min   0
+   Max 100
+   Invert true
+   Satisfy "Any"
+ </Match>
+
+=back
+
+=head2 Available targets
+
+=over 4
+
+=item B<notification>
+
+Creates and dispatches a notification.
+
+Available options:
+
+=over 4
+
+=item B<Message> I<String>
+
+This required option sets the message of the notification. The following
+placeholders will be replaced by an appropriate value:
+
+=over 4
+
+=item B<%{host}>
+
+=item B<%{plugin}>
+
+=item B<%{plugin_instance}>
+
+=item B<%{type}>
+
+=item B<%{type_instance}>
+
+These placeholders are replaced by the identifier field of the same name.
+
+=item B<%{ds:>I<name>B<}>
+
+These placeholders are replaced by a (hopefully) human readable representation
+of the current rate of this data source. If you changed the instance name
+(using the B<set> or B<replace> targets, see below), it may not be possible to
+convert counter values to rates.
+
+=back
+
+Please note that these placeholders are B<case sensitive>!
+
+=item B<Severity> B<"FATAL">|B<"WARNING">|B<"OKAY">
+
+Sets the severity of the message. If omitted, the severity B<"WARNING"> is
+used.
+
+=back
+
+Example:
+
+  <Target "notification">
+    Message "Oops, the %{type_instance} temperature is currently %{ds:value}!"
+    Severity "WARNING"
+  </Target>
+
+=item B<replace>
+
+Replaces parts of the identifier using regular expressions.
+
+Available options:
+
+=over 4
+
+=item B<Host> I<Regex> I<Replacement>
+
+=item B<Plugin> I<Regex> I<Replacement>
+
+=item B<PluginInstance> I<Regex> I<Replacement>
+
+=item B<TypeInstance> I<Regex> I<Replacement>
+
+Match the appropriate field with the given regular expression I<Regex>. If the
+regular expression matches, that part that matches is replaced with
+I<Replacement>. If multiple places of the input buffer match a given regular
+expression, only the first occurrence will be replaced.
+
+You can specify each option multiple times to use multiple regular expressions
+one after another.
+
+=back
+
+Example:
+
+ <Target "replace">
+   # Replace "example.net" with "example.com"
+   Host "\\<example.net\\>" "example.com"
+   # Strip "www." from hostnames
+   Host "\\<www\\." ""
+ </Target>
+
+=item B<set>
+
+Sets part of the identifier of a value to a given string.
+
+Available options:
+
+=over 4
+
+=item B<Host> I<String>
+
+=item B<Plugin> I<String>
+
+=item B<PluginInstance> I<String>
+
+=item B<TypeInstance> I<String>
+
+Set the appropriate field to the given string. The strings for plugin instance
+and type instance may be empty, the strings for host and plugin may not be
+empty. It's currently not possible to set the type of a value this way.
+
+=back
+
+Example:
+
+ <Target "set">
+   PluginInstance "coretemp"
+   TypeInstance "core3"
+ </Target>
+
+=back
+
+=head2 Backwards compatibility
+
+If you use collectd with an old configuration, i.E<nbsp>e. one without a
+B<Chain> block, it will behave as it used to. This is equivalent to the
+following configuration:
+
+ <Chain "PostCache">
+   Target "write"
+ </Chain>
+
+If you specify a B<PostCacheChain>, the B<write> target will not be added
+anywhere and you will have to make sure that it is called where appropriate. We
+suggest to add the above snippet as default target to your "PostCache" chain.
+
+=head2 Examples
+
+Ignore all values, where the hostname does not contain a dot, i.E<nbsp>e. can't
+be an FQDN.
+
+ <Chain "PreCache">
+   <Rule "no_fqdn">
+     <Match "regex">
+       Host "^[^\.]*$"
+     </Match>
+     Target "stop"
+   </Rule>
+   Target "write"
+ </Chain>
+
 =head1 SEE ALSO
 
 L<collectd(1)>,
@@ -2140,9 +3518,11 @@ L<collectd-perl(5)>,
 L<collectd-unixsock(5)>,
 L<types.db(5)>,
 L<hddtemp(8)>,
+L<iptables(8)>,
 L<kstat(3KSTAT)>,
 L<mbmon(1)>,
 L<psql(1)>,
+L<regex(7)>,
 L<rrdtool(1)>,
 L<sensors(1)>
 
index 16bde1c..3d8998f 100644 (file)
@@ -273,6 +273,9 @@ typedef bool _Bool;
 #endif
 
 #if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
+# undef strcpy
+# undef strcat
+# undef strtok
 # pragma GCC poison strcpy strcat strtok
 #endif
 
@@ -284,6 +287,7 @@ typedef bool _Bool;
  */
 #ifndef DONT_POISON_SPRINTF_YET
 # if defined(COLLECT_DEBUG) && COLLECT_DEBUG && defined(__GNUC__) && __GNUC__
+#  undef sprintf
 #  pragma GCC poison sprintf
 # endif
 #endif
index c10ae78..e36dcdf 100644 (file)
@@ -31,6 +31,11 @@ directory.
 Test the configuration only. The program immediately exits after parsing the
 config file. A return code not equal to zero indicates an error.
 
+=item B<-T>
+
+Test the plugin read callbacks only. The program immediately exits after invoking
+the read callbacks once. A return code not equal to zero indicates an error.
+
 =item B<-P> I<E<lt>pid-fileE<gt>>
 
 Specify an alternative pid file. This overwrites any settings in the config 
index 015344f..078b2eb 100644 (file)
  *   Sebastian Harl <sh at tokkee.org>
  **/
 
+#if !defined(__GNUC__) || !__GNUC__
+# define __attribute__(x) /**/
+#endif
+
 #include "config.h"
 
 #include <assert.h>
@@ -197,13 +201,13 @@ static int collectd_stop (void)
        return 0;
 } /* collectd_stop */
 
-static void sig_int_term_handler (int signo)
+static void sig_int_term_handler (int __attribute__((unused)) signo)
 {
        ++loop;
        return;
 } /* sig_int_term_handler */
 
-static void sig_hup_handler (int signo)
+static void sig_hup_handler (int __attribute__((unused)) signo)
 {
        ++restart;
        return;
index 122f962..1af2f14 100644 (file)
@@ -18,6 +18,8 @@
  * Authors:
  *   Florian octo Forster <octo at verplant.org>
  *   Niki W. Waibel <niki.waibel@gmx.net>
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Michał Mirosław <mirq-linux at rere.qmqm.pl>
 **/
 
 #if HAVE_CONFIG_H
@@ -77,15 +79,21 @@ int ssnprintf (char *dest, size_t n, const char *format, ...)
 char *sstrdup (const char *s)
 {
        char *r;
+       size_t sz;
 
        if (s == NULL)
                return (NULL);
 
-       if((r = strdup (s)) == NULL)
+       /* Do not use `strdup' here, because it's not specified in POSIX. It's
+        * ``only'' an XSI extension. */
+       sz = strlen (s) + 1;
+       r = (char *) malloc (sizeof (char) * sz);
+       if (r == NULL)
        {
-               ERROR ("Not enough memory.");
+               ERROR ("sstrdup: Out of memory.");
                exit (3);
        }
+       memcpy (r, s, sizeof (char) * sz);
 
        return (r);
 } /* char *sstrdup */
@@ -341,28 +349,56 @@ int escape_slashes (char *buf, int buf_len)
        return (0);
 } /* int escape_slashes */
 
-int timeval_sub_timespec (struct timeval *tv0, struct timeval *tv1, struct timespec *ret)
+int timeval_cmp (struct timeval tv0, struct timeval tv1, struct timeval *delta)
 {
-       if ((tv0 == NULL) || (tv1 == NULL) || (ret == NULL))
-               return (-2);
+       struct timeval *larger;
+       struct timeval *smaller;
 
-       if ((tv0->tv_sec < tv1->tv_sec)
-                       || ((tv0->tv_sec == tv1->tv_sec) && (tv0->tv_usec < tv1->tv_usec)))
-               return (-1);
+       int status;
 
-       ret->tv_sec  = tv0->tv_sec - tv1->tv_sec;
-       ret->tv_nsec = 1000 * ((long) (tv0->tv_usec - tv1->tv_usec));
+       NORMALIZE_TIMEVAL (tv0);
+       NORMALIZE_TIMEVAL (tv1);
 
-       if (ret->tv_nsec < 0)
+       if ((tv0.tv_sec == tv1.tv_sec) && (tv0.tv_usec == tv1.tv_usec))
        {
-               assert (ret->tv_sec > 0);
+               if (delta != NULL) {
+                       delta->tv_sec  = 0;
+                       delta->tv_usec = 0;
+               }
+               return (0);
+       }
 
-               ret->tv_nsec += 1000000000;
-               ret->tv_sec  -= 1;
+       if ((tv0.tv_sec < tv1.tv_sec)
+                       || ((tv0.tv_sec == tv1.tv_sec) && (tv0.tv_usec < tv1.tv_usec)))
+       {
+               larger  = &tv1;
+               smaller = &tv0;
+               status  = -1;
+       }
+       else
+       {
+               larger  = &tv0;
+               smaller = &tv1;
+               status  = 1;
        }
 
-       return (0);
-}
+       if (delta != NULL) {
+               delta->tv_sec = larger->tv_sec - smaller->tv_sec;
+
+               if (smaller->tv_usec <= larger->tv_usec)
+                       delta->tv_usec = larger->tv_usec - smaller->tv_usec;
+               else
+               {
+                       --delta->tv_sec;
+                       delta->tv_usec = 1000000 + larger->tv_usec - smaller->tv_usec;
+               }
+       }
+
+       assert ((delta == NULL)
+                       || ((0 <= delta->tv_usec) && (delta->tv_usec < 1000000)));
+
+       return (status);
+} /* int timeval_cmp */
 
 int check_create_dir (const char *file_orig)
 {
@@ -377,7 +413,7 @@ int check_create_dir (const char *file_orig)
        char *saveptr;
        int   last_is_file = 1;
        int   path_is_absolute = 0;
-       int   len;
+       size_t len;
        int   i;
 
        /*
@@ -930,4 +966,22 @@ int read_file_contents (const char *filename, char *buf, int bufsize)
        return n;
 }
 
+counter_t counter_diff (counter_t old_value, counter_t new_value)
+{
+       counter_t diff;
+
+       if (old_value > new_value)
+       {
+               if (old_value <= 4294967295U)
+                       diff = (4294967295U - old_value) + new_value;
+               else
+                       diff = (18446744073709551615ULL - old_value)
+                               + new_value;
+       }
+       else
+       {
+               diff = new_value - old_value;
+       }
 
+       return (diff);
+} /* counter_t counter_to_gauge */
index f463b77..85db3ad 100644 (file)
 
 #define STATIC_ARRAY_SIZE(a) (sizeof (a) / sizeof (*(a)))
 
+#define IS_TRUE(s) ((strcasecmp ("true", (s)) == 0) \
+               || (strcasecmp ("yes", (s)) == 0) \
+               || (strcasecmp ("on", (s)) == 0))
+#define IS_FALSE(s) ((strcasecmp ("false", (s)) == 0) \
+               || (strcasecmp ("no", (s)) == 0) \
+               || (strcasecmp ("off", (s)) == 0))
+
 char *sstrncpy (char *dest, const char *src, size_t n);
 int ssnprintf (char *dest, size_t n, const char *format, ...);
 char *sstrdup(const char *s);
@@ -153,8 +160,27 @@ int escape_slashes (char *buf, int buf_len);
 
 int strsubstitute (char *str, char c_from, char c_to);
 
-/* FIXME: `timeval_sub_timespec' needs a description */
-int timeval_sub_timespec (struct timeval *tv0, struct timeval *tv1, struct timespec *ret);
+/*
+ * NAME
+ *   timeval_cmp
+ *
+ * DESCRIPTION
+ *   Compare the two time values `tv0' and `tv1' and store the absolut value
+ *   of the difference in the time value pointed to by `delta' if it does not
+ *   equal NULL.
+ *
+ * RETURN VALUE
+ *   Returns an integer less than, equal to, or greater than zero if `tv0' is
+ *   less than, equal to, or greater than `tv1' respectively.
+ */
+int timeval_cmp (struct timeval tv0, struct timeval tv1, struct timeval *delta);
+
+/* make sure tv_usec stores less than a second */
+#define NORMALIZE_TIMEVAL(tv) \
+       do { \
+               (tv).tv_sec += (tv).tv_usec / 1000000; \
+               (tv).tv_usec = (tv).tv_usec % 1000000; \
+       } while (0)
 
 int check_create_dir (const char *file_orig);
 
@@ -209,4 +235,6 @@ int walk_directory (const char *dir, dirwalk_callback_f callback,
                void *user_data);
 int read_file_contents (const char *filename, char *buf, int bufsize);
 
+counter_t counter_diff (counter_t old_value, counter_t new_value);
+
 #endif /* COMMON_H */
index 580b24b..c929d00 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/configfile.c
- * Copyright (C) 2005-2008  Florian octo Forster
+ * Copyright (C) 2005-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
@@ -30,6 +30,7 @@
 #include "configfile.h"
 #include "types_list.h"
 #include "utils_threshold.h"
+#include "filter_chain.h"
 
 #if HAVE_WORDEXP_H
 # include <wordexp.h>
@@ -97,7 +98,9 @@ static cf_global_option_t cf_global_options[] =
        {"Hostname",    NULL, NULL},
        {"FQDNLookup",  NULL, "false"},
        {"Interval",    NULL, "10"},
-       {"ReadThreads", NULL, "5"}
+       {"ReadThreads", NULL, "5"},
+       {"PreCacheChain",  NULL, "PreCache"},
+       {"PostCacheChain", NULL, "PostCache"}
 };
 static int cf_global_options_num = STATIC_ARRAY_LEN (cf_global_options);
 
@@ -346,6 +349,8 @@ static int dispatch_block (oconfig_item_t *ci)
                return (dispatch_block_plugin (ci));
        else if (strcasecmp (ci->key, "Threshold") == 0)
                return (ut_config (ci));
+       else if (strcasecmp (ci->key, "Chain") == 0)
+               return (fc_configure (ci));
 
        return (0);
 }
@@ -555,7 +560,7 @@ static oconfig_item_t *cf_read_dir (const char *dir, int depth)
 
                status = ssnprintf (name, sizeof (name), "%s/%s",
                                dir, de->d_name);
-               if (status >= sizeof (name))
+               if ((status < 0) || ((size_t) status >= sizeof (name)))
                {
                        ERROR ("configfile: Not including `%s/%s' because its"
                                        " name is too long.",
@@ -630,7 +635,7 @@ static oconfig_item_t *cf_read_generic (const char *path, int depth)
        int status;
        const char *path_ptr;
        wordexp_t we;
-       int i;
+       size_t i;
 
        if (depth >= CF_MAX_DEPTH)
        {
@@ -897,7 +902,7 @@ int cf_read (char *filename)
 
        /* Read the default types.db if no `TypesDB' option was given. */
        if (cf_default_typesdb)
-               read_types_list (PLUGINDIR"/types.db");
+               read_types_list (PKGDATADIR"/types.db");
 
        return (0);
 } /* int cf_read */
index 66cd261..64ce5df 100644 (file)
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -2,6 +2,7 @@
  * collectd - src/cpu.c
  * Copyright (C) 2005-2009  Florian octo Forster
  * Copyright (C) 2008       Oleg King
+ * Copyright (C) 2009       Simon Kuhnle
  *
  * 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
@@ -19,6 +20,7 @@
  * Authors:
  *   Florian octo Forster <octo at verplant.org>
  *   Oleg King <king2 at kaluga.ru>
+ *   Simon Kuhnle <simon at blarzwurst.de>
  **/
 
 #include "collectd.h"
@@ -51,7 +53,8 @@
 # include <sys/sysinfo.h>
 #endif /* HAVE_LIBKSTAT */
 
-#ifdef HAVE_SYSCTLBYNAME
+#if (defined(HAVE_SYSCTL) && HAVE_SYSCTL) \
+       || (defined(HAVE_SYSCTLBYNAME) && HAVE_SYSCTLBYNAME)
 # ifdef HAVE_SYS_SYSCTL_H
 #  include <sys/sysctl.h>
 # endif
 #  define CP_IDLE   4
 #  define CPUSTATES 5
 # endif
-#endif /* HAVE_SYSCTLBYNAME */
+#endif /* HAVE_SYSCTL || HAVE_SYSCTLBYNAME */
+
+#if HAVE_SYSCTL
+# if defined(CTL_HW) && defined(HW_NCPU) \
+       && defined(CTL_KERN) && defined(KERN_CPTIME) && defined(CPUSTATES)
+#  define CAN_USE_SYSCTL 1
+# else
+#  define CAN_USE_SYSCTL 0
+# endif
+#else
+# define CAN_USE_SYSCTL 0
+#endif
 
 #if HAVE_STATGRAB_H
 # include <statgrab.h>
 #endif
 
 #if !PROCESSOR_CPU_LOAD_INFO && !KERNEL_LINUX && !HAVE_LIBKSTAT \
-       && !HAVE_SYSCTLBYNAME && !HAVE_LIBSTATGRAB
+       && !CAN_USE_SYSCTL && !HAVE_SYSCTLBYNAME && !HAVE_LIBSTATGRAB
 # error "No applicable input method."
 #endif
 
@@ -103,6 +117,10 @@ static kstat_t *ksp[MAX_NUMCPU];
 static int numcpu;
 /* #endif HAVE_LIBKSTAT */
 
+#elif CAN_USE_SYSCTL
+static int numcpu;
+/* #endif CAN_USE_SYSCTL */
+
 #elif defined(HAVE_SYSCTLBYNAME)
 static int numcpu;
 /* #endif HAVE_SYSCTLBYNAME */
@@ -148,6 +166,25 @@ static int init (void)
                        ksp[numcpu++] = ksp_chain;
 /* #endif HAVE_LIBKSTAT */
 
+#elif CAN_USE_SYSCTL
+       size_t numcpu_size;
+       int mib[2] = {CTL_HW, HW_NCPU};
+       int status;
+
+       numcpu = 0;
+       numcpu_size = sizeof (numcpu);
+
+       status = sysctl (mib, STATIC_ARRAY_SIZE (mib),
+                       &numcpu, &numcpu_size, NULL, 0);
+       if (status == -1)
+       {
+               char errbuf[1024];
+               WARNING ("cpu plugin: sysctl: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
+/* #endif CAN_USE_SYSCTL */
+
 #elif defined (HAVE_SYSCTLBYNAME)
        size_t numcpu_size;
 
@@ -181,7 +218,6 @@ static void submit (int cpu_num, const char *type_instance, counter_t value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "cpu", sizeof (vl.plugin));
        ssnprintf (vl.plugin_instance, sizeof (vl.type_instance),
@@ -365,6 +401,70 @@ static int cpu_read (void)
        }
 /* #endif defined(HAVE_LIBKSTAT) */
 
+#elif CAN_USE_SYSCTL
+       uint64_t cpuinfo[numcpu][CPUSTATES];
+       size_t cpuinfo_size;
+       int status;
+       int i;
+
+       if (numcpu < 1)
+       {
+               ERROR ("cpu plugin: Could not determine number of "
+                               "installed CPUs using sysctl(3).");
+               return (-1);
+       }
+
+       memset (cpuinfo, 0, sizeof (cpuinfo));
+
+#if defined(KERN_CPTIME2)
+       if (numcpu > 1) {
+               for (i = 0; i < numcpu; i++) {
+                       int mib[] = {CTL_KERN, KERN_CPTIME2, i};
+
+                       cpuinfo_size = sizeof (cpuinfo[0]);
+
+                       status = sysctl (mib, STATIC_ARRAY_SIZE (mib),
+                                       cpuinfo[i], &cpuinfo_size, NULL, 0);
+                       if (status == -1) {
+                               char errbuf[1024];
+                               ERROR ("cpu plugin: sysctl failed: %s.",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                               return (-1);
+                       }
+               }
+       }
+       else
+#endif /* defined(KERN_CPTIME2) */
+       {
+               int mib[] = {CTL_KERN, KERN_CPTIME};
+               long cpuinfo_tmp[CPUSTATES];
+
+               cpuinfo_size = sizeof(cpuinfo_tmp);
+
+               status = sysctl (mib, STATIC_ARRAY_SIZE (mib),
+                                       &cpuinfo_tmp, &cpuinfo_size, NULL, 0);
+               if (status == -1)
+               {
+                       char errbuf[1024];
+                       ERROR ("cpu plugin: sysctl failed: %s.",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       return (-1);
+               }
+
+               for(i = 0; i < CPUSTATES; i++) {
+                       cpuinfo[0][i] = cpuinfo_tmp[i];
+               }
+       }
+
+       for (i = 0; i < numcpu; i++) {
+               submit (i, "user",      cpuinfo[i][CP_USER]);
+               submit (i, "nice",      cpuinfo[i][CP_NICE]);
+               submit (i, "system",    cpuinfo[i][CP_SYS]);
+               submit (i, "idle",      cpuinfo[i][CP_IDLE]);
+               submit (i, "interrupt", cpuinfo[i][CP_INTR]);
+       }
+/* #endif CAN_USE_SYSCTL */
+
 #elif defined(HAVE_SYSCTLBYNAME)
        long cpuinfo[CPUSTATES];
        size_t cpuinfo_size;
@@ -379,30 +479,29 @@ static int cpu_read (void)
                return (-1);
        }
 
-       cpuinfo[CP_SYS] += cpuinfo[CP_INTR];
-
        submit (0, "user", cpuinfo[CP_USER]);
        submit (0, "nice", cpuinfo[CP_NICE]);
        submit (0, "system", cpuinfo[CP_SYS]);
        submit (0, "idle", cpuinfo[CP_IDLE]);
+       submit (0, "interrupt", cpuinfo[CP_INTR]);
 /* #endif HAVE_SYSCTLBYNAME */
 
 #elif defined(HAVE_LIBSTATGRAB)
-       sg_cpu_stats *cs;
-       cs = sg_get_cpu_stats ();
-
-       if (cs == NULL)
-       {
-              ERROR ("cpu plugin: sg_get_cpu_stats failed.");
-               return (-1);
-       }
-
-       submit (0, "idle",   (counter_t) cs->idle);
-       submit (0, "nice",   (counter_t) cs->nice);
-       submit (0, "swap",   (counter_t) cs->swap);
-       submit (0, "system", (counter_t) cs->kernel);
-       submit (0, "user",   (counter_t) cs->user);
-       submit (0, "wait",   (counter_t) cs->iowait);
+       sg_cpu_stats *cs;
+       cs = sg_get_cpu_stats ();
+
+       if (cs == NULL)
+       {
+               ERROR ("cpu plugin: sg_get_cpu_stats failed.");
+               return (-1);
+       }
+
+       submit (0, "idle",   (counter_t) cs->idle);
+       submit (0, "nice",   (counter_t) cs->nice);
+       submit (0, "swap",   (counter_t) cs->swap);
+       submit (0, "system", (counter_t) cs->kernel);
+       submit (0, "user",   (counter_t) cs->user);
+       submit (0, "wait",   (counter_t) cs->iowait);
 #endif /* HAVE_LIBSTATGRAB */
 
        return (0);
index 3738b54..b92b1d0 100644 (file)
@@ -67,7 +67,6 @@ static void cpufreq_submit (int cpu_num, double value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "cpufreq", sizeof (vl.plugin));
        sstrncpy (vl.type, "cpufreq", sizeof (vl.type));
index a94b700..8189bd1 100644 (file)
--- a/src/csv.c
+++ b/src/csv.c
@@ -1,6 +1,7 @@
 /**
  * collectd - src/csv.c
- * Copyright (C) 2007  Florian octo Forster
+ * Copyright (C) 2007-2009  Florian octo Forster
+ * Copyright (C) 2009       Doug MacEachern
  *
  * 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:
  *   Florian octo Forster <octo at verplant.org>
+ *   Doug MacEachern <dougm@hyperic.com>
  **/
 
 #include "collectd.h"
 #include "plugin.h"
 #include "common.h"
 #include "utils_cache.h"
+#include "utils_parse_option.h"
 
 /*
  * Private variables
@@ -36,6 +39,7 @@ static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 static char *datadir   = NULL;
 static int store_rates = 0;
+static int use_stdio   = 0;
 
 static int value_list_to_string (char *buffer, int buffer_len,
                const data_set_t *ds, const value_list_t *vl)
@@ -146,6 +150,7 @@ static int value_list_to_filename (char *buffer, int buffer_len,
                return (-1);
        offset += status;
 
+       if (!use_stdio)
        {
                time_t now;
                struct tm stm;
@@ -200,6 +205,16 @@ static int csv_config (const char *key, const char *value)
        {
                if (datadir != NULL)
                        free (datadir);
+               if (strcasecmp ("stdout", value) == 0)
+               {
+                       use_stdio = 1;
+                       return (0);
+               }
+               else if (strcasecmp ("stderr", value) == 0)
+               {
+                       use_stdio = 2;
+                       return (0);
+               }
                datadir = strdup (value);
                if (datadir != NULL)
                {
@@ -259,6 +274,27 @@ static int csv_write (const data_set_t *ds, const value_list_t *vl)
        if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
                return (-1);
 
+       if (use_stdio)
+       {
+               size_t i;
+
+               escape_string (filename, sizeof (filename));
+
+               /* Replace commas by colons for PUTVAL compatible output. */
+               for (i = 0; i < sizeof (values); i++)
+               {
+                       if (values[i] == 0)
+                               break;
+                       else if (values[i] == ',')
+                               values[i] = ':';
+               }
+
+               fprintf (use_stdio == 1 ? stdout : stderr,
+                        "PUTVAL %s interval=%i %s\n",
+                        filename, interval_g, values);
+               return (0);
+       }
+
        if (stat (filename, &statbuf) == -1)
        {
                if (errno == ENOENT)
diff --git a/src/curl.c b/src/curl.c
new file mode 100644 (file)
index 0000000..88b5496
--- /dev/null
@@ -0,0 +1,620 @@
+/**
+ * collectd - src/curl.c
+ * Copyright (C) 2006-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 "configfile.h"
+#include "utils_match.h"
+
+#include <curl/curl.h>
+
+/*
+ * Data types
+ */
+struct web_match_s;
+typedef struct web_match_s web_match_t;
+struct web_match_s /* {{{ */
+{
+  char *regex;
+  int dstype;
+  char *type;
+  char *instance;
+
+  cu_match_t *match;
+
+  web_match_t *next;
+}; /* }}} */
+
+struct web_page_s;
+typedef struct web_page_s web_page_t;
+struct web_page_s /* {{{ */
+{
+  char *instance;
+
+  char *url;
+  char *user;
+  char *pass;
+  char *credentials;
+  int   verify_peer;
+  int   verify_host;
+  char *cacert;
+
+  CURL *curl;
+  char curl_errbuf[CURL_ERROR_SIZE];
+  char *buffer;
+  size_t buffer_size;
+  size_t buffer_fill;
+
+  web_match_t *matches;
+
+  web_page_t *next;
+}; /* }}} */
+
+/*
+ * Global variables;
+ */
+/* static CURLM *curl = NULL; */
+static web_page_t *pages_g = NULL;
+
+/*
+ * Private functions
+ */
+static size_t cc_curl_callback (void *buf, /* {{{ */
+    size_t size, size_t nmemb, void *user_data)
+{
+  web_page_t *wp;
+  size_t len;
+  
+  len = size * nmemb;
+  if (len <= 0)
+    return (len);
+
+  wp = user_data;
+  if (wp == NULL)
+    return (0);
+
+  if ((wp->buffer_fill + len) >= wp->buffer_size)
+  {
+    char *temp;
+    size_t temp_size;
+
+    temp_size = wp->buffer_fill + len + 1;
+    temp = (char *) realloc (wp->buffer, temp_size);
+    if (temp == NULL)
+    {
+      ERROR ("curl plugin: realloc failed.");
+      return (0);
+    }
+    wp->buffer = temp;
+    wp->buffer_size = temp_size;
+  }
+
+  memcpy (wp->buffer + wp->buffer_fill, (char *) buf, len);
+  wp->buffer_fill += len;
+  wp->buffer[wp->buffer_fill] = 0;
+
+  return (len);
+} /* }}} size_t cc_curl_callback */
+
+static void cc_web_match_free (web_match_t *wm) /* {{{ */
+{
+  if (wm == NULL)
+    return;
+
+  sfree (wm->regex);
+  sfree (wm->type);
+  sfree (wm->instance);
+  match_destroy (wm->match);
+  cc_web_match_free (wm->next);
+  sfree (wm);
+} /* }}} void cc_web_match_free */
+
+static void cc_web_page_free (web_page_t *wp) /* {{{ */
+{
+  if (wp == NULL)
+    return;
+
+  if (wp->curl != NULL)
+    curl_easy_cleanup (wp->curl);
+  wp->curl = NULL;
+
+  sfree (wp->instance);
+
+  sfree (wp->url);
+  sfree (wp->user);
+  sfree (wp->pass);
+  sfree (wp->credentials);
+  sfree (wp->cacert);
+
+  sfree (wp->buffer);
+
+  cc_web_match_free (wp->matches);
+  cc_web_page_free (wp->next);
+  sfree (wp);
+} /* }}} void cc_web_page_free */
+
+static int cc_config_add_string (const char *name, char **dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl plugin: `%s' needs exactly one string argument.", name);
+    return (-1);
+  }
+
+  sfree (*dest);
+  *dest = strdup (ci->values[0].value.string);
+  if (*dest == NULL)
+    return (-1);
+
+  return (0);
+} /* }}} int cc_config_add_string */
+
+static int cc_config_set_boolean (const char *name, int *dest, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("curl plugin: `%s' needs exactly one boolean argument.", name);
+    return (-1);
+  }
+
+  *dest = ci->values[0].value.boolean ? 1 : 0;
+
+  return (0);
+} /* }}} int cc_config_set_boolean */
+
+static int cc_config_add_match_dstype (int *dstype_ret, /* {{{ */
+    oconfig_item_t *ci)
+{
+  int dstype;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl plugin: `DSType' needs exactly one string argument.");
+    return (-1);
+  }
+
+  if (strncasecmp ("Gauge", ci->values[0].value.string,
+        strlen ("Gauge")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_GAUGE;
+    if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_AVERAGE;
+    else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_MIN;
+    else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_MAX;
+    else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_GAUGE_LAST;
+    else
+      dstype = 0;
+  }
+  else if (strncasecmp ("Counter", ci->values[0].value.string,
+        strlen ("Counter")) == 0)
+  {
+    dstype = UTILS_MATCH_DS_TYPE_COUNTER;
+    if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_SET;
+    else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_ADD;
+    else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
+      dstype |= UTILS_MATCH_CF_COUNTER_INC;
+    else
+      dstype = 0;
+  }
+  else
+  {
+    dstype = 0;
+  }
+
+  if (dstype == 0)
+  {
+    WARNING ("curl plugin: `%s' is not a valid argument to `DSType'.",
+       ci->values[0].value.string);
+    return (-1);
+  }
+
+  *dstype_ret = dstype;
+  return (0);
+} /* }}} int cc_config_add_match_dstype */
+
+static int cc_config_add_match (web_page_t *page, /* {{{ */
+    oconfig_item_t *ci)
+{
+  web_match_t *match;
+  int status;
+  int i;
+
+  if (ci->values_num != 0)
+  {
+    WARNING ("curl plugin: Ignoring arguments for the `Match' block.");
+  }
+
+  match = (web_match_t *) malloc (sizeof (*match));
+  if (match == NULL)
+  {
+    ERROR ("curl plugin: malloc failed.");
+    return (-1);
+  }
+  memset (match, 0, sizeof (*match));
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Regex", child->key) == 0)
+      status = cc_config_add_string ("Regex", &match->regex, child);
+    else if (strcasecmp ("DSType", child->key) == 0)
+      status = cc_config_add_match_dstype (&match->dstype, child);
+    else if (strcasecmp ("Type", child->key) == 0)
+      status = cc_config_add_string ("Type", &match->type, child);
+    else if (strcasecmp ("Instance", child->key) == 0)
+      status = cc_config_add_string ("Instance", &match->instance, child);
+    else
+    {
+      WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  while (status == 0)
+  {
+    if (match->regex == NULL)
+    {
+      WARNING ("curl plugin: `Regex' missing in `Match' block.");
+      status = -1;
+    }
+
+    if (match->type == NULL)
+    {
+      WARNING ("curl plugin: `Type' missing in `Match' block.");
+      status = -1;
+    }
+
+    if (match->dstype == 0)
+    {
+      WARNING ("curl plugin: `DSType' missing in `Match' block.");
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+    return (status);
+
+  match->match = match_create_simple (match->regex, match->dstype);
+  if (match->match == NULL)
+  {
+    ERROR ("curl plugin: tail_match_add_match_simple failed.");
+    cc_web_match_free (match);
+    return (-1);
+  }
+  else
+  {
+    web_match_t *prev;
+
+    prev = page->matches;
+    while ((prev != NULL) && (prev->next != NULL))
+      prev = prev->next;
+
+    if (prev == NULL)
+      page->matches = match;
+    else
+      prev->next = match;
+  }
+
+  return (0);
+} /* }}} int cc_config_add_match */
+
+static int cc_page_init_curl (web_page_t *wp) /* {{{ */
+{
+  wp->curl = curl_easy_init ();
+  if (wp->curl == NULL)
+  {
+    ERROR ("curl plugin: curl_easy_init failed.");
+    return (-1);
+  }
+
+  curl_easy_setopt (wp->curl, CURLOPT_WRITEFUNCTION, cc_curl_callback);
+  curl_easy_setopt (wp->curl, CURLOPT_WRITEDATA, wp);
+  curl_easy_setopt (wp->curl, CURLOPT_USERAGENT,
+      PACKAGE_NAME"/"PACKAGE_VERSION);
+  curl_easy_setopt (wp->curl, CURLOPT_ERRORBUFFER, wp->curl_errbuf);
+  curl_easy_setopt (wp->curl, CURLOPT_URL, wp->url);
+
+  if (wp->user != NULL)
+  {
+    size_t credentials_size;
+
+    credentials_size = strlen (wp->user) + 2;
+    if (wp->pass != NULL)
+      credentials_size += strlen (wp->pass);
+
+    wp->credentials = (char *) malloc (credentials_size);
+    if (wp->credentials == NULL)
+    {
+      ERROR ("curl plugin: malloc failed.");
+      return (-1);
+    }
+
+    ssnprintf (wp->credentials, credentials_size, "%s:%s",
+        wp->user, (wp->pass == NULL) ? "" : wp->pass);
+    curl_easy_setopt (wp->curl, CURLOPT_USERPWD, wp->credentials);
+  }
+
+  curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYPEER, wp->verify_peer);
+  curl_easy_setopt (wp->curl, CURLOPT_SSL_VERIFYHOST,
+      wp->verify_host ? 2 : 0);
+  if (wp->cacert != NULL)
+    curl_easy_setopt (wp->curl, CURLOPT_CAINFO, wp->cacert);
+
+  return (0);
+} /* }}} int cc_page_init_curl */
+
+static int cc_config_add_page (oconfig_item_t *ci) /* {{{ */
+{
+  web_page_t *page;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("curl plugin: `Page' blocks need exactly one string argument.");
+    return (-1);
+  }
+
+  page = (web_page_t *) malloc (sizeof (*page));
+  if (page == NULL)
+  {
+    ERROR ("curl plugin: malloc failed.");
+    return (-1);
+  }
+  memset (page, 0, sizeof (*page));
+  page->url = NULL;
+  page->user = NULL;
+  page->pass = NULL;
+  page->verify_peer = 1;
+  page->verify_host = 1;
+
+  page->instance = strdup (ci->values[0].value.string);
+  if (page->instance == NULL)
+  {
+    ERROR ("curl plugin: strdup failed.");
+    sfree (page);
+    return (-1);
+  }
+
+  /* Process all children */
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("URL", child->key) == 0)
+      status = cc_config_add_string ("URL", &page->url, child);
+    else if (strcasecmp ("User", child->key) == 0)
+      status = cc_config_add_string ("User", &page->user, child);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = cc_config_add_string ("Password", &page->pass, child);
+    else if (strcasecmp ("VerifyPeer", child->key) == 0)
+      status = cc_config_set_boolean ("VerifyPeer", &page->verify_peer, child);
+    else if (strcasecmp ("VerifyHost", child->key) == 0)
+      status = cc_config_set_boolean ("VerifyHost", &page->verify_host, child);
+    else if (strcasecmp ("CACert", child->key) == 0)
+      status = cc_config_add_string ("CACert", &page->cacert, child);
+    else if (strcasecmp ("Match", child->key) == 0)
+      /* Be liberal with failing matches => don't set `status'. */
+      cc_config_add_match (page, child);
+    else
+    {
+      WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  /* Additionial sanity checks and libCURL initialization. */
+  while (status == 0)
+  {
+    if (page->url == NULL)
+    {
+      WARNING ("curl plugin: `URL' missing in `Page' block.");
+      status = -1;
+    }
+
+    if (page->matches == NULL)
+    {
+      assert (page->instance != NULL);
+      WARNING ("curl plugin: No (valid) `Match' block "
+          "within `Page' block `%s'.", page->instance);
+      status = -1;
+    }
+
+    if (status == 0)
+      status = cc_page_init_curl (page);
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    cc_web_page_free (page);
+    return (status);
+  }
+
+  /* Add the new page to the linked list */
+  if (pages_g == NULL)
+    pages_g = page;
+  else
+  {
+    web_page_t *prev;
+
+    prev = pages_g;
+    while ((prev != NULL) && (prev->next != NULL))
+      prev = prev->next;
+    prev->next = page;
+  }
+
+  return (0);
+} /* }}} int cc_config_add_page */
+
+static int cc_config (oconfig_item_t *ci) /* {{{ */
+{
+  int success;
+  int errors;
+  int status;
+  int i;
+
+  success = 0;
+  errors = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Page", child->key) == 0)
+    {
+      status = cc_config_add_page (child);
+      if (status == 0)
+        success++;
+      else
+        errors++;
+    }
+    else
+    {
+      WARNING ("curl plugin: Option `%s' not allowed here.", child->key);
+      errors++;
+    }
+  }
+
+  if ((success == 0) && (errors > 0))
+  {
+    ERROR ("curl plugin: All statements failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cc_config */
+
+static int cc_init (void) /* {{{ */
+{
+  if (pages_g == NULL)
+  {
+    INFO ("curl plugin: No pages have been defined.");
+    return (-1);
+  }
+  return (0);
+} /* }}} int cc_init */
+
+static void cc_submit (const web_page_t *wp, const web_match_t *wm, /* {{{ */
+    const cu_match_value_t *mv)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0] = mv->value;
+
+  vl.values = values;
+  vl.values_len = 1;
+  vl.time = time (NULL);
+  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  sstrncpy (vl.plugin, "curl", sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, wp->instance, sizeof (vl.plugin_instance));
+  sstrncpy (vl.type, wm->type, sizeof (vl.type));
+  sstrncpy (vl.type_instance, wm->instance, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* }}} void cc_submit */
+
+static int cc_read_page (web_page_t *wp) /* {{{ */
+{
+  web_match_t *wm;
+  int status;
+
+  wp->buffer_fill = 0;
+  status = curl_easy_perform (wp->curl);
+  if (status != 0)
+  {
+    ERROR ("curl plugin: curl_easy_perform failed with staus %i: %s",
+        status, wp->curl_errbuf);
+    return (-1);
+  }
+
+  for (wm = wp->matches; wm != NULL; wm = wm->next)
+  {
+    cu_match_value_t *mv;
+
+    status = match_apply (wm->match, wp->buffer);
+    if (status != 0)
+    {
+      WARNING ("curl plugin: match_apply failed.");
+      continue;
+    }
+
+    mv = match_get_user_data (wm->match);
+    if (mv == NULL)
+    {
+      WARNING ("curl plugin: match_get_user_data returned NULL.");
+      continue;
+    }
+
+    cc_submit (wp, wm, mv);
+  } /* for (wm = wp->matches; wm != NULL; wm = wm->next) */
+
+  return (0);
+} /* }}} int cc_read_page */
+
+static int cc_read (void) /* {{{ */
+{
+  web_page_t *wp;
+
+  for (wp = pages_g; wp != NULL; wp = wp->next)
+    cc_read_page (wp);
+
+  return (0);
+} /* }}} int cc_read */
+
+static int cc_shutdown (void) /* {{{ */
+{
+  cc_web_page_free (pages_g);
+  pages_g = NULL;
+
+  return (0);
+} /* }}} int cc_shutdown */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("curl", cc_config);
+  plugin_register_init ("curl", cc_init);
+  plugin_register_read ("curl", cc_read);
+  plugin_register_shutdown ("curl", cc_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/dbi.c b/src/dbi.c
new file mode 100644 (file)
index 0000000..ce4cd02
--- /dev/null
+++ b/src/dbi.c
@@ -0,0 +1,814 @@
+/**
+ * collectd - src/dbi.c
+ * Copyright (C) 2008,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 "configfile.h"
+#include "utils_db_query.h"
+
+#include <dbi/dbi.h>
+
+/*
+ * Data types
+ */
+struct cdbi_driver_option_s /* {{{ */
+{
+  char *key;
+  char *value;
+};
+typedef struct cdbi_driver_option_s cdbi_driver_option_t; /* }}} */
+
+struct cdbi_database_s /* {{{ */
+{
+  char *name;
+  char *select_db;
+
+  char *driver;
+  cdbi_driver_option_t *driver_options;
+  size_t driver_options_num;
+
+  udb_query_t **queries;
+  size_t        queries_num;
+
+  dbi_conn connection;
+};
+typedef struct cdbi_database_s cdbi_database_t; /* }}} */
+
+/*
+ * Global variables
+ */
+static udb_query_t     **queries       = NULL;
+static size_t            queries_num   = 0;
+static cdbi_database_t **databases     = NULL;
+static size_t            databases_num = 0;
+
+/*
+ * Functions
+ */
+static const char *cdbi_strerror (dbi_conn conn, /* {{{ */
+    char *buffer, size_t buffer_size)
+{
+  const char *msg;
+  int status;
+
+  if (conn == NULL)
+  {
+    sstrncpy (buffer, "connection is NULL", buffer_size);
+    return (buffer);
+  }
+
+  msg = NULL;
+  status = dbi_conn_error (conn, &msg);
+  if ((status >= 0) && (msg != NULL))
+    ssnprintf (buffer, buffer_size, "%s (status %i)", msg, status);
+  else
+    ssnprintf (buffer, buffer_size, "dbi_conn_error failed with status %i",
+        status);
+
+  return (buffer);
+} /* }}} const char *cdbi_conn_error */
+
+static int cdbi_result_get_field (dbi_result res, /* {{{ */
+    unsigned int index, char *buffer, size_t buffer_size)
+{
+  unsigned short src_type;
+
+  src_type = dbi_result_get_field_type_idx (res, index);
+  if (src_type == DBI_TYPE_ERROR)
+  {
+    ERROR ("dbi plugin: cdbi_result_get: "
+        "dbi_result_get_field_type_idx failed.");
+    return (-1);
+  }
+
+  if (src_type == DBI_TYPE_INTEGER)
+  {
+    long long value;
+
+    value = dbi_result_get_longlong_idx (res, index);
+    ssnprintf (buffer, buffer_size, "%lli", value);
+  }
+  else if (src_type == DBI_TYPE_DECIMAL)
+  {
+    double value;
+
+    value = dbi_result_get_double_idx (res, index);
+    ssnprintf (buffer, buffer_size, "%63.15g", value);
+  }
+  else if (src_type == DBI_TYPE_STRING)
+  {
+    const char *value;
+    
+    value = dbi_result_get_string_idx (res, index);
+    if (value == NULL)
+      sstrncpy (buffer, "", buffer_size);
+    else if (strcmp ("ERROR", value) == 0)
+      return (-1);
+    else
+      sstrncpy (buffer, value, buffer_size);
+  }
+  /* DBI_TYPE_BINARY */
+  /* DBI_TYPE_DATETIME */
+  else
+  {
+    const char *field_name;
+
+    field_name = dbi_result_get_field_name (res, index);
+    if (field_name == NULL)
+      field_name = "<unknown>";
+
+    ERROR ("dbi plugin: Column `%s': Don't know how to handle "
+        "source type %hu.",
+        field_name, src_type);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_result_get_field */
+
+static void cdbi_database_free (cdbi_database_t *db) /* {{{ */
+{
+  size_t i;
+
+  if (db == NULL)
+    return;
+
+  sfree (db->name);
+  sfree (db->driver);
+
+  for (i = 0; i < db->driver_options_num; i++)
+  {
+    sfree (db->driver_options[i].key);
+    sfree (db->driver_options[i].value);
+  }
+  sfree (db->driver_options);
+
+  sfree (db);
+} /* }}} void cdbi_database_free */
+
+/* Configuration handling functions {{{
+ *
+ * <Plugin dbi>
+ *   <Query "plugin_instance0">
+ *     Statement "SELECT name, value FROM table"
+ *     <Result>
+ *       Type "gauge"
+ *       InstancesFrom "name"
+ *       ValuesFrom "value"
+ *     </Result>
+ *     ...
+ *   </Query>
+ *     
+ *   <Database "plugin_instance1">
+ *     Driver "mysql"
+ *     DriverOption "hostname" "localhost"
+ *     ...
+ *     Query "plugin_instance0"
+ *   </Database>
+ * </Plugin>
+ */
+
+static int cdbi_config_set_string (char **ret_string, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char *string;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("dbi plugin: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  string = strdup (ci->values[0].value.string);
+  if (string == NULL)
+  {
+    ERROR ("dbi plugin: strdup failed.");
+    return (-1);
+  }
+
+  if (*ret_string != NULL)
+    free (*ret_string);
+  *ret_string = string;
+
+  return (0);
+} /* }}} int cdbi_config_set_string */
+
+static int cdbi_config_add_database_driver_option (cdbi_database_t *db, /* {{{ */
+    oconfig_item_t *ci)
+{
+  cdbi_driver_option_t *option;
+
+  if ((ci->values_num != 2)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING)
+      || (ci->values[1].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("dbi plugin: The `DriverOption' config option "
+        "needs exactly two string arguments.");
+    return (-1);
+  }
+
+  option = (cdbi_driver_option_t *) realloc (db->driver_options,
+      sizeof (*option) * (db->driver_options_num + 1));
+  if (option == NULL)
+  {
+    ERROR ("dbi plugin: realloc failed");
+    return (-1);
+  }
+
+  db->driver_options = option;
+  option = db->driver_options + db->driver_options_num;
+
+  option->key = strdup (ci->values[0].value.string);
+  if (option->key == NULL)
+  {
+    ERROR ("dbi plugin: strdup failed.");
+    return (-1);
+  }
+
+  option->value = strdup (ci->values[1].value.string);
+  if (option->value == NULL)
+  {
+    ERROR ("dbi plugin: strdup failed.");
+    sfree (option->key);
+    return (-1);
+  }
+
+  db->driver_options_num++;
+  return (0);
+} /* }}} int cdbi_config_add_database_driver_option */
+
+static int cdbi_config_add_database (oconfig_item_t *ci) /* {{{ */
+{
+  cdbi_database_t *db;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("dbi plugin: The `Database' block "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  db = (cdbi_database_t *) malloc (sizeof (*db));
+  if (db == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    return (-1);
+  }
+  memset (db, 0, sizeof (*db));
+
+  status = cdbi_config_set_string (&db->name, ci);
+  if (status != 0)
+  {
+    sfree (db);
+    return (status);
+  }
+
+  /* Fill the `cdbi_database_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Driver", child->key) == 0)
+      status = cdbi_config_set_string (&db->driver, child);
+    else if (strcasecmp ("DriverOption", child->key) == 0)
+      status = cdbi_config_add_database_driver_option (db, child);
+    else if (strcasecmp ("SelectDB", child->key) == 0)
+      status = cdbi_config_set_string (&db->select_db, child);
+    else if (strcasecmp ("Query", child->key) == 0)
+      status = udb_query_pick_from_list (child, queries, queries_num,
+          &db->queries, &db->queries_num);
+    else
+    {
+      WARNING ("dbi plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  while (status == 0)
+  {
+    if (db->driver == NULL)
+    {
+      WARNING ("dbi plugin: `Driver' not given for database `%s'", db->name);
+      status = -1;
+    }
+    if (db->driver_options_num == 0)
+    {
+      WARNING ("dbi plugin: No `DriverOption' given for database `%s'. "
+          "This will likely not work.", db->name);
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  /* If all went well, add this database to the global list of databases. */
+  if (status == 0)
+  {
+    cdbi_database_t **temp;
+
+    temp = (cdbi_database_t **) realloc (databases,
+        sizeof (*databases) * (databases_num + 1));
+    if (temp == NULL)
+    {
+      ERROR ("dbi plugin: realloc failed");
+      status = -1;
+    }
+    else
+    {
+      databases = temp;
+      databases[databases_num] = db;
+      databases_num++;
+    }
+  }
+
+  if (status != 0)
+  {
+    cdbi_database_free (db);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_config_add_database */
+
+static int cdbi_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Query", child->key) == 0)
+      udb_query_create (&queries, &queries_num, child,
+          /* callback = */ NULL, /* legacy mode = */ 0);
+    else if (strcasecmp ("Database", child->key) == 0)
+      cdbi_config_add_database (child);
+    else
+    {
+      WARNING ("snmp plugin: Ignoring unknown config option `%s'.", child->key);
+    }
+  } /* for (ci->children) */
+
+  return (0);
+} /* }}} int cdbi_config */
+
+/* }}} End of configuration handling functions */
+
+static int cdbi_init (void) /* {{{ */
+{
+  static int did_init = 0;
+  int status;
+
+  if (did_init != 0)
+    return (0);
+
+  if (queries_num == 0)
+  {
+    ERROR ("dbi plugin: No <Query> blocks have been found. Without them, "
+        "this plugin can't do anything useful, so we will returns an error.");
+    return (-1);
+  }
+
+  if (databases_num == 0)
+  {
+    ERROR ("dbi plugin: No <Database> blocks have been found. Without them, "
+        "this plugin can't do anything useful, so we will returns an error.");
+    return (-1);
+  }
+
+  status = dbi_initialize (NULL);
+  if (status < 0)
+  {
+    ERROR ("dbi plugin: cdbi_init: dbi_initialize failed with status %i.",
+        status);
+    return (-1);
+  }
+  else if (status == 0)
+  {
+    ERROR ("dbi plugin: `dbi_initialize' could not load any drivers. Please "
+        "install at least one `DBD' or check your installation.");
+    return (-1);
+  }
+  DEBUG ("dbi plugin: cdbi_init: dbi_initialize reports %i driver%s.",
+      status, (status == 1) ? "" : "s");
+
+  return (0);
+} /* }}} int cdbi_init */
+
+static int cdbi_read_database_query (cdbi_database_t *db, /* {{{ */
+    udb_query_t *q)
+{
+  const char *statement;
+  dbi_result res;
+  size_t column_num;
+  char **column_names;
+  char **column_values;
+  int status;
+  size_t i;
+
+  /* Macro that cleans up dynamically allocated memory and returns the
+   * specified status. */
+#define BAIL_OUT(status) \
+  if (column_names != NULL) { sfree (column_names[0]); sfree (column_names); } \
+  if (column_values != NULL) { sfree (column_values[0]); sfree (column_values); } \
+  if (res != NULL) { dbi_result_free (res); res = NULL; } \
+  return (status)
+
+  column_names = NULL;
+  column_values = NULL;
+  res = NULL;
+
+  statement = udb_query_get_statement (q);
+  assert (statement != NULL);
+
+  res = dbi_conn_query (db->connection, statement);
+  if (res == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+        "dbi_conn_query failed: %s",
+        db->name, udb_query_get_name (q),
+        cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+    BAIL_OUT (-1);
+  }
+  else /* Get the number of columns */
+  {
+    unsigned int db_status;
+
+    db_status = dbi_result_get_numfields (res);
+    if (db_status == DBI_FIELD_ERROR)
+    {
+      char errbuf[1024];
+      ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+          "dbi_result_get_numfields failed: %s",
+          db->name, udb_query_get_name (q),
+          cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+      BAIL_OUT (-1);
+    }
+
+    column_num = (size_t) db_status;
+    DEBUG ("cdbi_read_database_query (%s, %s): There are %zu columns.",
+        db->name, udb_query_get_name (q), column_num);
+  }
+
+  /* Allocate `column_names' and `column_values'. {{{ */
+  column_names = (char **) calloc (column_num, sizeof (char *));
+  if (column_names == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+
+  column_names[0] = (char *) calloc (column_num,
+      DATA_MAX_NAME_LEN * sizeof (char));
+  if (column_names[0] == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+  for (i = 1; i < column_num; i++)
+    column_names[i] = column_names[i - 1] + DATA_MAX_NAME_LEN;
+
+  column_values = (char **) calloc (column_num, sizeof (char *));
+  if (column_values == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+
+  column_values[0] = (char *) calloc (column_num,
+      DATA_MAX_NAME_LEN * sizeof (char));
+  if (column_values[0] == NULL)
+  {
+    ERROR ("dbi plugin: malloc failed.");
+    BAIL_OUT (-1);
+  }
+  for (i = 1; i < column_num; i++)
+    column_values[i] = column_values[i - 1] + DATA_MAX_NAME_LEN;
+  /* }}} */
+
+  /* Copy the field names to `column_names' */
+  for (i = 0; i < column_num; i++) /* {{{ */
+  {
+    const char *column_name;
+
+    column_name = dbi_result_get_field_name (res, (unsigned int) (i + 1));
+    if (column_name == NULL)
+    {
+      ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+          "Cannot retrieve name of field %zu.",
+          db->name, udb_query_get_name (q), i + 1);
+      BAIL_OUT (-1);
+    }
+
+    sstrncpy (column_names[i], column_name, DATA_MAX_NAME_LEN);
+  } /* }}} for (i = 0; i < column_num; i++) */
+
+  udb_query_prepare_result (q, hostname_g, /* plugin = */ "dbi", db->name,
+      column_names, column_num);
+
+  /* 0 = error; 1 = success; */
+  status = dbi_result_first_row (res); /* {{{ */
+  if (status != 1)
+  {
+    char errbuf[1024];
+    ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+        "dbi_result_first_row failed: %s. Maybe the statement didn't "
+        "return any rows?",
+        db->name, udb_query_get_name (q),
+        cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+    udb_query_finish_result (q);
+    BAIL_OUT (-1);
+  } /* }}} */
+
+  /* Iterate over all rows and call `udb_query_handle_result' with each list of
+   * values. */
+  while (42) /* {{{ */
+  {
+    status = 0;
+    /* Copy the value of the columns to `column_values' */
+    for (i = 0; i < column_num; i++) /* {{{ */
+    {
+      status = cdbi_result_get_field (res, (unsigned int) (i + 1),
+          column_values[i], DATA_MAX_NAME_LEN);
+
+      if (status != 0)
+      {
+        ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+            "cdbi_result_get_field (%zu) failed.",
+            db->name, udb_query_get_name (q), i + 1);
+        status = -1;
+        break;
+      }
+    } /* }}} for (i = 0; i < column_num; i++) */
+
+    /* If all values were copied successfully, call `udb_query_handle_result'
+     * to dispatch the row to the daemon. */
+    if (status == 0) /* {{{ */
+    {
+      status = udb_query_handle_result (q, column_values);
+      if (status != 0)
+      {
+        ERROR ("dbi plugin: cdbi_read_database_query (%s, %s): "
+            "udb_query_handle_result failed.",
+            db->name, udb_query_get_name (q));
+      }
+    } /* }}} */
+
+    /* Get the next row from the database. */
+    status = dbi_result_next_row (res); /* {{{ */
+    if (status != 1)
+    {
+      if (dbi_conn_error (db->connection, NULL) != 0)
+      {
+        char errbuf[1024];
+        WARNING ("dbi plugin: cdbi_read_database_query (%s, %s): "
+            "dbi_result_next_row failed: %s.",
+            db->name, udb_query_get_name (q),
+            cdbi_strerror (db->connection, errbuf, sizeof (errbuf)));
+      }
+      break;
+    } /* }}} */
+  } /* }}} while (42) */
+
+  /* Tell the db query interface that we're done with this query. */
+  udb_query_finish_result (q);
+
+  /* Clean up and return `status = 0' (success) */
+  BAIL_OUT (0);
+#undef BAIL_OUT
+} /* }}} int cdbi_read_database_query */
+
+static int cdbi_connect_database (cdbi_database_t *db) /* {{{ */
+{
+  dbi_driver driver;
+  dbi_conn connection;
+  size_t i;
+  int status;
+
+  if (db->connection != NULL)
+  {
+    status = dbi_conn_ping (db->connection);
+    if (status != 0) /* connection is alive */
+      return (0);
+
+    dbi_conn_close (db->connection);
+    db->connection = NULL;
+  }
+
+  driver = dbi_driver_open (db->driver);
+  if (driver == NULL)
+  {
+    ERROR ("dbi plugin: cdbi_connect_database: dbi_driver_open (%s) failed.",
+        db->driver);
+    INFO ("dbi plugin: Maybe the driver isn't installed? "
+        "Known drivers are:");
+    for (driver = dbi_driver_list (NULL);
+        driver != NULL;
+        driver = dbi_driver_list (driver))
+    {
+      INFO ("dbi plugin: * %s", dbi_driver_get_name (driver));
+    }
+    return (-1);
+  }
+
+  connection = dbi_conn_open (driver);
+  if (connection == NULL)
+  {
+    ERROR ("dbi plugin: cdbi_connect_database: dbi_conn_open (%s) failed.",
+        db->driver);
+    return (-1);
+  }
+
+  /* Set all the driver options. Because this is a very very very generic
+   * interface, the error handling is kind of long. If an invalid option is
+   * encountered, it will get a list of options understood by the driver and
+   * report that as `INFO'. This way, users hopefully don't have too much
+   * trouble finding out how to configure the plugin correctly.. */
+  for (i = 0; i < db->driver_options_num; i++)
+  {
+    DEBUG ("dbi plugin: cdbi_connect_database (%s): "
+        "key = %s; value = %s;",
+        db->name,
+        db->driver_options[i].key,
+        db->driver_options[i].value);
+
+    status = dbi_conn_set_option (connection,
+        db->driver_options[i].key, db->driver_options[i].value);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      const char *opt;
+
+      ERROR ("dbi plugin: cdbi_connect_database (%s): "
+          "dbi_conn_set_option (%s, %s) failed: %s.",
+          db->name,
+          db->driver_options[i].key, db->driver_options[i].value,
+          cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+
+      INFO ("dbi plugin: This is a list of all options understood "
+          "by the `%s' driver:", db->driver);
+      for (opt = dbi_conn_get_option_list (connection, NULL);
+          opt != NULL;
+          opt = dbi_conn_get_option_list (connection, opt))
+      {
+        INFO ("dbi plugin: * %s", opt);
+      }
+
+      dbi_conn_close (connection);
+      return (-1);
+    }
+  } /* for (i = 0; i < db->driver_options_num; i++) */
+
+  status = dbi_conn_connect (connection);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("dbi plugin: cdbi_connect_database (%s): "
+        "dbi_conn_connect failed: %s",
+        db->name, cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+    dbi_conn_close (connection);
+    return (-1);
+  }
+
+  if (db->select_db != NULL)
+  {
+    status = dbi_conn_select_db (connection, db->select_db);
+    if (status != 0)
+    {
+      char errbuf[1024];
+      WARNING ("dbi plugin: cdbi_connect_database (%s): "
+          "dbi_conn_select_db (%s) failed: %s. Check the `SelectDB' option.",
+          db->name, db->select_db,
+          cdbi_strerror (connection, errbuf, sizeof (errbuf)));
+      dbi_conn_close (connection);
+      return (-1);
+    }
+  }
+
+  db->connection = connection;
+  return (0);
+} /* }}} int cdbi_connect_database */
+
+static int cdbi_read_database (cdbi_database_t *db) /* {{{ */
+{
+  size_t i;
+  int success;
+  int status;
+
+  unsigned int db_version;
+
+  status = cdbi_connect_database (db);
+  if (status != 0)
+    return (status);
+  assert (db->connection != NULL);
+
+  db_version = dbi_conn_get_engine_version (db->connection);
+  /* TODO: Complain if `db_version == 0' */
+
+  success = 0;
+  for (i = 0; i < db->queries_num; i++)
+  {
+    /* Check if we know the database's version and if so, if this query applies
+     * to that version. */
+    if ((db_version != 0)
+        && (udb_query_check_version (db->queries[i], db_version) == 0))
+      continue;
+
+    status = cdbi_read_database_query (db, db->queries[i]);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+  {
+    ERROR ("dbi plugin: All queries failed for database `%s'.", db->name);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_read_database */
+
+static int cdbi_read (void) /* {{{ */
+{
+  size_t i;
+  int success = 0;
+  int status;
+
+  for (i = 0; i < databases_num; i++)
+  {
+    status = cdbi_read_database (databases[i]);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+  {
+    ERROR ("dbi plugin: No database could be read. Will return an error so "
+        "the plugin will be delayed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int cdbi_read */
+
+static int cdbi_shutdown (void) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < databases_num; i++)
+  {
+    if (databases[i]->connection != NULL)
+    {
+      dbi_conn_close (databases[i]->connection);
+      databases[i]->connection = NULL;
+    }
+    cdbi_database_free (databases[i]);
+  }
+  sfree (databases);
+  databases_num = 0;
+
+  udb_query_free (queries, queries_num);
+  queries = NULL;
+  queries_num = 0;
+
+  return (0);
+} /* }}} int cdbi_shutdown */
+
+void module_register (void) /* {{{ */
+{
+  plugin_register_complex_config ("dbi", cdbi_config);
+  plugin_register_init ("dbi", cdbi_init);
+  plugin_register_read ("dbi", cdbi_read);
+  plugin_register_shutdown ("dbi", cdbi_shutdown);
+} /* }}} void module_register */
+
+/*
+ * vim: shiftwidth=2 softtabstop=2 et fdm=marker
+ */
index 26d2382..38079d8 100644 (file)
--- a/src/df.c
+++ b/src/df.c
@@ -124,7 +124,6 @@ static void df_submit (char *df_name,
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "df", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
index 29aa979..489770c 100644 (file)
@@ -212,7 +212,6 @@ static void disk_submit (const char *plugin_instance,
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "disk", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance,
index 4d3106b..476b6dd 100644 (file)
--- a/src/dns.c
+++ b/src/dns.c
@@ -195,7 +195,7 @@ static void dns_child_callback (const rfc1035_header_t *dns)
        pthread_mutex_unlock (&opcode_mutex);
 }
 
-static void *dns_child_loop (void *dummy)
+static void *dns_child_loop (void __attribute__((unused)) *dummy)
 {
        pcap_t *pcap_obj;
        char    pcap_error[PCAP_ERRBUF_SIZE];
@@ -298,7 +298,6 @@ static void submit_counter (const char *type, const char *type_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "dns", sizeof (vl.plugin));
        sstrncpy (vl.type, type, sizeof (vl.type));
@@ -317,7 +316,6 @@ static void submit_octets (counter_t queries, counter_t responses)
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "dns", sizeof (vl.plugin));
        sstrncpy (vl.type, "dns_octets", sizeof (vl.type));
index 4d4a08f..8fc5509 100644 (file)
@@ -378,7 +378,7 @@ static void *collect (void *arg)
        pthread_exit ((void *)0);
 } /* static void *collect (void *) */
 
-static void *open_connection (void *arg)
+static void *open_connection (void __attribute__((unused)) *arg)
 {
        struct sockaddr_un addr;
 
@@ -656,7 +656,6 @@ static void email_submit (const char *type, const char *type_instance, gauge_t v
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "email", sizeof (vl.plugin));
        sstrncpy (vl.type, type, sizeof (vl.type));
index 2e20a67..d56be6d 100644 (file)
@@ -38,7 +38,6 @@ static void entropy_submit (double entropy)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "entropy", sizeof (vl.plugin));
        sstrncpy (vl.type, "entropy", sizeof (vl.type));
index f5d2a6d..ea31b30 100644 (file)
@@ -82,7 +82,7 @@ static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER;
 /*
  * Functions
  */
-static void sigchld_handler (int signal) /* {{{ */
+static void sigchld_handler (int __attribute__((unused)) signal) /* {{{ */
 {
   pid_t pid;
   int status;
@@ -728,7 +728,8 @@ static void *exec_notification_one (void *arg) /* {{{ */
   DEBUG ("exec plugin: Child %i exited with status %i.",
       pid, status);
 
-  plugin_notification_meta_free (n);
+  plugin_notification_meta_free (n->meta);
+  n->meta = NULL;
   sfree (arg);
   pthread_exit ((void *) 0);
   return (NULL);
index f071379..05bb4b3 100644 (file)
 #include <dirent.h>
 #include <fnmatch.h>
 
+#define FC_RECURSIVE 1
+
 struct fc_directory_conf_s
 {
   char *path;
   char *instance;
 
+  int options;
+
   /* Data counters */
   uint64_t files_num;
   uint64_t files_size;
@@ -62,7 +66,6 @@ static void fc_submit_dir (const fc_directory_conf_t *dir)
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE (values);
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "filecount", sizeof (vl.plugin));
   sstrncpy (vl.plugin_instance, dir->instance, sizeof (vl.plugin_instance));
@@ -307,6 +310,25 @@ static int fc_config_add_dir_size (fc_directory_conf_t *dir,
   return (0);
 } /* int fc_config_add_dir_size */
 
+static int fc_config_add_dir_recursive (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("filecount plugin: The `Recursive' config options needs exactly "
+        "one boolean argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    dir->options |= FC_RECURSIVE;
+  else
+    dir->options &= ~FC_RECURSIVE;
+
+  return (0);
+} /* int fc_config_add_dir_recursive */
+
 static int fc_config_add_dir (oconfig_item_t *ci)
 {
   fc_directory_conf_t *dir;
@@ -338,6 +360,8 @@ static int fc_config_add_dir (oconfig_item_t *ci)
 
   fc_config_set_instance (dir, dir->path);
 
+  dir->options = FC_RECURSIVE;
+
   dir->name = NULL;
   dir->mtime = 0;
   dir->size = 0;
@@ -355,6 +379,8 @@ static int fc_config_add_dir (oconfig_item_t *ci)
       status = fc_config_add_dir_mtime (dir, option);
     else if (strcasecmp ("Size", option->key) == 0)
       status = fc_config_add_dir_size (dir, option);
+    else if (strcasecmp ("Recursive", option->key) == 0)
+      status = fc_config_add_dir_recursive (dir, option);
     else
     {
       WARNING ("filecount plugin: fc_config_add_dir: "
@@ -447,7 +473,7 @@ static int fc_read_dir_callback (const char *dirname, const char *filename,
     return (-1);
   }
 
-  if (S_ISDIR (statbuf.st_mode))
+  if (S_ISDIR (statbuf.st_mode) && (dir->options & FC_RECURSIVE))
   {
     status = walk_directory (abs_path, fc_read_dir_callback, dir);
     return (status);
diff --git a/src/filter_chain.c b/src/filter_chain.c
new file mode 100644 (file)
index 0000000..c777fac
--- /dev/null
@@ -0,0 +1,1020 @@
+/**
+ * collectd - src/filter_chain.h
+ * Copyright (C) 2008,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 "configfile.h"
+#include "plugin.h"
+#include "common.h"
+#include "filter_chain.h"
+
+/*
+ * Data types
+ */
+/* List of matches, used in fc_rule_t and for the global `match_list_head'
+ * variable. */
+struct fc_match_s;
+typedef struct fc_match_s fc_match_t; /* {{{ */
+struct fc_match_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  match_proc_t proc;
+  void *user_data;
+  fc_match_t *next;
+}; /* }}} */
+
+/* List of targets, used in fc_rule_t and for the global `target_list_head'
+ * variable. */
+struct fc_target_s;
+typedef struct fc_target_s fc_target_t; /* {{{ */
+struct fc_target_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  void *user_data;
+  target_proc_t proc;
+  fc_target_t *next;
+}; /* }}} */
+
+/* List of rules, used in fc_chain_t */
+struct fc_rule_s;
+typedef struct fc_rule_s fc_rule_t; /* {{{ */
+struct fc_rule_s
+{
+  char name[DATA_MAX_NAME_LEN];
+  fc_match_t  *matches;
+  fc_target_t *targets;
+  fc_rule_t *next;
+}; /* }}} */
+
+/* List of chains, used for `chain_list_head' */
+struct fc_chain_s /* {{{ */
+{
+  char name[DATA_MAX_NAME_LEN];
+  fc_rule_t   *rules;
+  fc_target_t *targets;
+  fc_chain_t  *next;
+}; /* }}} */
+
+/*
+ * Global variables
+ */
+static fc_match_t  *match_list_head;
+static fc_target_t *target_list_head;
+static fc_chain_t  *chain_list_head;
+
+/*
+ * Private functions
+ */
+static void fc_free_matches (fc_match_t *m) /* {{{ */
+{
+  if (m == NULL)
+    return;
+
+  if (m->proc.destroy != NULL)
+    (*m->proc.destroy) (&m->user_data);
+  else if (m->user_data != NULL)
+  {
+    ERROR ("Filter sybsystem: fc_free_matches: There is user data, but no "
+        "destroy functions has been specified. "
+        "Memory will probably be lost!");
+  }
+
+  if (m->next != NULL)
+    fc_free_matches (m->next);
+
+  free (m);
+} /* }}} void fc_free_matches */
+
+static void fc_free_targets (fc_target_t *t) /* {{{ */
+{
+  if (t == NULL)
+    return;
+
+  if (t->proc.destroy != NULL)
+    (*t->proc.destroy) (&t->user_data);
+  else if (t->user_data != NULL)
+  {
+    ERROR ("Filter sybsystem: fc_free_targets: There is user data, but no "
+        "destroy functions has been specified. "
+        "Memory will probably be lost!");
+  }
+
+  if (t->next != NULL)
+    fc_free_targets (t->next);
+
+  free (t);
+} /* }}} void fc_free_targets */
+
+static void fc_free_rules (fc_rule_t *r) /* {{{ */
+{
+  if (r == NULL)
+    return;
+
+  fc_free_matches (r->matches);
+  fc_free_targets (r->targets);
+
+  if (r->next != NULL)
+    fc_free_rules (r->next);
+
+  free (r);
+} /* }}} void fc_free_rules */
+
+static void fc_free_chains (fc_chain_t *c) /* {{{ */
+{
+  if (c == NULL)
+    return;
+
+  fc_free_rules (c->rules);
+  fc_free_targets (c->targets);
+
+  if (c->next != NULL)
+    fc_free_chains (c->next);
+
+  free (c);
+} /* }}} void fc_free_chains */
+
+static char *fc_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *fc_strdup */
+
+/*
+ * Configuration.
+ *
+ * The configuration looks somewhat like this:
+ *
+ *  <Chain "main">
+ *    <Rule>
+ *      <Match "regex">
+ *        Plugin "^mysql$"
+ *        Type "^mysql_command$"
+ *        TypeInstance "^show_"
+ *      </Match>
+ *      <Target "drop">
+ *      </Target>
+ *    </Rule>
+ *
+ *    <Target "write">
+ *      Plugin "rrdtool"
+ *    </Target>
+ *  </Chain>
+ */
+static int fc_config_add_match (fc_match_t **matches_head, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_match_t *m;
+  fc_match_t *ptr;
+  int status;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Match' blocks require "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  ptr = match_list_head;
+  while (ptr != NULL)
+  {
+    if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
+      break;
+    ptr = ptr->next;
+  }
+
+  if (ptr == NULL)
+  {
+    WARNING ("Filter subsystem: Cannot find a \"%s\" match. "
+        "Did you load the appropriate plugin?",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  m = (fc_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("fc_config_add_match: malloc failed.");
+    return (-1);
+  }
+  memset (m, 0, sizeof (*m));
+
+  sstrncpy (m->name, ptr->name, sizeof (m->name));
+  memcpy (&m->proc, &ptr->proc, sizeof (m->proc));
+  m->user_data = NULL;
+  m->next = NULL;
+
+  if (m->proc.create != NULL)
+  {
+    status = (*m->proc.create) (ci, &m->user_data);
+    if (status != 0)
+    {
+      WARNING ("Filter subsystem: Failed to create a %s match.",
+          m->name);
+      fc_free_matches (m);
+      return (-1);
+    }
+  }
+
+  if (*matches_head != NULL)
+  {
+    ptr = *matches_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = m;
+  }
+  else
+  {
+    *matches_head = m;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_match */
+
+static int fc_config_add_target (fc_target_t **targets_head, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_target_t *t;
+  fc_target_t *ptr;
+  int status;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Target' blocks require "
+        "exactly one string argument.");
+    return (-1);
+  }
+
+  ptr = target_list_head;
+  while (ptr != NULL)
+  {
+    if (strcasecmp (ptr->name, ci->values[0].value.string) == 0)
+      break;
+    ptr = ptr->next;
+  }
+
+  if (ptr == NULL)
+  {
+    WARNING ("Filter subsystem: Cannot find a \"%s\" target. "
+        "Did you load the appropriate plugin?",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  t = (fc_target_t *) malloc (sizeof (*t));
+  if (t == NULL)
+  {
+    ERROR ("fc_config_add_target: malloc failed.");
+    return (-1);
+  }
+  memset (t, 0, sizeof (*t));
+
+  sstrncpy (t->name, ptr->name, sizeof (t->name));
+  memcpy (&t->proc, &ptr->proc, sizeof (t->proc));
+  t->user_data = NULL;
+  t->next = NULL;
+
+  if (t->proc.create != NULL)
+  {
+    status = (*t->proc.create) (ci, &t->user_data);
+    if (status != 0)
+    {
+      WARNING ("Filter subsystem: Failed to create a %s target.",
+          t->name);
+      fc_free_targets (t);
+      return (-1);
+    }
+  }
+  else
+  {
+    t->user_data = NULL;
+  }
+  
+  if (*targets_head != NULL)
+  {
+    ptr = *targets_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = t;
+  }
+  else
+  {
+    *targets_head = t;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_target */
+
+static int fc_config_add_rule (fc_chain_t *chain, /* {{{ */
+    oconfig_item_t *ci)
+{
+  fc_rule_t *rule;
+  char rule_name[2*DATA_MAX_NAME_LEN] = "Unnamed rule";
+  int status = 0;
+  int i;
+
+  if (ci->values_num > 1)
+  {
+    WARNING ("Filter subsystem: `Rule' blocks have at most one argument.");
+    return (-1);
+  }
+  else if ((ci->values_num == 1)
+      && (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: `Rule' blocks expect one string argument "
+        "or no argument at all.");
+    return (-1);
+  }
+
+  rule = (fc_rule_t *) malloc (sizeof (*rule));
+  if (rule == NULL)
+  {
+    ERROR ("fc_config_add_rule: malloc failed.");
+    return (-1);
+  }
+  memset (rule, 0, sizeof (*rule));
+  rule->next = NULL;
+
+  if (ci->values_num == 1)
+  {
+    sstrncpy (rule->name, ci->values[0].value.string, sizeof (rule->name));
+    ssnprintf (rule_name, sizeof (rule_name), "Rule \"%s\"",
+        ci->values[0].value.string);
+  }
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Match", option->key) == 0)
+      status = fc_config_add_match (&rule->matches, option);
+    else if (strcasecmp ("Target", option->key) == 0)
+      status = fc_config_add_target (&rule->targets, option);
+    else
+    {
+      WARNING ("Filter subsystem: %s: Option `%s' not allowed "
+          "inside a <Rule> block.", rule_name, option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  /* Additional sanity checking. */
+  while (status == 0)
+  {
+    if (rule->targets == NULL)
+    {
+      WARNING ("Filter subsystem: %s: No target has been specified.",
+          rule_name);
+      status = -1;
+      break;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    fc_free_rules (rule);
+    return (-1);
+  }
+
+  if (chain->rules != NULL)
+  {
+    fc_rule_t *ptr;
+
+    ptr = chain->rules;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = rule;
+  }
+  else
+  {
+    chain->rules = rule;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_rule */
+
+static int fc_config_add_chain (const oconfig_item_t *ci) /* {{{ */
+{
+  fc_chain_t *chain;
+  int status = 0;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("Filter subsystem: <Chain> blocks require exactly one "
+        "string argument.");
+    return (-1);
+  }
+
+  chain = (fc_chain_t *) malloc (sizeof (*chain));
+  if (chain == NULL)
+  {
+    ERROR ("fc_config_add_chain: malloc failed.");
+    return (-1);
+  }
+  memset (chain, 0, sizeof (*chain));
+  sstrncpy (chain->name, ci->values[0].value.string, sizeof (chain->name));
+  chain->rules = NULL;
+  chain->targets = NULL;
+  chain->next = NULL;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Rule", option->key) == 0)
+      status = fc_config_add_rule (chain, option);
+    else if (strcasecmp ("Target", option->key) == 0)
+      status = fc_config_add_target (&chain->targets, option);
+    else
+    {
+      WARNING ("Filter subsystem: Chain %s: Option `%s' not allowed "
+          "inside a <Chain> block.", chain->name, option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  if (status != 0)
+  {
+    fc_free_chains (chain);
+    return (-1);
+  }
+
+  if (chain_list_head != NULL)
+  {
+    fc_chain_t *ptr;
+
+    ptr = chain_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = chain;
+  }
+  else
+  {
+    chain_list_head = chain;
+  }
+
+  return (0);
+} /* }}} int fc_config_add_chain */
+
+/*
+ * Built-in target "jump"
+ *
+ * Prefix `bit' like `_b_uilt-_i_n _t_arget'
+ */
+static int fc_bit_jump_create (const oconfig_item_t *ci, /* {{{ */
+    void **user_data)
+{
+  oconfig_item_t *ci_chain;
+
+  if (ci->children_num != 1)
+  {
+    ERROR ("Filter subsystem: The built-in target `jump' needs exactly "
+        "one `Chain' argument!");
+    return (-1);
+  }
+
+  ci_chain = ci->children;
+  if (strcasecmp ("Chain", ci_chain->key) != 0)
+  {
+    ERROR ("Filter subsystem: The built-in target `jump' does not "
+        "support the configuration option `%s'.",
+        ci_chain->key);
+    return (-1);
+  }
+
+  if ((ci_chain->values_num != 1)
+      || (ci_chain->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Filter subsystem: Built-in target `jump': The `Chain' option "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  *user_data = fc_strdup (ci_chain->values[0].value.string);
+  if (*user_data == NULL)
+  {
+    ERROR ("fc_bit_jump_create: fc_strdup failed.");
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int fc_bit_jump_create */
+
+static int fc_bit_jump_destroy (void **user_data) /* {{{ */
+{
+  if (user_data != NULL)
+  {
+    free (*user_data);
+    *user_data = NULL;
+  }
+
+  return (0);
+} /* }}} int fc_bit_jump_destroy */
+
+static int fc_bit_jump_invoke (const data_set_t *ds, /* {{{ */
+    value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
+    void **user_data)
+{
+  char *chain_name;
+  fc_chain_t *chain;
+  int status;
+
+  chain_name = *user_data;
+
+  for (chain = chain_list_head; chain != NULL; chain = chain->next)
+    if (strcasecmp (chain_name, chain->name) == 0)
+      break;
+
+  if (chain == NULL)
+  {
+    ERROR ("Filter subsystem: Built-in target `jump': There is no chain "
+        "named `%s'.", chain_name);
+    return (-1);
+  }
+
+  status = fc_process_chain (ds, vl, chain);
+  if (status < 0)
+    return (status);
+  else if (status == FC_TARGET_STOP)
+    return (FC_TARGET_STOP);
+  else
+    return (FC_TARGET_CONTINUE);
+} /* }}} int fc_bit_jump_invoke */
+
+static int fc_bit_stop_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    value_list_t __attribute__((unused)) *vl,
+    notification_meta_t __attribute__((unused)) **meta,
+    void __attribute__((unused)) **user_data)
+{
+  return (FC_TARGET_STOP);
+} /* }}} int fc_bit_stop_invoke */
+
+static int fc_bit_return_invoke (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    value_list_t __attribute__((unused)) *vl,
+    notification_meta_t __attribute__((unused)) **meta,
+    void __attribute__((unused)) **user_data)
+{
+  return (FC_TARGET_RETURN);
+} /* }}} int fc_bit_return_invoke */
+
+static int fc_bit_write_create (const oconfig_item_t *ci, /* {{{ */
+    void **user_data)
+{
+  int i;
+
+  char **plugin_list;
+  size_t plugin_list_len;
+
+  plugin_list = NULL;
+  plugin_list_len = 0;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    char **temp;
+    int j;
+
+    if (strcasecmp ("Plugin", child->key) != 0)
+    {
+      ERROR ("Filter subsystem: The built-in target `write' does not "
+          "support the configuration option `%s'.",
+          child->key);
+      continue;
+    }
+
+    for (j = 0; j < child->values_num; j++)
+    {
+      if (child->values[j].type != OCONFIG_TYPE_STRING)
+      {
+        ERROR ("Filter subsystem: Built-in target `write': "
+            "The `Plugin' option accepts only string arguments.");
+        continue;
+      }
+
+      temp = (char **) realloc (plugin_list, (plugin_list_len + 2)
+          * (sizeof (*plugin_list)));
+      if (temp == NULL)
+      {
+        ERROR ("fc_bit_write_create: realloc failed.");
+        continue;
+      }
+      plugin_list = temp;
+
+      plugin_list[plugin_list_len] = fc_strdup (child->values[j].value.string);
+      if (plugin_list[plugin_list_len] == NULL)
+      {
+        ERROR ("fc_bit_write_create: fc_strdup failed.");
+        continue;
+      }
+      plugin_list_len++;
+      plugin_list[plugin_list_len] = NULL;
+    } /* for (j = 0; j < child->values_num; j++) */
+  } /* for (i = 0; i < ci->children_num; i++) */
+
+  *user_data = plugin_list;
+
+  return (0);
+} /* }}} int fc_bit_write_create */
+
+static int fc_bit_write_destroy (void **user_data) /* {{{ */
+{
+  char **plugin_list;
+  size_t i;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (0);
+
+  plugin_list = *user_data;
+
+  for (i = 0; plugin_list[i] != NULL; i++)
+    free (plugin_list[i]);
+  free (plugin_list);
+
+  return (0);
+} /* }}} int fc_bit_write_destroy */
+
+static int fc_bit_write_invoke (const data_set_t *ds, /* {{{ */
+    value_list_t *vl, notification_meta_t __attribute__((unused)) **meta,
+    void **user_data)
+{
+  char **plugin_list;
+  int status;
+
+  plugin_list = NULL;
+  if (user_data != NULL)
+    plugin_list = *user_data;
+
+  if ((plugin_list == NULL) || (plugin_list[0] == NULL))
+  {
+    status = plugin_write (/* plugin = */ NULL, ds, vl);
+    if (status != 0)
+    {
+      INFO ("Filter subsystem: Built-in target `write': Dispatching value to "
+          "all write plugins failed with status %i.", status);
+    }
+  }
+  else
+  {
+    size_t i;
+
+    for (i = 0; plugin_list[i] != NULL; i++)
+    {
+      status = plugin_write (plugin_list[i], ds, vl);
+      if (status != 0)
+      {
+        INFO ("Filter subsystem: Built-in target `write': Dispatching value to "
+            "the `%s' plugin failed with status %i.", plugin_list[i], status);
+      }
+    } /* for (i = 0; plugin_list[i] != NULL; i++) */
+  }
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int fc_bit_write_invoke */
+
+static int fc_init_once (void) /* {{{ */
+{
+  static int done = 0;
+  target_proc_t tproc;
+
+  if (done != 0)
+    return (0);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = fc_bit_jump_create;
+  tproc.destroy = fc_bit_jump_destroy;
+  tproc.invoke  = fc_bit_jump_invoke;
+  fc_register_target ("jump", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = NULL;
+  tproc.destroy = NULL;
+  tproc.invoke  = fc_bit_stop_invoke;
+  fc_register_target ("stop", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = NULL;
+  tproc.destroy = NULL;
+  tproc.invoke  = fc_bit_return_invoke;
+  fc_register_target ("return", tproc);
+
+  memset (&tproc, 0, sizeof (tproc));
+  tproc.create  = fc_bit_write_create;
+  tproc.destroy = fc_bit_write_destroy;
+  tproc.invoke  = fc_bit_write_invoke;
+  fc_register_target ("write", tproc);
+
+  done++;
+  return (0);
+} /* }}} int fc_init_once */
+
+/*
+ * Public functions
+ */
+/* Add a match to list of available matches. */
+int fc_register_match (const char *name, match_proc_t proc) /* {{{ */
+{
+  fc_match_t *m;
+
+  DEBUG ("fc_register_match (%s);", name);
+
+  m = (fc_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+    return (-ENOMEM);
+  memset (m, 0, sizeof (*m));
+
+  sstrncpy (m->name, name, sizeof (m->name));
+  memcpy (&m->proc, &proc, sizeof (m->proc));
+  m->next = NULL;
+
+  if (match_list_head == NULL)
+  {
+    match_list_head = m;
+  }
+  else
+  {
+    fc_match_t *ptr;
+
+    ptr = match_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = m;
+  }
+
+  return (0);
+} /* }}} int fc_register_match */
+
+/* Add a target to list of available targets. */
+int fc_register_target (const char *name, target_proc_t proc) /* {{{ */
+{
+  fc_target_t *t;
+
+  DEBUG ("fc_register_target (%s);", name);
+
+  t = (fc_target_t *) malloc (sizeof (*t));
+  if (t == NULL)
+    return (-ENOMEM);
+  memset (t, 0, sizeof (*t));
+
+  sstrncpy (t->name, name, sizeof (t->name));
+  memcpy (&t->proc, &proc, sizeof (t->proc));
+  t->next = NULL;
+
+  if (target_list_head == NULL)
+  {
+    target_list_head = t;
+  }
+  else
+  {
+    fc_target_t *ptr;
+
+    ptr = target_list_head;
+    while (ptr->next != NULL)
+      ptr = ptr->next;
+
+    ptr->next = t;
+  }
+
+  return (0);
+} /* }}} int fc_register_target */
+
+fc_chain_t *fc_chain_get_by_name (const char *chain_name) /* {{{ */
+{
+  fc_chain_t *chain;
+
+  if (chain_name == NULL)
+    return (NULL);
+
+  for (chain = chain_list_head; chain != NULL; chain = chain->next)
+    if (strcasecmp (chain_name, chain->name) == 0)
+      return (chain);
+
+  return (NULL);
+} /* }}} int fc_chain_get_by_name */
+
+int fc_process_chain (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    fc_chain_t *chain)
+{
+  fc_rule_t *rule;
+  fc_target_t *target;
+  int status;
+
+  if (chain == NULL)
+    return (-1);
+
+  DEBUG ("fc_process_chain (chain = %s);", chain->name);
+
+  status = FC_TARGET_CONTINUE;
+  for (rule = chain->rules; rule != NULL; rule = rule->next)
+  {
+    fc_match_t *match;
+
+    if (rule->name[0] != 0)
+    {
+      DEBUG ("fc_process_chain (%s): Testing the `%s' rule.",
+          chain->name, rule->name);
+    }
+
+    /* N. B.: rule->matches may be NULL. */
+    for (match = rule->matches; match != NULL; match = match->next)
+    {
+      /* FIXME: Pass the meta-data to match targets here (when implemented). */
+      status = (*match->proc.match) (ds, vl, /* meta = */ NULL,
+          &match->user_data);
+      if (status < 0)
+      {
+        WARNING ("fc_process_chain (%s): A match failed.", chain->name);
+        break;
+      }
+      else if (status != FC_MATCH_MATCHES)
+        break;
+    }
+
+    /* for-loop has been aborted: Either error or no match. */
+    if (match != NULL)
+    {
+      status = FC_TARGET_CONTINUE;
+      continue;
+    }
+
+    if (rule->name[0] != 0)
+    {
+      DEBUG ("fc_process_chain (%s): Rule `%s' matches.",
+          chain->name, rule->name);
+    }
+
+    for (target = rule->targets; target != NULL; target = target->next)
+    {
+      /* If we get here, all matches have matched the value. Execute the
+       * target. */
+      /* FIXME: Pass the meta-data to match targets here (when implemented). */
+      status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
+          &target->user_data);
+      if (status < 0)
+      {
+        WARNING ("fc_process_chain (%s): A target failed.", chain->name);
+        continue;
+      }
+      else if (status == FC_TARGET_CONTINUE)
+        continue;
+      else if (status == FC_TARGET_STOP)
+        break;
+      else if (status == FC_TARGET_RETURN)
+        break;
+      else
+      {
+        WARNING ("fc_process_chain (%s): Unknown return value "
+            "from target `%s': %i",
+            chain->name, target->name, status);
+      }
+    }
+
+    if ((status == FC_TARGET_STOP)
+        || (status == FC_TARGET_RETURN))
+    {
+      if (rule->name[0] != 0)
+      {
+        DEBUG ("fc_process_chain (%s): Rule `%s' signaled "
+            "the %s condition.",
+            chain->name, rule->name,
+            (status == FC_TARGET_STOP) ? "stop" : "return");
+      }
+      break;
+    }
+    else
+    {
+      status = FC_TARGET_CONTINUE;
+    }
+  } /* for (rule) */
+
+  if (status == FC_TARGET_STOP)
+    return (FC_TARGET_STOP);
+  else if (status == FC_TARGET_RETURN)
+    return (FC_TARGET_CONTINUE);
+
+  /* for-loop has been aborted: A target returned `FC_TARGET_STOP' */
+  if (rule != NULL)
+    return (FC_TARGET_CONTINUE);
+
+  DEBUG ("fc_process_chain (%s): Executing the default targets.",
+      chain->name);
+
+  status = FC_TARGET_CONTINUE;
+  for (target = chain->targets; target != NULL; target = target->next)
+  {
+    /* If we get here, all matches have matched the value. Execute the
+     * target. */
+    /* FIXME: Pass the meta-data to match targets here (when implemented). */
+    status = (*target->proc.invoke) (ds, vl, /* meta = */ NULL,
+        &target->user_data);
+    if (status < 0)
+    {
+      WARNING ("fc_process_chain (%s): The default target failed.",
+          chain->name);
+    }
+    else if (status == FC_TARGET_CONTINUE)
+      continue;
+    else if (status == FC_TARGET_STOP)
+      break;
+    else if (status == FC_TARGET_RETURN)
+      break;
+    else
+    {
+      WARNING ("fc_process_chain (%s): Unknown return value "
+          "from target `%s': %i",
+          chain->name, target->name, status);
+    }
+  }
+
+  if ((status == FC_TARGET_STOP)
+      || (status == FC_TARGET_RETURN))
+  {
+    assert (target != NULL);
+    DEBUG ("fc_process_chain (%s): Default target `%s' signaled "
+        "the %s condition.",
+        chain->name, target->name,
+        (status == FC_TARGET_STOP) ? "stop" : "return");
+    if (status == FC_TARGET_STOP)
+      return (FC_TARGET_STOP);
+    else
+      return (FC_TARGET_CONTINUE);
+  }
+
+  DEBUG ("fc_process_chain (%s): Signaling `continue' at end of chain.",
+      chain->name);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int fc_process_chain */
+
+/* Iterate over all rules in the chain and execute all targets for which all
+ * matches match. */
+int fc_default_action (const data_set_t *ds, value_list_t *vl) /* {{{ */
+{
+  /* FIXME: Pass the meta-data to match targets here (when implemented). */
+  return (fc_bit_write_invoke (ds, vl,
+        /* meta = */ NULL, /* user_data = */ NULL));
+} /* }}} int fc_default_action */
+
+int fc_configure (const oconfig_item_t *ci) /* {{{ */
+{
+  fc_init_once ();
+
+  if (ci == NULL)
+    return (-EINVAL);
+
+  if (strcasecmp ("Chain", ci->key) == 0)
+    return (fc_config_add_chain (ci));
+
+  WARNING ("Filter subsystem: Unknown top level config option `%s'.",
+      ci->key);
+
+  return (-1);
+} /* }}} int fc_configure */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/filter_chain.h b/src/filter_chain.h
new file mode 100644 (file)
index 0000000..187fe22
--- /dev/null
@@ -0,0 +1,101 @@
+/**
+ * collectd - src/filter_chain.h
+ * Copyright (C) 2008,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>
+ **/
+
+#ifndef FILTER_CHAIN_H
+#define FILTER_CHAIN_H 1
+
+#include "collectd.h"
+#include "plugin.h"
+
+#define FC_MATCH_NO_MATCH  0
+#define FC_MATCH_MATCHES   1
+
+#define FC_TARGET_CONTINUE 0
+#define FC_TARGET_STOP     1
+#define FC_TARGET_RETURN   2
+
+/*
+ * Match functions
+ */
+struct match_proc_s
+{
+  int (*create) (const oconfig_item_t *ci, void **user_data);
+  int (*destroy) (void **user_data);
+  int (*match) (const data_set_t *ds, const value_list_t *vl,
+      notification_meta_t **meta, void **user_data);
+};
+typedef struct match_proc_s match_proc_t;
+
+int fc_register_match (const char *name, match_proc_t proc);
+
+/*
+ * Target functions
+ */
+struct target_proc_s
+{
+  int (*create) (const oconfig_item_t *ci, void **user_data);
+  int (*destroy) (void **user_data);
+  int (*invoke) (const data_set_t *ds, value_list_t *vl,
+      notification_meta_t **meta, void **user_data);
+};
+typedef struct target_proc_s target_proc_t;
+
+struct fc_chain_s;
+typedef struct fc_chain_s fc_chain_t;
+
+int fc_register_target (const char *name, target_proc_t proc);
+
+/*
+ * TODO: Chain management
+ */
+#if 0
+int fc_chain_add (const char *chain_name,
+    const char *target_name, int target_argc, char **target_argv);
+int fc_chain_delete (const char *chain_name);
+#endif
+
+/*
+ * TODO: Rule management
+ */
+#if 0
+int fc_rule_add (const char *chain_name, int position,
+    int match_num, const char **match_name, int *match_argc, char ***match_argv,
+    const char *target_name, int target_argc, char **target_argv);
+int fc_rule_delete (const char *chain_name, int position);
+#endif
+
+/*
+ * Processing function
+ */
+fc_chain_t *fc_chain_get_by_name (const char *chain_name);
+
+int fc_process_chain (const data_set_t *ds, value_list_t *vl,
+    fc_chain_t *chain);
+
+int fc_default_action (const data_set_t *ds, value_list_t *vl);
+
+/* 
+ * Shortcut for global configuration
+ */
+int fc_configure (const oconfig_item_t *ci);
+
+#endif /* FILTER_CHAIN_H */
+/* vim: set sw=2 sts=2 et : */
index b765e89..2405d8c 100644 (file)
@@ -450,7 +450,6 @@ static void hddtemp_submit (char *type_instance, double value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "hddtemp", sizeof (vl.plugin));
        sstrncpy (vl.type, "temperature", sizeof (vl.type));
index 8947dd6..fad37db 100644 (file)
@@ -24,6 +24,7 @@
 #include "common.h"
 #include "plugin.h"
 #include "configfile.h"
+#include "utils_ignorelist.h"
 
 #if HAVE_SYS_TYPES_H
 #  include <sys/types.h>
@@ -77,14 +78,7 @@ static const char *config_keys[] =
 };
 static int config_keys_num = 2;
 
-static char **if_list = NULL;
-static int    if_list_num = 0;
-/* 
- * if_list_action:
- * 0 => default is to collect selected interface
- * 1 => ignore selcted interfaces
- */
-static int    if_list_action = 0;
+static ignorelist_t *ignorelist = NULL;
 
 #ifdef HAVE_LIBKSTAT
 #define MAX_NUMIF 256
@@ -95,33 +89,21 @@ static int numif = 0;
 
 static int interface_config (const char *key, const char *value)
 {
-       char **temp;
+       if (ignorelist == NULL)
+               ignorelist = ignorelist_create (/* invert = */ 1);
 
        if (strcasecmp (key, "Interface") == 0)
        {
-               temp = (char **) realloc (if_list, (if_list_num + 1) * sizeof (char *));
-               if (temp == NULL)
-               {
-                       ERROR ("Cannot allocate more memory.");
-                       return (1);
-               }
-               if_list = temp;
-
-               if ((if_list[if_list_num] = strdup (value)) == NULL)
-               {
-                       ERROR ("Cannot allocate memory.");
-                       return (1);
-               }
-               if_list_num++;
+               ignorelist_add (ignorelist, value);
        }
        else if (strcasecmp (key, "IgnoreSelected") == 0)
        {
+               int invert = 1;
                if ((strcasecmp (value, "True") == 0)
                                || (strcasecmp (value, "Yes") == 0)
                                || (strcasecmp (value, "On") == 0))
-                       if_list_action = 1;
-               else
-                       if_list_action = 0;
+                       invert = 0;
+               ignorelist_set_invert (ignorelist, invert);
        }
        else
        {
@@ -161,26 +143,6 @@ static int interface_init (void)
 } /* int interface_init */
 #endif /* HAVE_LIBKSTAT */
 
-/*
- * Check if this interface/instance should be ignored. This is called from
- * both, `submit' and `write' to give client and server the ability to
- * ignore certain stuff..
- */
-static int check_ignore_if (const char *interface)
-{
-       int i;
-
-       /* If no interfaces are given collect all interfaces. Mostly to be
-        * backwards compatible, but also because this is much easier. */
-       if (if_list_num < 1)
-               return (0);
-
-       for (i = 0; i < if_list_num; i++)
-               if (strcasecmp (interface, if_list[i]) == 0)
-                       return (if_list_action);
-       return (1 - if_list_action);
-} /* int check_ignore_if */
-
 static void if_submit (const char *dev, const char *type,
                unsigned long long rx,
                unsigned long long tx)
@@ -188,7 +150,7 @@ static void if_submit (const char *dev, const char *type,
        value_t values[2];
        value_list_t vl = VALUE_LIST_INIT;
 
-       if (check_ignore_if (dev))
+       if (ignorelist_match (ignorelist, dev) != 0)
                return;
 
        values[0].counter = rx;
@@ -196,7 +158,6 @@ static void if_submit (const char *dev, const char *type,
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "interface", sizeof (vl.plugin));
        sstrncpy (vl.type, type, sizeof (vl.type));
index c0a2c5f..441ad8f 100644 (file)
@@ -113,9 +113,9 @@ static int sensor_list_remove (ipmi_sensor_t *sensor);
 static void sensor_read_handler (ipmi_sensor_t *sensor,
     int err,
     enum ipmi_value_present_e value_present,
-    unsigned int raw_value,
+    unsigned int __attribute__((unused)) raw_value,
     double value,
-    ipmi_states_t *states,
+    ipmi_states_t __attribute__((unused)) *states,
     void *user_data)
 {
   value_t values[1];
@@ -150,11 +150,33 @@ static void sensor_read_handler (ipmi_sensor_t *sensor,
         }
       }
     }
+    else if (IPMI_IS_IPMI_ERR(err) && IPMI_GET_IPMI_ERR(err) == IPMI_NOT_SUPPORTED_IN_PRESENT_STATE_CC)
+    {
+      INFO ("ipmi plugin: sensor_read_handler: Sensor %s not ready",
+          list_item->sensor_name);
+    }
     else
     {
-      INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
-          "because it failed with status %#x.",
-          list_item->sensor_name, err);
+      if (IPMI_IS_IPMI_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with IPMI error %#x.",
+            list_item->sensor_name, IPMI_GET_IPMI_ERR(err));
+      else if (IPMI_IS_OS_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with OS error %#x.",
+            list_item->sensor_name, IPMI_GET_OS_ERR(err));
+      else if (IPMI_IS_RMCPP_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with RMCPP error %#x.",
+            list_item->sensor_name, IPMI_GET_RMCPP_ERR(err));
+      else if (IPMI_IS_SOL_ERR(err))
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with RMCPP error %#x.",
+            list_item->sensor_name, IPMI_GET_SOL_ERR(err));
+      else
+        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+            "because it failed with error %#x. of class %#x",
+            list_item->sensor_name, err & 0xff, err & 0xffffff00);
       sensor_list_remove (sensor);
     }
     return;
@@ -199,7 +221,6 @@ static void sensor_read_handler (ipmi_sensor_t *sensor,
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
 
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "ipmi", sizeof (vl.plugin));
@@ -459,9 +480,9 @@ static int sensor_list_remove_all (void)
  * Entity handlers
  */
 static void entity_sensor_update_handler (enum ipmi_update_e op,
-    ipmi_entity_t *entity,
+    ipmi_entity_t __attribute__((unused)) *entity,
     ipmi_sensor_t *sensor,
-    void *user_data)
+    void __attribute__((unused)) *user_data)
 {
   /* TODO: Ignore sensors we cannot read */
 
@@ -480,9 +501,9 @@ static void entity_sensor_update_handler (enum ipmi_update_e op,
  * Domain handlers
  */
 static void domain_entity_update_handler (enum ipmi_update_e op,
-    ipmi_domain_t *domain,
+    ipmi_domain_t __attribute__((unused)) *domain,
     ipmi_entity_t *entity,
-    void *user_data)
+    void __attribute__((unused)) *user_data)
 {
   int status;
 
@@ -574,7 +595,7 @@ static int thread_init (os_handler_t **ret_os_handler)
   return (0);
 } /* int thread_init */
 
-static void *thread_main (void *user_data)
+static void *thread_main (void __attribute__((unused)) *user_data)
 {
   int status;
   os_handler_t *os_handler = NULL;
index 09ffda0..12275cd 100644 (file)
@@ -220,7 +220,6 @@ static int submit_match (const struct ipt_entry_match *match,
 
     vl.values = values;
     vl.values_len = 1;
-    vl.time = time (NULL);
     sstrncpy (vl.host, hostname_g, sizeof (vl.host));
     sstrncpy (vl.plugin, "iptables", sizeof (vl.plugin));
 
index 85e65d2..87eee10 100644 (file)
@@ -235,7 +235,6 @@ static void cipvs_submit_connections (char *pi, char *ti, counter_t value)
        vl.values     = values;
        vl.values_len = 1;
 
-       vl.time     = time (NULL);
        vl.interval = interval_g;
 
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
@@ -261,7 +260,6 @@ static void cipvs_submit_if (char *pi, char *t, char *ti,
        vl.values     = values;
        vl.values_len = 2;
 
-       vl.time     = time (NULL);
        vl.interval = interval_g;
 
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
index a7efd36..1aef344 100644 (file)
--- a/src/irq.c
+++ b/src/irq.c
@@ -131,7 +131,6 @@ static void irq_submit (unsigned int irq, counter_t value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "irq", sizeof (vl.plugin));
        sstrncpy (vl.type, "irq", sizeof (vl.type));
diff --git a/src/libcollectdclient/Makefile.am b/src/libcollectdclient/Makefile.am
new file mode 100644 (file)
index 0000000..6044145
--- /dev/null
@@ -0,0 +1,14 @@
+AUTOMAKE_OPTIONS = foreign no-dependencies
+
+if COMPILER_IS_GCC
+AM_CFLAGS = -Wall -Werror
+endif
+
+pkginclude_HEADERS = client.h lcc_features.h
+lib_LTLIBRARIES = libcollectdclient.la
+nodist_pkgconfig_DATA = libcollectdclient.pc
+
+BUILT_SOURCES = lcc_features.h
+
+libcollectdclient_la_SOURCES = client.c
+libcollectdclient_la_LDFLAGS = -version-info 0:0:0
diff --git a/src/libcollectdclient/client.c b/src/libcollectdclient/client.c
new file mode 100644 (file)
index 0000000..2523eea
--- /dev/null
@@ -0,0 +1,1072 @@
+/**
+ * libcollectdclient - src/libcollectdclient/client.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+/* Set to C99 and POSIX code */
+#if COLLECT_STANDARDS
+# include "standards.h"
+#endif /* COLLECT_STANDARDS */
+
+#if !defined(__GNUC__) || !__GNUC__
+# define __attribute__(x) /**/
+#endif
+
+#include "lcc_features.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <math.h>
+#include <netdb.h>
+
+#include "client.h"
+
+/* NI_MAXHOST has been obsoleted by RFC 3493 which is a reason for SunOS 5.11
+ * to no longer define it. We'll use the old, RFC 2553 value here. */
+#ifndef NI_MAXHOST
+# define NI_MAXHOST 1025
+#endif
+
+/* OpenBSD doesn't have EPROTO, FreeBSD doesn't have EILSEQ. Oh what joy! */
+#ifndef EILSEQ
+# ifdef EPROTO
+#  define EILSEQ EPROTO
+# else
+#  define EILSEQ EINVAL
+# endif
+#endif
+
+/* Secure/static macros. They work like `strcpy' and `strcat', but assure null
+ * termination. They work for static buffers only, because they use `sizeof'.
+ * The `SSTRCATF' combines the functionality of `snprintf' and `strcat' which
+ * is very useful to add formatted stuff to the end of a buffer. */
+#define SSTRCPY(d,s) do { \
+    strncpy ((d), (s), sizeof (d)); \
+    (d)[sizeof (d) - 1] = 0; \
+  } while (0)
+
+#define SSTRCAT(d,s) do { \
+    size_t _l = strlen (d); \
+    strncpy ((d) + _l, (s), sizeof (d) - _l); \
+    (d)[sizeof (d) - 1] = 0; \
+  } while (0)
+
+#define SSTRCATF(d, ...) do { \
+    char _b[sizeof (d)]; \
+    snprintf (_b, sizeof (_b), __VA_ARGS__); \
+    _b[sizeof (_b) - 1] = 0; \
+    SSTRCAT ((d), _b); \
+  } while (0)
+    
+
+#define LCC_SET_ERRSTR(c, ...) do { \
+  snprintf ((c)->errbuf, sizeof ((c)->errbuf), __VA_ARGS__); \
+  (c)->errbuf[sizeof ((c)->errbuf) - 1] = 0; \
+} while (0)
+
+#if COLLECT_DEBUG
+# define LCC_DEBUG(...) printf (__VA_ARGS__)
+#else
+# define LCC_DEBUG(...) /**/
+#endif
+
+/*
+ * Types
+ */
+struct lcc_connection_s
+{
+  FILE *fh;
+  char errbuf[1024];
+};
+
+struct lcc_response_s
+{
+  int status;
+  char message[1024];
+  char **lines;
+  size_t lines_num;
+};
+typedef struct lcc_response_s lcc_response_t;
+
+/*
+ * Private functions
+ */
+/* Even though Posix requires "strerror_r" to return an "int",
+ * some systems (e.g. the GNU libc) return a "char *" _and_
+ * ignore the second argument ... -tokkee */
+char *sstrerror (int errnum, char *buf, size_t buflen)
+{
+  buf[0] = 0;
+
+#if !HAVE_STRERROR_R
+  snprintf (buf, buflen "Error #%i; strerror_r is not available.", errnum);
+/* #endif !HAVE_STRERROR_R */
+
+#elif STRERROR_R_CHAR_P
+  {
+    char *temp;
+    temp = strerror_r (errnum, buf, buflen);
+    if (buf[0] == 0)
+    {
+      if ((temp != NULL) && (temp != buf) && (temp[0] != 0))
+        strncpy (buf, temp, buflen);
+      else
+        strncpy (buf, "strerror_r did not return "
+            "an error message", buflen);
+    }
+  }
+/* #endif STRERROR_R_CHAR_P */
+
+#else
+  if (strerror_r (errnum, buf, buflen) != 0)
+  {
+    snprintf (buf, buflen, "Error #%i; "
+        "Additionally, strerror_r failed.",
+        errnum);
+  }
+#endif /* STRERROR_R_CHAR_P */
+
+  buf[buflen - 1] = 0;
+
+  return (buf);
+} /* char *sstrerror */
+
+static int lcc_set_errno (lcc_connection_t *c, int err) /* {{{ */
+{
+  if (c == NULL)
+    return (-1);
+
+  sstrerror (err, c->errbuf, sizeof (c->errbuf));
+  c->errbuf[sizeof (c->errbuf) - 1] = 0;
+
+  return (0);
+} /* }}} int lcc_set_errno */
+
+/* lcc_strdup: Since `strdup' is an XSI extension, we provide our own version
+ * here. */
+__attribute__((malloc, nonnull (1)))
+static char *lcc_strdup (const char *str) /* {{{ */
+{
+  size_t strsize;
+  char *ret;
+
+  strsize = strlen (str) + 1;
+  ret = (char *) malloc (strsize);
+  if (ret != NULL)
+    memcpy (ret, str, strsize);
+  return (ret);
+} /* }}} char *lcc_strdup */
+
+__attribute__((nonnull (1, 2)))
+static char *lcc_strescape (char *dest, const char *src, size_t dest_size) /* {{{ */
+{
+  size_t dest_pos;
+  size_t src_pos;
+
+  dest_pos = 0;
+  src_pos = 0;
+
+  assert (dest_size >= 3);
+
+  dest[dest_pos] = '"';
+  dest_pos++;
+
+  while (42)
+  {
+    if ((dest_pos == (dest_size - 2))
+        || (src[src_pos] == 0))
+      break;
+
+    if ((src[src_pos] == '"') || (src[src_pos] == '\\'))
+    {
+      /* Check if there is enough space for both characters.. */
+      if (dest_pos == (dest_size - 3))
+        break;
+
+      dest[dest_pos] = '\\';
+      dest_pos++;
+    }
+
+    dest[dest_pos] = src[src_pos];
+    dest_pos++;
+    src_pos++;
+  }
+
+  assert (dest_pos <= (dest_size - 2));
+
+  dest[dest_pos] = '"';
+  dest_pos++;
+
+  dest[dest_pos] = 0;
+  dest_pos++;
+  src_pos++;
+
+  return (dest);
+} /* }}} char *lcc_strescape */
+
+/* lcc_chomp: Removes all control-characters at the end of a string. */
+static void lcc_chomp (char *str) /* {{{ */
+{
+  size_t str_len;
+
+  str_len = strlen (str);
+  while (str_len > 0)
+  {
+    if (str[str_len - 1] >= 32)
+      break;
+    str[str_len - 1] = 0;
+    str_len--;
+  }
+} /* }}} void lcc_chomp */
+
+static void lcc_response_free (lcc_response_t *res) /* {{{ */
+{
+  size_t i;
+
+  if (res == NULL)
+    return;
+
+  for (i = 0; i < res->lines_num; i++)
+    free (res->lines[i]);
+  free (res->lines);
+  res->lines = NULL;
+} /* }}} void lcc_response_free */
+
+static int lcc_send (lcc_connection_t *c, const char *command) /* {{{ */
+{
+  int status;
+
+  LCC_DEBUG ("send:    --> %s\n", command);
+
+  status = fprintf (c->fh, "%s\r\n", command);
+  if (status < 0)
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int lcc_send */
+
+static int lcc_receive (lcc_connection_t *c, /* {{{ */
+    lcc_response_t *ret_res)
+{
+  lcc_response_t res;
+  char *ptr;
+  char buffer[4096];
+  size_t i;
+
+  memset (&res, 0, sizeof (res));
+
+  /* Read the first line, containing the status and a message */
+  ptr = fgets (buffer, sizeof (buffer), c->fh);
+  if (ptr == NULL)
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+  lcc_chomp (buffer);
+  LCC_DEBUG ("receive: <-- %s\n", buffer);
+
+  /* Convert the leading status to an integer and make `ptr' to point to the
+   * beginning of the message. */
+  ptr = NULL;
+  errno = 0;
+  res.status = strtol (buffer, &ptr, 0);
+  if ((errno != 0) || (ptr == &buffer[0]))
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+
+  /* Skip white spaces after the status number */
+  while ((*ptr == ' ') || (*ptr == '\t'))
+    ptr++;
+
+  /* Now copy the message. */
+  strncpy (res.message, ptr, sizeof (res.message));
+  res.message[sizeof (res.message) - 1] = 0;
+
+  /* Error or no lines follow: We're done. */
+  if (res.status <= 0)
+  {
+    memcpy (ret_res, &res, sizeof (res));
+    return (0);
+  }
+
+  /* Allocate space for the char-pointers */
+  res.lines_num = (size_t) res.status;
+  res.status = 0;
+  res.lines = (char **) malloc (res.lines_num * sizeof (char *));
+  if (res.lines == NULL)
+  {
+    lcc_set_errno (c, ENOMEM);
+    return (-1);
+  }
+
+  /* Now receive all the lines */
+  for (i = 0; i < res.lines_num; i++)
+  {
+    ptr = fgets (buffer, sizeof (buffer), c->fh);
+    if (ptr == NULL)
+    {
+      lcc_set_errno (c, errno);
+      break;
+    }
+    lcc_chomp (buffer);
+    LCC_DEBUG ("receive: <-- %s\n", buffer);
+
+    res.lines[i] = lcc_strdup (buffer);
+    if (res.lines[i] == NULL)
+    {
+      lcc_set_errno (c, ENOMEM);
+      break;
+    }
+  }
+
+  /* Check if the for-loop exited with an error. */
+  if (i < res.lines_num)
+  {
+    while (i > 0)
+    {
+      i--;
+      free (res.lines[i]);
+    }
+    free (res.lines);
+    return (-1);
+  }
+
+  memcpy (ret_res, &res, sizeof (res));
+  return (0);
+} /* }}} int lcc_receive */
+
+static int lcc_sendreceive (lcc_connection_t *c, /* {{{ */
+    const char *command, lcc_response_t *ret_res)
+{
+  lcc_response_t res;
+  int status;
+
+  if (c->fh == NULL)
+  {
+    lcc_set_errno (c, EBADF);
+    return (-1);
+  }
+
+  status = lcc_send (c, command);
+  if (status != 0)
+    return (status);
+
+  memset (&res, 0, sizeof (res));
+  status = lcc_receive (c, &res);
+  if (status == 0)
+    memcpy (ret_res, &res, sizeof (*ret_res));
+
+  return (status);
+} /* }}} int lcc_sendreceive */
+
+static int lcc_open_unixsocket (lcc_connection_t *c, const char *path) /* {{{ */
+{
+  struct sockaddr_un sa;
+  int fd;
+  int status;
+
+  assert (c != NULL);
+  assert (c->fh == NULL);
+  assert (path != NULL);
+
+  /* Don't use PF_UNIX here, because it's broken on Mac OS X (10.4, possibly
+   * others). */
+  fd = socket (AF_UNIX, SOCK_STREAM, /* protocol = */ 0);
+  if (fd < 0)
+  {
+    lcc_set_errno (c, errno);
+    return (-1);
+  }
+
+  memset (&sa, 0, sizeof (sa));
+  sa.sun_family = AF_UNIX;
+  strncpy (sa.sun_path, path, sizeof (sa.sun_path) - 1);
+
+  status = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
+  if (status != 0)
+  {
+    lcc_set_errno (c, errno);
+    close (fd);
+    return (-1);
+  }
+
+  c->fh = fdopen (fd, "r+");
+  if (c->fh == NULL)
+  {
+    lcc_set_errno (c, errno);
+    close (fd);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int lcc_open_unixsocket */
+
+static int lcc_open_netsocket (lcc_connection_t *c, /* {{{ */
+    const char *addr_orig)
+{
+  struct addrinfo ai_hints;
+  struct addrinfo *ai_res;
+  struct addrinfo *ai_ptr;
+  char addr_copy[NI_MAXHOST];
+  char *addr;
+  char *port;
+  int fd;
+  int status;
+
+  assert (c != NULL);
+  assert (c->fh == NULL);
+  assert (addr_orig != NULL);
+
+  strncpy(addr_copy, addr_orig, sizeof(addr_copy));
+  addr_copy[sizeof(addr_copy) - 1] = '\0';
+  addr = addr_copy;
+
+  memset (&ai_hints, 0, sizeof (ai_hints));
+  ai_hints.ai_flags = 0;
+#ifdef AI_ADDRCONFIG
+  ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+  ai_hints.ai_family = AF_UNSPEC;
+  ai_hints.ai_socktype = SOCK_STREAM;
+
+  port = NULL;
+  if (*addr == '[') /* IPv6+port format */
+  {
+    /* `addr' is something like "[2001:780:104:2:211:24ff:feab:26f8]:12345" */
+    addr++;
+
+    port = strchr (addr, ']');
+    if (port == NULL)
+    {
+      LCC_SET_ERRSTR (c, "malformed address: %s", addr_orig);
+      return (-1);
+    }
+    *port = 0;
+    port++;
+
+    if (*port == ':')
+      port++;
+    else if (*port == 0)
+      port = NULL;
+    else
+    {
+      LCC_SET_ERRSTR (c, "garbage after address: %s", port);
+      return (-1);
+    }
+  } /* if (*addr = ']') */
+  else if (strchr (addr, '.') != NULL) /* Hostname or IPv4 */
+  {
+    port = strrchr (addr, ':');
+    if (port != NULL)
+    {
+      *port = 0;
+      port++;
+    }
+  }
+
+  ai_res = NULL;
+  status = getaddrinfo (addr,
+                        port == NULL ? LCC_DEFAULT_PORT : port,
+                        &ai_hints, &ai_res);
+  if (status != 0)
+  {
+    LCC_SET_ERRSTR (c, "getaddrinfo: %s", gai_strerror (status));
+    return (-1);
+  }
+
+  for (ai_ptr = ai_res; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+  {
+    fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
+    if (fd < 0)
+    {
+      status = errno;
+      fd = -1;
+      continue;
+    }
+
+    status = connect (fd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+    if (status != 0)
+    {
+      status = errno;
+      close (fd);
+      fd = -1;
+      continue;
+    }
+
+    c->fh = fdopen (fd, "r+");
+    if (c->fh == NULL)
+    {
+      status = errno;
+      close (fd);
+      fd = -1;
+      continue;
+    }
+
+    assert (status == 0);
+    break;
+  } /* for (ai_ptr) */
+
+  if (status != 0)
+  {
+    lcc_set_errno (c, status);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int lcc_open_netsocket */
+
+static int lcc_open_socket (lcc_connection_t *c, const char *addr) /* {{{ */
+{
+  int status = 0;
+
+  if (addr == NULL)
+    return (-1);
+
+  assert (c != NULL);
+  assert (c->fh == NULL);
+  assert (addr != NULL);
+
+  if (strncmp ("unix:", addr, strlen ("unix:")) == 0)
+    status = lcc_open_unixsocket (c, addr + strlen ("unix:"));
+  else if (addr[0] == '/')
+    status = lcc_open_unixsocket (c, addr);
+  else
+    status = lcc_open_netsocket (c, addr);
+
+  return (status);
+} /* }}} int lcc_open_socket */
+
+/*
+ * Public functions
+ */
+unsigned int lcc_version (void) /* {{{ */
+{
+  return (LCC_VERSION);
+} /* }}} unsigned int lcc_version */
+
+const char *lcc_version_string (void) /* {{{ */
+{
+  return (LCC_VERSION_STRING);
+} /* }}} const char *lcc_version_string */
+
+const char *lcc_version_extra (void) /* {{{ */
+{
+  return (LCC_VERSION_EXTRA);
+} /* }}} const char *lcc_version_extra */
+
+int lcc_connect (const char *address, lcc_connection_t **ret_con) /* {{{ */
+{
+  lcc_connection_t *c;
+  int status;
+
+  if (address == NULL)
+    return (-1);
+
+  if (ret_con == NULL)
+    return (-1);
+
+  c = (lcc_connection_t *) malloc (sizeof (*c));
+  if (c == NULL)
+    return (-1);
+  memset (c, 0, sizeof (*c));
+
+  status = lcc_open_socket (c, address);
+  if (status != 0)
+  {
+    lcc_disconnect (c);
+    return (status);
+  }
+
+  *ret_con = c;
+  return (0);
+} /* }}} int lcc_connect */
+
+int lcc_disconnect (lcc_connection_t *c) /* {{{ */
+{
+  if (c == NULL)
+    return (-1);
+
+  if (c->fh != NULL)
+  {
+    fclose (c->fh);
+    c->fh = NULL;
+  }
+
+  free (c);
+  return (0);
+} /* }}} int lcc_disconnect */
+
+int lcc_getval (lcc_connection_t *c, lcc_identifier_t *ident, /* {{{ */
+    size_t *ret_values_num, gauge_t **ret_values, char ***ret_values_names)
+{
+  char ident_str[6 * LCC_NAME_LEN];
+  char ident_esc[12 * LCC_NAME_LEN];
+  char command[14 * LCC_NAME_LEN];
+
+  lcc_response_t res;
+  size_t   values_num;
+  gauge_t *values = NULL;
+  char   **values_names = NULL;
+
+  size_t i;
+  int status;
+
+  if (c == NULL)
+    return (-1);
+
+  if (ident == NULL)
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  /* Build a commend with an escaped version of the identifier string. */
+  status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str), ident);
+  if (status != 0)
+    return (status);
+
+  snprintf (command, sizeof (command), "GETVAL %s",
+      lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
+  command[sizeof (command) - 1] = 0;
+
+  /* Send talk to the daemon.. */
+  status = lcc_sendreceive (c, command, &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  values_num = res.lines_num;
+
+#define BAIL_OUT(e) do { \
+  lcc_set_errno (c, (e)); \
+  free (values); \
+  if (values_names != NULL) { \
+    for (i = 0; i < values_num; i++) { \
+      free (values_names[i]); \
+    } \
+  } \
+  free (values_names); \
+  lcc_response_free (&res); \
+  return (-1); \
+} while (0)
+
+  /* If neither the values nor the names are requested, return here.. */
+  if ((ret_values == NULL) && (ret_values_names == NULL))
+  {
+    if (ret_values_num != NULL)
+      *ret_values_num = values_num;
+    lcc_response_free (&res);
+    return (0);
+  }
+
+  /* Allocate space for the values */
+  if (ret_values != NULL)
+  {
+    values = (gauge_t *) malloc (values_num * sizeof (*values));
+    if (values == NULL)
+      BAIL_OUT (ENOMEM);
+  }
+
+  if (ret_values_names != NULL)
+  {
+    values_names = (char **) calloc (values_num, sizeof (*values_names));
+    if (values_names == NULL)
+      BAIL_OUT (ENOMEM);
+  }
+
+  for (i = 0; i < res.lines_num; i++)
+  {
+    char *key;
+    char *value;
+    char *endptr;
+
+    key = res.lines[i];
+    value = strchr (key, '=');
+    if (value == NULL)
+      BAIL_OUT (EILSEQ);
+
+    *value = 0;
+    value++;
+
+    if (values != NULL)
+    {
+      endptr = NULL;
+      errno = 0;
+      values[i] = strtod (value, &endptr);
+
+      if ((endptr == value) || (errno != 0))
+        BAIL_OUT (errno);
+    }
+
+    if (values_names != NULL)
+    {
+      values_names[i] = lcc_strdup (key);
+      if (values_names[i] == NULL)
+        BAIL_OUT (ENOMEM);
+    }
+  } /* for (i = 0; i < res.lines_num; i++) */
+
+  if (ret_values_num != NULL)
+    *ret_values_num = values_num;
+  if (ret_values != NULL)
+    *ret_values = values;
+  if (ret_values_names != NULL)
+    *ret_values_names = values_names;
+
+  return (0);
+} /* }}} int lcc_getval */
+
+int lcc_putval (lcc_connection_t *c, const lcc_value_list_t *vl) /* {{{ */
+{
+  char ident_str[6 * LCC_NAME_LEN];
+  char ident_esc[12 * LCC_NAME_LEN];
+  char command[1024] = "";
+  lcc_response_t res;
+  int status;
+  size_t i;
+
+  if ((c == NULL) || (vl == NULL) || (vl->values_len < 1)
+      || (vl->values == NULL) || (vl->values_types == NULL))
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str),
+      &vl->identifier);
+  if (status != 0)
+    return (status);
+
+  SSTRCATF (command, "PUTVAL %s",
+      lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
+
+  if (vl->interval > 0)
+    SSTRCATF (command, " interval=%i", vl->interval);
+
+  if (vl->time > 0)
+    SSTRCATF (command, "%u", (unsigned int) vl->time);
+  else
+    SSTRCAT (command, "N");
+
+  for (i = 0; i < vl->values_len; i++)
+  {
+    if (vl->values_types[i] == LCC_TYPE_COUNTER)
+      SSTRCATF (command, ":%"PRIu64, vl->values[i].counter);
+    else if (vl->values_types[i] == LCC_TYPE_GAUGE)
+    {
+      if (isnan (vl->values[i].gauge))
+        SSTRCPY (command, ":U");
+      else
+        SSTRCATF (command, ":%g", vl->values[i].gauge);
+    }
+  } /* for (i = 0; i < vl->values_len; i++) */
+
+  status = lcc_sendreceive (c, command, &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  lcc_response_free (&res);
+  return (0);
+} /* }}} int lcc_putval */
+
+int lcc_flush (lcc_connection_t *c, const char *plugin, /* {{{ */
+    lcc_identifier_t *ident, int timeout)
+{
+  char command[1024] = "";
+  lcc_response_t res;
+  int status;
+
+  if (c == NULL)
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  SSTRCPY (command, "FLUSH");
+
+  if (timeout > 0)
+    SSTRCATF (command, " timeout=%i", timeout);
+
+  if (plugin != NULL)
+  {
+    char buffer[2 * LCC_NAME_LEN];
+    SSTRCATF (command, " plugin=%s",
+        lcc_strescape (buffer, plugin, sizeof (buffer)));
+  }
+
+  if (ident != NULL)
+  {
+    char ident_str[6 * LCC_NAME_LEN];
+    char ident_esc[12 * LCC_NAME_LEN];
+
+    status = lcc_identifier_to_string (c, ident_str, sizeof (ident_str), ident);
+    if (status != 0)
+      return (status);
+
+    SSTRCATF (command, " identifier=%s",
+        lcc_strescape (ident_esc, ident_str, sizeof (ident_esc)));
+  }
+
+  status = lcc_sendreceive (c, command, &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  lcc_response_free (&res);
+  return (0);
+} /* }}} int lcc_flush */
+
+/* TODO: Implement lcc_putnotif */
+
+int lcc_listval (lcc_connection_t *c, /* {{{ */
+    lcc_identifier_t **ret_ident, size_t *ret_ident_num)
+{
+  lcc_response_t res;
+  size_t i;
+  int status;
+
+  lcc_identifier_t *ident;
+  size_t ident_num;
+
+  if (c == NULL)
+    return (-1);
+
+  if ((ret_ident == NULL) || (ret_ident_num == NULL))
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  status = lcc_sendreceive (c, "LISTVAL", &res);
+  if (status != 0)
+    return (status);
+
+  if (res.status != 0)
+  {
+    LCC_SET_ERRSTR (c, "Server error: %s", res.message);
+    lcc_response_free (&res);
+    return (-1);
+  }
+
+  ident_num = res.lines_num;
+  ident = (lcc_identifier_t *) malloc (ident_num * sizeof (*ident));
+  if (ident == NULL)
+  {
+    lcc_response_free (&res);
+    lcc_set_errno (c, ENOMEM);
+    return (-1);
+  }
+
+  for (i = 0; i < res.lines_num; i++)
+  {
+    char *time_str;
+    char *ident_str;
+
+    /* First field is the time. */
+    time_str = res.lines[i];
+
+    /* Set `ident_str' to the beginning of the second field. */
+    ident_str = time_str;
+    while ((*ident_str != ' ') && (*ident_str != '\t') && (*ident_str != 0))
+      ident_str++;
+    while ((*ident_str == ' ') || (*ident_str == '\t'))
+    {
+      *ident_str = 0;
+      ident_str++;
+    }
+
+    if (*ident_str == 0)
+    {
+      lcc_set_errno (c, EILSEQ);
+      status = -1;
+      break;
+    }
+
+    status = lcc_string_to_identifier (c, ident + i, ident_str);
+    if (status != 0)
+      break;
+  }
+
+  lcc_response_free (&res);
+
+  if (status != 0)
+  {
+    free (ident);
+    return (-1);
+  }
+
+  *ret_ident = ident;
+  *ret_ident_num = ident_num;
+
+  return (0);
+} /* }}} int lcc_listval */
+
+const char *lcc_strerror (lcc_connection_t *c) /* {{{ */
+{
+  if (c == NULL)
+    return ("Invalid object");
+  return (c->errbuf);
+} /* }}} const char *lcc_strerror */
+
+int lcc_identifier_to_string (lcc_connection_t *c, /* {{{ */
+    char *string, size_t string_size, const lcc_identifier_t *ident)
+{
+  if ((string == NULL) || (string_size < 6) || (ident == NULL))
+  {
+    lcc_set_errno (c, EINVAL);
+    return (-1);
+  }
+
+  if (ident->plugin_instance[0] == 0)
+  {
+    if (ident->type_instance[0] == 0)
+      snprintf (string, string_size, "%s/%s/%s",
+          ident->host,
+          ident->plugin,
+          ident->type);
+    else
+      snprintf (string, string_size, "%s/%s/%s-%s",
+          ident->host,
+          ident->plugin,
+          ident->type,
+          ident->type_instance);
+  }
+  else
+  {
+    if (ident->type_instance[0] == 0)
+      snprintf (string, string_size, "%s/%s-%s/%s",
+          ident->host,
+          ident->plugin,
+          ident->plugin_instance,
+          ident->type);
+    else
+      snprintf (string, string_size, "%s/%s-%s/%s-%s",
+          ident->host,
+          ident->plugin,
+          ident->plugin_instance,
+          ident->type,
+          ident->type_instance);
+  }
+
+  string[string_size - 1] = 0;
+  return (0);
+} /* }}} int lcc_identifier_to_string */
+
+int lcc_string_to_identifier (lcc_connection_t *c, /* {{{ */
+    lcc_identifier_t *ident, const char *string)
+{
+  char *string_copy;
+  char *host;
+  char *plugin;
+  char *plugin_instance;
+  char *type;
+  char *type_instance;
+
+  string_copy = lcc_strdup (string);
+  if (string_copy == NULL)
+  {
+    lcc_set_errno (c, ENOMEM);
+    return (-1);
+  }
+
+  host = string_copy;
+  plugin = strchr (host, '/');
+  if (plugin == NULL)
+  {
+    LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
+    free (string_copy);
+    return (-1);
+  }
+  *plugin = 0;
+  plugin++;
+
+  type = strchr (plugin, '/');
+  if (type == NULL)
+  {
+    LCC_SET_ERRSTR (c, "Malformed identifier string: %s", string);
+    free (string_copy);
+    return (-1);
+  }
+  *type = 0;
+  type++;
+
+  plugin_instance = strchr (plugin, '-');
+  if (plugin_instance != NULL)
+  {
+    *plugin_instance = 0;
+    plugin_instance++;
+  }
+
+  type_instance = strchr (type, '-');
+  if (type_instance != NULL)
+  {
+    *type_instance = 0;
+    type_instance++;
+  }
+
+  memset (ident, 0, sizeof (*ident));
+
+  SSTRCPY (ident->host, host);
+  SSTRCPY (ident->plugin, plugin);
+  if (plugin_instance != NULL)
+    SSTRCPY (ident->plugin_instance, plugin_instance);
+  SSTRCPY (ident->type, type);
+  if (type_instance != NULL)
+    SSTRCPY (ident->type_instance, type_instance);
+
+  free (string_copy);
+  return (0);
+} /* }}} int lcc_string_to_identifier */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/libcollectdclient/client.h b/src/libcollectdclient/client.h
new file mode 100644 (file)
index 0000000..d5371fb
--- /dev/null
@@ -0,0 +1,114 @@
+/**
+ * libcollectdclient - src/libcollectdclient/client.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef LIBCOLLECTD_COLLECTDCLIENT_H
+#define LIBCOLLECTD_COLLECTDCLIENT_H 1
+
+#include "lcc_features.h"
+
+/*
+ * Includes (for data types)
+ */
+#include <stdint.h>
+#include <inttypes.h>
+#include <time.h>
+
+/*
+ * Defines
+ */
+#define LCC_NAME_LEN 64
+#define LCC_DEFAULT_PORT "25826"
+
+/*
+ * Types
+ */
+#define LCC_TYPE_COUNTER 0
+#define LCC_TYPE_GAUGE   1
+
+LCC_BEGIN_DECLS
+
+typedef uint64_t counter_t;
+typedef double gauge_t;
+
+union value_u
+{
+  counter_t counter;
+  gauge_t   gauge;
+};
+typedef union value_u value_t;
+
+struct lcc_identifier_s
+{
+  char host[LCC_NAME_LEN];
+  char plugin[LCC_NAME_LEN];
+  char plugin_instance[LCC_NAME_LEN];
+  char type[LCC_NAME_LEN];
+  char type_instance[LCC_NAME_LEN];
+};
+typedef struct lcc_identifier_s lcc_identifier_t;
+#define LCC_IDENTIFIER_INIT { "localhost", "", "", "", "" }
+
+struct lcc_value_list_s
+{
+  value_t *values;
+  int     *values_types;
+  size_t   values_len;
+  time_t   time;
+  int      interval;
+  lcc_identifier_t identifier;
+};
+typedef struct lcc_value_list_s lcc_value_list_t;
+#define LCC_VALUE_LIST_INIT { NULL, 0, 0, 0, LCC_IDENTIFIER_INIT }
+
+struct lcc_connection_s;
+typedef struct lcc_connection_s lcc_connection_t;
+
+/*
+ * Functions
+ */
+int lcc_connect (const char *address, lcc_connection_t **ret_con);
+int lcc_disconnect (lcc_connection_t *c);
+#define LCC_DESTROY(c) do { lcc_disconnect (c); (c) = NULL; } while (0)
+
+int lcc_getval (lcc_connection_t *c, lcc_identifier_t *ident,
+    size_t *ret_values_num, gauge_t **ret_values, char ***ret_values_names);
+
+int lcc_putval (lcc_connection_t *c, const lcc_value_list_t *vl);
+
+int lcc_flush (lcc_connection_t *c, const char *plugin,
+    lcc_identifier_t *ident, int timeout);
+
+int lcc_listval (lcc_connection_t *c,
+    lcc_identifier_t **ret_ident, size_t *ret_ident_num);
+
+/* TODO: putnotif */
+
+const char *lcc_strerror (lcc_connection_t *c);
+
+int lcc_identifier_to_string (lcc_connection_t *c,
+    char *string, size_t string_size, const lcc_identifier_t *ident);
+int lcc_string_to_identifier (lcc_connection_t *c,
+    lcc_identifier_t *ident, const char *string);
+
+LCC_END_DECLS
+
+/* vim: set sw=2 sts=2 et : */
+#endif /* LIBCOLLECTD_COLLECTDCLIENT_H */
diff --git a/src/libcollectdclient/lcc_features.h.in b/src/libcollectdclient/lcc_features.h.in
new file mode 100644 (file)
index 0000000..3916a17
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * libcollectdclient - src/libcollectdclient/lcc_features.h
+ * Copyright (C) 2009  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian tokkee Harl <sh at tokkee.org>
+ **/
+
+#ifndef LIBCOLLECTD_LCC_FEATURES_H
+#define LIBCOLLECTD_LCC_FEATURES_H 1
+
+#ifdef __cplusplus
+# define LCC_BEGIN_DECLS extern "C" {
+# define LCC_END_DECLS   }
+#else
+# define LCC_BEGIN_DECLS
+# define LCC_END_DECLS
+#endif
+
+#define LCC_API_VERSION 0
+
+#define LCC_VERSION_MAJOR @LCC_VERSION_MAJOR@
+#define LCC_VERSION_MINOR @LCC_VERSION_MINOR@
+#define LCC_VERSION_PATCH @LCC_VERSION_PATCH@
+
+#define LCC_VERSION_EXTRA "@LCC_VERSION_EXTRA@"
+
+#define LCC_VERSION_STRING "@LCC_VERSION_STRING@"
+
+#define LCC_VERSION_ENCODE(major, minor, patch) \
+       ((major) * 10000 + (minor) * 100 + (patch))
+
+#define LCC_VERSION \
+       LCC_VERSION_ENCODE(LCC_VERSION_MAJOR, LCC_VERSION_MINOR, LCC_VERSION_PATCH)
+
+LCC_BEGIN_DECLS
+
+unsigned int lcc_version (void);
+
+const char *lcc_version_string (void);
+
+const char *lcc_version_extra (void);
+
+LCC_END_DECLS
+
+#endif /* ! LIBCOLLECTD_LCC_FEATURES_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/libcollectdclient/libcollectdclient.pc.in b/src/libcollectdclient/libcollectdclient.pc.in
new file mode 100644 (file)
index 0000000..faade70
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libcollectdclient
+Description: Client library for the unixsock plugin of collectd.
+Version: @LCC_VERSION_STRING@
+URL: http://collectd.org/
+Libs: -L${libdir} -lcollectdclient
+Cflags: -I${includedir}
index 338aecb..a8ed93c 100644 (file)
@@ -1,6 +1,6 @@
 AUTOMAKE_OPTIONS = foreign no-dependencies
 
-EXTRA_DIST = libiptc.c
+EXTRA_DIST = libiptc.c README.collectd
 
 if COMPILER_IS_GCC
 AM_CFLAGS = -Wall -Werror
@@ -8,7 +8,8 @@ endif
 
 noinst_LTLIBRARIES = libiptc.la
 
-libiptc_la_CFLAGS = -DIPTABLES_VERSION=\"1.4.0\" -I$(KERNEL_DIR)/include
+libiptc_la_CFLAGS = -I$(KERNEL_DIR)/include
 libiptc_la_SOURCES = libip4tc.c libip6tc.c \
-               ipt_kernel_headers.h libip6tc.h libiptc.h linux_list.h
+               ipt_kernel_headers.h libip6tc.h libiptc.h linux_list.h \
+               xtables.h libxtc.h
 
diff --git a/src/libiptc/README.collectd b/src/libiptc/README.collectd
new file mode 100644 (file)
index 0000000..adb53b0
--- /dev/null
@@ -0,0 +1,25 @@
+ libiptc (IPTables Chains) in collectd
+=======================================
+http://netfilter.org/
+http://collectd.org/
+
+About
+-----
+
+  This is libiptc taken from the iptables source distribution. As it is not
+  meant to be a public interface by upstream it is not shipped in some binary
+  distributions. Thus, collectd ships its own copy as a fall-back.
+
+  The presently available version was imported from iptables 1.4.1.1.
+
+Changes to the iptables upstream sources:
+-----------------------------------------
+
+  * Added copyright headers mentioning the "Netfilter Core Team" as copyright
+    holder.
+
+  * Changed "libiptc/*" includes to "*".
+
+  * Use the shipped copy of "xtables.h" instead of the one possibly available
+    on the system.
+
index 103267b..9253e11 100644 (file)
@@ -162,7 +162,7 @@ int ip6tc_set_counter(const ip6t_chainlabel chain,
 int ip6tc_commit(ip6tc_handle_t *handle);
 
 /* Get raw socket. */
-int ip6tc_get_raw_socket();
+int ip6tc_get_raw_socket(void);
 
 /* Translates errno numbers into more human-readable form than strerror. */
 const char *ip6tc_strerror(int err);
@@ -170,4 +170,6 @@ const char *ip6tc_strerror(int err);
 /* Return prefix length, or -1 if not contiguous */
 int ipv6_prefix_length(const struct in6_addr *a);
 
+extern void dump_entries6(const ip6tc_handle_t);
+
 #endif /* _LIBIP6TC_H */
index f8e88b2..f7a6640 100644 (file)
@@ -44,6 +44,7 @@
  */
 #include <sys/types.h>
 #include <sys/socket.h>
+#include "xtables.h"
 
 #include "linux_list.h"
 
 #define DEBUGP_C(x, args...)
 #endif
 
-#ifndef IPT_LIB_DIR
-#define IPT_LIB_DIR "/usr/local/lib/iptables"
+#ifdef DEBUG
+#define debug(x, args...)      fprintf(stderr, x, ## args)
+#else
+#define debug(x, args...)
 #endif
 
 static int sockfd = -1;
@@ -150,6 +153,11 @@ STRUCT_TC_HANDLE
        struct chain_head *chain_iterator_cur;
        struct rule_head *rule_iterator_cur;
 
+       unsigned int num_chains;         /* number of user defined chains */
+
+       struct chain_head **chain_index;   /* array for fast chain list access*/
+       unsigned int        chain_index_sz;/* size of chain index array */
+
        STRUCT_GETINFO info;
        STRUCT_GET_ENTRIES *entries;
 };
@@ -184,7 +192,7 @@ static struct rule_head *iptcc_alloc_rule(struct chain_head *c, unsigned int siz
 }
 
 /* notify us that the ruleset has been modified by the user */
-static void
+static inline void
 set_changed(TC_HANDLE_T h)
 {
        h->changed = 1;
@@ -282,11 +290,307 @@ iptcb_ent_is_hook_entry(STRUCT_ENTRY *e, TC_HANDLE_T h)
 
 
 /**********************************************************************
+ * Chain index (cache utility) functions
+ **********************************************************************
+ * The chain index is an array with pointers into the chain list, with
+ * CHAIN_INDEX_BUCKET_LEN spacing.  This facilitates the ability to
+ * speedup chain list searching, by find a more optimal starting
+ * points when searching the linked list.
+ *
+ * The starting point can be found fast by using a binary search of
+ * the chain index. Thus, reducing the previous search complexity of
+ * O(n) to O(log(n/k) + k) where k is CHAIN_INDEX_BUCKET_LEN.
+ *
+ * A nice property of the chain index, is that the "bucket" list
+ * length is max CHAIN_INDEX_BUCKET_LEN (when just build, inserts will
+ * change this). Oppose to hashing, where the "bucket" list length can
+ * vary a lot.
+ */
+#ifndef CHAIN_INDEX_BUCKET_LEN
+#define CHAIN_INDEX_BUCKET_LEN 40
+#endif
+
+/* Another nice property of the chain index is that inserting/creating
+ * chains in chain list don't change the correctness of the chain
+ * index, it only causes longer lists in the buckets.
+ *
+ * To mitigate the performance penalty of longer bucket lists and the
+ * penalty of rebuilding, the chain index is rebuild only when
+ * CHAIN_INDEX_INSERT_MAX chains has been added.
+ */
+#ifndef CHAIN_INDEX_INSERT_MAX
+#define CHAIN_INDEX_INSERT_MAX 355
+#endif
+
+static inline unsigned int iptcc_is_builtin(struct chain_head *c);
+
+
+/* Use binary search in the chain index array, to find a chain_head
+ * pointer closest to the place of the searched name element.
+ *
+ * Notes that, binary search (obviously) requires that the chain list
+ * is sorted by name.
+ */
+static struct list_head *
+iptcc_bsearch_chain_index(const char *name, unsigned int *idx, TC_HANDLE_T handle)
+{
+       unsigned int pos, end;
+       int res;
+
+       struct list_head *list_pos;
+       list_pos=&handle->chains;
+
+       /* Check for empty array, e.g. no user defined chains */
+       if (handle->chain_index_sz == 0) {
+               debug("WARNING: handle->chain_index_sz == 0\n");
+               return list_pos;
+       }
+
+       /* Init */
+       end = handle->chain_index_sz;
+       pos = end / 2;
+
+       debug("bsearch Find chain:%s (pos:%d end:%d)\n", name, pos, end);
+
+       /* Loop */
+ loop:
+       if (!handle->chain_index[pos]) {
+               fprintf(stderr, "ERROR: NULL pointer chain_index[%d]\n", pos);
+               return &handle->chains; /* Be safe, return orig start pos */
+       }
+
+       res = strcmp(name, handle->chain_index[pos]->name);
+       list_pos = &handle->chain_index[pos]->list;
+       *idx = pos;
+
+       debug("bsearch Index[%d] name:%s res:%d ",
+             pos, handle->chain_index[pos]->name, res);
+
+       if (res == 0) { /* Found element, by direct hit */
+               debug("[found] Direct hit pos:%d end:%d\n", pos, end);
+               return list_pos;
+       } else if (res < 0) { /* Too far, jump back */
+               end = pos;
+               pos = pos / 2;
+
+               /* Exit case: First element of array */
+               if (end == 0) {
+                       debug("[found] Reached first array elem (end%d)\n",end);
+                       return list_pos;
+               }
+               debug("jump back to pos:%d (end:%d)\n", pos, end);
+               goto loop;
+       } else if (res > 0 ){ /* Not far enough, jump forward */
+
+               /* Exit case: Last element of array */
+               if (pos == handle->chain_index_sz-1) {
+                       debug("[found] Last array elem (end:%d)\n", end);
+                       return list_pos;
+               }
+
+               /* Exit case: Next index less, thus elem in this list section */
+               res = strcmp(name, handle->chain_index[pos+1]->name);
+               if (res < 0) {
+                       debug("[found] closest list (end:%d)\n", end);
+                       return list_pos;
+               }
+
+               pos = (pos+end)/2;
+               debug("jump forward to pos:%d (end:%d)\n", pos, end);
+               goto loop;
+       }
+
+       return list_pos;
+}
+
+#ifdef DEBUG
+/* Trivial linear search of chain index. Function used for verifying
+   the output of bsearch function */
+static struct list_head *
+iptcc_linearly_search_chain_index(const char *name, TC_HANDLE_T handle)
+{
+       unsigned int i=0;
+       int res=0;
+
+       struct list_head *list_pos;
+       list_pos = &handle->chains;
+
+       if (handle->chain_index_sz)
+               list_pos = &handle->chain_index[0]->list;
+
+       /* Linearly walk of chain index array */
+
+       for (i=0; i < handle->chain_index_sz; i++) {
+               if (handle->chain_index[i]) {
+                       res = strcmp(handle->chain_index[i]->name, name);
+                       if (res > 0)
+                               break; // One step too far
+                       list_pos = &handle->chain_index[i]->list;
+                       if (res == 0)
+                               break; // Direct hit
+               }
+       }
+
+       return list_pos;
+}
+#endif
+
+static int iptcc_chain_index_alloc(TC_HANDLE_T h)
+{
+       unsigned int list_length = CHAIN_INDEX_BUCKET_LEN;
+       unsigned int array_elems;
+       unsigned int array_mem;
+
+       /* Allocate memory for the chain index array */
+       array_elems = (h->num_chains / list_length) +
+                      (h->num_chains % list_length ? 1 : 0);
+       array_mem   = sizeof(h->chain_index) * array_elems;
+
+       debug("Alloc Chain index, elems:%d mem:%d bytes\n",
+             array_elems, array_mem);
+
+       h->chain_index = malloc(array_mem);
+       if (!h->chain_index) {
+               h->chain_index_sz = 0;
+               return -ENOMEM;
+       }
+       memset(h->chain_index, 0, array_mem);
+       h->chain_index_sz = array_elems;
+
+       return 1;
+}
+
+static void iptcc_chain_index_free(TC_HANDLE_T h)
+{
+       h->chain_index_sz = 0;
+       free(h->chain_index);
+}
+
+
+#ifdef DEBUG
+static void iptcc_chain_index_dump(TC_HANDLE_T h)
+{
+       unsigned int i = 0;
+
+       /* Dump: contents of chain index array */
+       for (i=0; i < h->chain_index_sz; i++) {
+               if (h->chain_index[i]) {
+                       fprintf(stderr, "Chain index[%d].name: %s\n",
+                               i, h->chain_index[i]->name);
+               }
+       }
+}
+#endif
+
+/* Build the chain index */
+static int iptcc_chain_index_build(TC_HANDLE_T h)
+{
+       unsigned int list_length = CHAIN_INDEX_BUCKET_LEN;
+       unsigned int chains = 0;
+       unsigned int cindex = 0;
+       struct chain_head *c;
+
+       /* Build up the chain index array here */
+       debug("Building chain index\n");
+
+       debug("Number of user defined chains:%d bucket_sz:%d array_sz:%d\n",
+               h->num_chains, list_length, h->chain_index_sz);
+
+       if (h->chain_index_sz == 0)
+               return 0;
+
+       list_for_each_entry(c, &h->chains, list) {
+
+               /* Issue: The index array needs to start after the
+                * builtin chains, as they are not sorted */
+               if (!iptcc_is_builtin(c)) {
+                       cindex=chains / list_length;
+
+                       /* Safe guard, break out on array limit, this
+                        * is useful if chains are added and array is
+                        * rebuild, without realloc of memory. */
+                       if (cindex >= h->chain_index_sz)
+                               break;
+
+                       if ((chains % list_length)== 0) {
+                               debug("\nIndex[%d] Chains:", cindex);
+                               h->chain_index[cindex] = c;
+                       }
+                       chains++;
+               }
+               debug("%s, ", c->name);
+       }
+       debug("\n");
+
+       return 1;
+}
+
+static int iptcc_chain_index_rebuild(TC_HANDLE_T h)
+{
+       debug("REBUILD chain index array\n");
+       iptcc_chain_index_free(h);
+       if ((iptcc_chain_index_alloc(h)) < 0)
+               return -ENOMEM;
+       iptcc_chain_index_build(h);
+       return 1;
+}
+
+/* Delete chain (pointer) from index array.  Removing an element from
+ * the chain list only affects the chain index array, if the chain
+ * index points-to/uses that list pointer.
+ *
+ * There are different strategies, the simple and safe is to rebuild
+ * the chain index every time.  The more advanced is to update the
+ * array index to point to the next element, but that requires some
+ * house keeping and boundry checks.  The advanced is implemented, as
+ * the simple approach behaves badly when all chains are deleted
+ * because list_for_each processing will always hit the first chain
+ * index, thus causing a rebuild for every chain.
+ */
+static int iptcc_chain_index_delete_chain(struct chain_head *c, TC_HANDLE_T h)
+{
+       struct list_head *index_ptr, *index_ptr2, *next;
+       struct chain_head *c2;
+       unsigned int idx, idx2;
+
+       index_ptr = iptcc_bsearch_chain_index(c->name, &idx, h);
+
+       debug("Del chain[%s] c->list:%p index_ptr:%p\n",
+             c->name, &c->list, index_ptr);
+
+       /* Save the next pointer */
+       next = c->list.next;
+       list_del(&c->list);
+
+       if (index_ptr == &c->list) { /* Chain used as index ptr */
+
+               /* See if its possible to avoid a rebuild, by shifting
+                * to next pointer.  Its possible if the next pointer
+                * is located in the same index bucket.
+                */
+               c2         = list_entry(next, struct chain_head, list);
+               index_ptr2 = iptcc_bsearch_chain_index(c2->name, &idx2, h);
+               if (idx != idx2) {
+                       /* Rebuild needed */
+                       return iptcc_chain_index_rebuild(h);
+               } else {
+                       /* Avoiding rebuild */
+                       debug("Update cindex[%d] with next ptr name:[%s]\n",
+                             idx, c2->name);
+                       h->chain_index[idx]=c2;
+                       return 0;
+               }
+       }
+       return 0;
+}
+
+
+/**********************************************************************
  * iptc cache utility functions (iptcc_*)
  **********************************************************************/
 
 /* Is the given chain builtin (1) or user-defined (0) */
-static unsigned int iptcc_is_builtin(struct chain_head *c)
+static inline unsigned int iptcc_is_builtin(struct chain_head *c)
 {
        return (c->hooknum ? 1 : 0);
 }
@@ -338,21 +642,81 @@ iptcc_find_chain_by_offset(TC_HANDLE_T handle, unsigned int offset)
 
        return NULL;
 }
+
 /* Returns chain head if found, otherwise NULL. */
 static struct chain_head *
 iptcc_find_label(const char *name, TC_HANDLE_T handle)
 {
        struct list_head *pos;
+       struct list_head *list_start_pos;
+       unsigned int i=0;
+       int res;
 
        if (list_empty(&handle->chains))
                return NULL;
 
+       /* First look at builtin chains */
        list_for_each(pos, &handle->chains) {
                struct chain_head *c = list_entry(pos, struct chain_head, list);
+               if (!iptcc_is_builtin(c))
+                       break;
                if (!strcmp(c->name, name))
                        return c;
        }
 
+       /* Find a smart place to start the search via chain index */
+       //list_start_pos = iptcc_linearly_search_chain_index(name, handle);
+       list_start_pos = iptcc_bsearch_chain_index(name, &i, handle);
+
+       /* Handel if bsearch bails out early */
+       if (list_start_pos == &handle->chains) {
+               list_start_pos = pos;
+       }
+#ifdef DEBUG
+       else {
+               /* Verify result of bsearch against linearly index search */
+               struct list_head *test_pos;
+               struct chain_head *test_c, *tmp_c;
+               test_pos = iptcc_linearly_search_chain_index(name, handle);
+               if (list_start_pos != test_pos) {
+                       debug("BUG in chain_index search\n");
+                       test_c=list_entry(test_pos,      struct chain_head,list);
+                       tmp_c =list_entry(list_start_pos,struct chain_head,list);
+                       debug("Verify search found:\n");
+                       debug(" Chain:%s\n", test_c->name);
+                       debug("BSearch found:\n");
+                       debug(" Chain:%s\n", tmp_c->name);
+                       exit(42);
+               }
+       }
+#endif
+
+       /* Initial/special case, no user defined chains */
+       if (handle->num_chains == 0)
+               return NULL;
+
+       /* Start searching through the chain list */
+       list_for_each(pos, list_start_pos->prev) {
+               struct chain_head *c = list_entry(pos, struct chain_head, list);
+               res = strcmp(c->name, name);
+               debug("List search name:%s == %s res:%d\n", name, c->name, res);
+               if (res==0)
+                       return c;
+
+               /* We can stop earlier as we know list is sorted */
+               if (res>0 && !iptcc_is_builtin(c)) { /* Walked too far*/
+                       debug(" Not in list, walked too far, sorted list\n");
+                       return NULL;
+               }
+
+               /* Stop on wrap around, if list head is reached */
+               if (pos == &handle->chains) {
+                       debug("Stop, list head reached\n");
+                       return NULL;
+               }
+       }
+
+       debug("List search NOT found name:%s\n", name);
        return NULL;
 }
 
@@ -413,14 +777,37 @@ static int __iptcc_p_del_policy(TC_HANDLE_T h, unsigned int num)
 static inline void iptc_insert_chain(TC_HANDLE_T h, struct chain_head *c)
 {
        struct chain_head *tmp;
+       struct list_head  *list_start_pos;
+       unsigned int i=1;
+
+       /* Find a smart place to start the insert search */
+       list_start_pos = iptcc_bsearch_chain_index(c->name, &i, h);
+
+       /* Handle the case, where chain.name is smaller than index[0] */
+       if (i==0 && strcmp(c->name, h->chain_index[0]->name) <= 0) {
+               h->chain_index[0] = c; /* Update chain index head */
+               list_start_pos = h->chains.next;
+               debug("Update chain_index[0] with %s\n", c->name);
+       }
+
+       /* Handel if bsearch bails out early */
+       if (list_start_pos == &h->chains) {
+               list_start_pos = h->chains.next;
+       }
 
        /* sort only user defined chains */
        if (!c->hooknum) {
-               list_for_each_entry(tmp, &h->chains, list) {
+               list_for_each_entry(tmp, list_start_pos->prev, list) {
                        if (!tmp->hooknum && strcmp(c->name, tmp->name) <= 0) {
                                list_add(&c->list, tmp->list.prev);
                                return;
                        }
+
+                       /* Stop if list head is reached */
+                       if (&tmp->list == &h->chains) {
+                               debug("Insert, list head reached add to tail\n");
+                               break;
+                       }
                }
        }
 
@@ -493,6 +880,7 @@ static int cache_add_entry(STRUCT_ENTRY *e,
                        errno = -ENOMEM;
                        return -1;
                }
+               h->num_chains++; /* New user defined chain */
 
                __iptcc_p_add_chain(h, c, offset, num);
 
@@ -580,22 +968,27 @@ static int parse_table(TC_HANDLE_T h)
        ENTRY_ITERATE(h->entries->entrytable, h->entries->size,
                        cache_add_entry, h, &prev, &num);
 
+       /* Build the chain index, used for chain list search speedup */
+       if ((iptcc_chain_index_alloc(h)) < 0)
+               return -ENOMEM;
+       iptcc_chain_index_build(h);
+
        /* Second pass: fixup parsed data from first pass */
        list_for_each_entry(c, &h->chains, list) {
                struct rule_head *r;
                list_for_each_entry(r, &c->rules, list) {
-                       struct chain_head *c;
+                       struct chain_head *lc;
                        STRUCT_STANDARD_TARGET *t;
 
                        if (r->type != IPTCC_R_JUMP)
                                continue;
 
                        t = (STRUCT_STANDARD_TARGET *)GET_TARGET(r->entry);
-                       c = iptcc_find_chain_by_offset(h, t->verdict);
-                       if (!c)
+                       lc = iptcc_find_chain_by_offset(h, t->verdict);
+                       if (!lc)
                                return -1;
-                       r->jump = c;
-                       c->references++;
+                       r->jump = lc;
+                       lc->references++;
                }
        }
 
@@ -848,7 +1241,7 @@ TC_INIT(const char *tablename)
                        return NULL;
        }
        sockfd_use++;
-
+retry:
        s = sizeof(info);
 
        strcpy(info.name, tablename);
@@ -901,6 +1294,9 @@ TC_INIT(const char *tablename)
        return h;
 error:
        TC_FREE(&h);
+       /* A different process changed the ruleset size, retry */
+       if (errno == EAGAIN)
+               goto retry;
        return NULL;
 }
 
@@ -925,6 +1321,8 @@ TC_FREE(TC_HANDLE_T *h)
                free(c);
        }
 
+       iptcc_chain_index_free(*h);
+
        free((*h)->entries);
        free(*h);
 
@@ -947,7 +1345,7 @@ TC_DUMP_ENTRIES(const TC_HANDLE_T handle)
        CHECK(handle);
 
        printf("libiptc v%s. %u bytes.\n",
-              IPTABLES_VERSION, handle->entries->size);
+              XTABLES_VERSION, handle->entries->size);
        printf("Table `%s'\n", handle->info.name);
        printf("Hooks: pre/in/fwd/out/post = %u/%u/%u/%u/%u\n",
               handle->info.hook_entry[HOOK_PRE_ROUTING],
@@ -1091,7 +1489,7 @@ TC_NEXT_RULE(const STRUCT_ENTRY *prev, TC_HANDLE_T *handle)
 }
 
 /* How many rules in this chain? */
-unsigned int
+static unsigned int
 TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle)
 {
        struct chain_head *c;
@@ -1107,9 +1505,8 @@ TC_NUM_RULES(const char *chain, TC_HANDLE_T *handle)
        return c->num_rules;
 }
 
-const STRUCT_ENTRY *TC_GET_RULE(const char *chain,
-                               unsigned int n,
-                               TC_HANDLE_T *handle)
+static const STRUCT_ENTRY *
+TC_GET_RULE(const char *chain, unsigned int n, TC_HANDLE_T *handle)
 {
        struct chain_head *c;
        struct rule_head *r;
@@ -1791,6 +2188,8 @@ int
 TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
 {
        static struct chain_head *c;
+       int capacity;
+       int exceeded;
 
        iptc_fn = TC_CREATE_CHAIN;
 
@@ -1819,10 +2218,25 @@ TC_CREATE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
                return 0;
 
        }
+       (*handle)->num_chains++; /* New user defined chain */
 
        DEBUGP("Creating chain `%s'\n", chain);
        iptc_insert_chain(*handle, c); /* Insert sorted */
 
+       /* Inserting chains don't change the correctness of the chain
+        * index (except if its smaller than index[0], but that
+        * handled by iptc_insert_chain).  It only causes longer lists
+        * in the buckets. Thus, only rebuild chain index when the
+        * capacity is exceed with CHAIN_INDEX_INSERT_MAX chains.
+        */
+       capacity = (*handle)->chain_index_sz * CHAIN_INDEX_BUCKET_LEN;
+       exceeded = ((((*handle)->num_chains)-capacity));
+       if (exceeded > CHAIN_INDEX_INSERT_MAX) {
+               debug("Capacity(%d) exceeded(%d) rebuild (chains:%d)\n",
+                     capacity, exceeded, (*handle)->num_chains);
+               iptcc_chain_index_rebuild(*handle);
+       }
+
        set_changed(*handle);
 
        return 1;
@@ -1885,11 +2299,14 @@ TC_DELETE_CHAIN(const IPT_CHAINLABEL chain, TC_HANDLE_T *handle)
        }
 
        /* If we are about to delete the chain that is the current
-        * iterator, move chain iterator firward. */
+        * iterator, move chain iterator forward. */
        if (c == (*handle)->chain_iterator_cur)
                iptcc_chain_iterator_advance(*handle);
 
-       list_del(&c->list);
+       (*handle)->num_chains--; /* One user defined chain deleted */
+
+       //list_del(&c->list); /* Done in iptcc_chain_index_delete_chain() */
+       iptcc_chain_index_delete_chain(c, *handle);
        free(c);
 
        DEBUGP("chain `%s' deleted\n", chain);
@@ -1997,16 +2414,14 @@ subtract_counters(STRUCT_COUNTERS *answer,
 }
 
 
-static void counters_nomap(STRUCT_COUNTERS_INFO *newcounters,
-                          unsigned int index)
+static void counters_nomap(STRUCT_COUNTERS_INFO *newcounters, unsigned int idx)
 {
-       newcounters->counters[index] = ((STRUCT_COUNTERS) { 0, 0});
+       newcounters->counters[idx] = ((STRUCT_COUNTERS) { 0, 0});
        DEBUGP_C("NOMAP => zero\n");
 }
 
 static void counters_normal_map(STRUCT_COUNTERS_INFO *newcounters,
-                               STRUCT_REPLACE *repl,
-                               unsigned int index,
+                               STRUCT_REPLACE *repl, unsigned int idx,
                                unsigned int mappos)
 {
        /* Original read: X.
@@ -2016,15 +2431,13 @@ static void counters_normal_map(STRUCT_COUNTERS_INFO *newcounters,
         * => Add in X + Y
         * => Add in replacement read.
         */
-       newcounters->counters[index] = repl->counters[mappos];
+       newcounters->counters[idx] = repl->counters[mappos];
        DEBUGP_C("NORMAL_MAP => mappos %u \n", mappos);
 }
 
 static void counters_map_zeroed(STRUCT_COUNTERS_INFO *newcounters,
-                               STRUCT_REPLACE *repl,
-                               unsigned int index,
-                               unsigned int mappos,
-                               STRUCT_COUNTERS *counters)
+                               STRUCT_REPLACE *repl, unsigned int idx,
+                               unsigned int mappos, STRUCT_COUNTERS *counters)
 {
        /* Original read: X.
         * Atomic read on replacement: X + Y.
@@ -2033,19 +2446,18 @@ static void counters_map_zeroed(STRUCT_COUNTERS_INFO *newcounters,
         * => Add in Y.
         * => Add in (replacement read - original read).
         */
-       subtract_counters(&newcounters->counters[index],
+       subtract_counters(&newcounters->counters[idx],
                          &repl->counters[mappos],
                          counters);
        DEBUGP_C("ZEROED => mappos %u\n", mappos);
 }
 
 static void counters_map_set(STRUCT_COUNTERS_INFO *newcounters,
-                            unsigned int index,
-                            STRUCT_COUNTERS *counters)
+                             unsigned int idx, STRUCT_COUNTERS *counters)
 {
        /* Want to set counter (iptables-restore) */
 
-       memcpy(&newcounters->counters[index], counters,
+       memcpy(&newcounters->counters[idx], counters,
                sizeof(STRUCT_COUNTERS));
 
        DEBUGP_C("SET\n");
index d9f7423..3fc25b6 100644 (file)
@@ -172,11 +172,13 @@ int iptc_set_counter(const ipt_chainlabel chain,
 int iptc_commit(iptc_handle_t *handle);
 
 /* Get raw socket. */
-int iptc_get_raw_socket();
+int iptc_get_raw_socket(void);
 
 /* Translates errno numbers into more human-readable form than strerror. */
 const char *iptc_strerror(int err);
 
+extern void dump_entries(const iptc_handle_t);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/libiptc/libxtc.h b/src/libiptc/libxtc.h
new file mode 100644 (file)
index 0000000..2ed03f4
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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
+ */
+
+#ifndef _LIBXTC_H
+#define _LIBXTC_H
+/* Library which manipulates filtering rules. */
+
+#include "ipt_kernel_headers.h"
+#include <linux/netfilter/x_tables.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef XT_MIN_ALIGN
+/* xt_entry has pointers and u_int64_t's in it, so if you align to
+   it, you'll also align to any crazy matches and targets someone
+   might write */
+#define XT_MIN_ALIGN (__alignof__(struct xt_entry))
+#endif
+
+#ifndef XT_ALIGN
+#define XT_ALIGN(s) (((s) + ((XT_MIN_ALIGN)-1)) & ~((XT_MIN_ALIGN)-1))
+#endif
+
+typedef char xt_chainlabel[32];
+
+#define XTC_LABEL_ACCEPT  "ACCEPT"
+#define XTC_LABEL_DROP    "DROP"
+#define XTC_LABEL_QUEUE   "QUEUE"
+#define XTC_LABEL_RETURN  "RETURN"
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBXTC_H */
diff --git a/src/libiptc/xtables.h b/src/libiptc/xtables.h
new file mode 100644 (file)
index 0000000..47e47dc
--- /dev/null
@@ -0,0 +1,239 @@
+/**
+ * This file was imported from the iptables sources.
+ * Copyright (C) 1999-2008 Netfilter Core Team
+ *
+ * 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
+ */
+
+#ifndef _XTABLES_H
+#define _XTABLES_H
+
+#include <sys/types.h>
+#include <linux/types.h>
+#include <linux/netfilter/x_tables.h>
+#include "libxtc.h"
+#include <stdbool.h>
+
+#ifndef IPPROTO_SCTP
+#define IPPROTO_SCTP 132
+#endif
+#ifndef IPPROTO_DCCP
+#define IPPROTO_DCCP 33
+#endif
+#ifndef IPPROTO_UDPLITE
+#define IPPROTO_UDPLITE        136
+#endif
+
+#define XTABLES_VERSION "1.4.1.1"
+#define XTABLES_VERSION_CODE (0x10000 * 1 + 0x100 * 4 + 1)
+
+#define XTABLES_API_VERSION(x,y,z)    (0x10000*(x) + 0x100*(y) + z)
+
+/* Include file for additions: new matches and targets. */
+struct xtables_match
+{
+       struct xtables_match *next;
+
+       xt_chainlabel name;
+
+       /* Revision of match (0 by default). */
+       u_int8_t revision;
+
+       u_int16_t family;
+
+       const char *version;
+
+       /* Size of match data. */
+       size_t size;
+
+       /* Size of match data relevent for userspace comparison purposes */
+       size_t userspacesize;
+
+       /* Function which prints out usage message. */
+       void (*help)(void);
+
+       /* Initialize the match. */
+       void (*init)(struct xt_entry_match *m);
+
+       /* Function which parses command options; returns true if it
+           ate an option */
+       /* entry is struct ipt_entry for example */
+       int (*parse)(int c, char **argv, int invert, unsigned int *flags,
+                    const void *entry,
+                    struct xt_entry_match **match);
+
+       /* Final check; exit if not ok. */
+       void (*final_check)(unsigned int flags);
+
+       /* Prints out the match iff non-NULL: put space at end */
+       /* ip is struct ipt_ip * for example */
+       void (*print)(const void *ip,
+                     const struct xt_entry_match *match, int numeric);
+
+       /* Saves the match info in parsable form to stdout. */
+       /* ip is struct ipt_ip * for example */
+       void (*save)(const void *ip, const struct xt_entry_match *match);
+
+       /* Pointer to list of extra command-line options */
+       const struct option *extra_opts;
+
+       /* Ignore these men behind the curtain: */
+       unsigned int option_offset;
+       struct xt_entry_match *m;
+       unsigned int mflags;
+#ifdef NO_SHARED_LIBS
+       unsigned int loaded; /* simulate loading so options are merged properly */
+#endif
+};
+
+struct xtables_target
+{
+       struct xtables_target *next;
+
+       xt_chainlabel name;
+
+       /* Revision of target (0 by default). */
+       u_int8_t revision;
+
+       u_int16_t family;
+
+       const char *version;
+
+       /* Size of target data. */
+       size_t size;
+
+       /* Size of target data relevent for userspace comparison purposes */
+       size_t userspacesize;
+
+       /* Function which prints out usage message. */
+       void (*help)(void);
+
+       /* Initialize the target. */
+       void (*init)(struct xt_entry_target *t);
+
+       /* Function which parses command options; returns true if it
+           ate an option */
+       /* entry is struct ipt_entry for example */
+       int (*parse)(int c, char **argv, int invert, unsigned int *flags,
+                    const void *entry,
+                    struct xt_entry_target **targetinfo);
+
+       /* Final check; exit if not ok. */
+       void (*final_check)(unsigned int flags);
+
+       /* Prints out the target iff non-NULL: put space at end */
+       void (*print)(const void *ip,
+                     const struct xt_entry_target *target, int numeric);
+
+       /* Saves the targinfo in parsable form to stdout. */
+       void (*save)(const void *ip,
+                    const struct xt_entry_target *target);
+
+       /* Pointer to list of extra command-line options */
+       const struct option *extra_opts;
+
+       /* Ignore these men behind the curtain: */
+       unsigned int option_offset;
+       struct xt_entry_target *t;
+       unsigned int tflags;
+       unsigned int used;
+#ifdef NO_SHARED_LIBS
+       unsigned int loaded; /* simulate loading so options are merged properly */
+#endif
+};
+
+/* Your shared library should call one of these. */
+extern void xtables_register_match(struct xtables_match *me);
+extern void xtables_register_target(struct xtables_target *me);
+
+extern int string_to_number_ll(const char *s,
+                              unsigned long long min,
+                              unsigned long long max,
+                              unsigned long long *ret);
+extern int string_to_number_l(const char *s,
+                             unsigned long min,
+                             unsigned long max,
+                             unsigned long *ret);
+extern int string_to_number(const char *s,
+                           unsigned int min,
+                           unsigned int max,
+                           unsigned int *ret);
+extern bool strtonuml(const char *, char **, unsigned long *,
+       unsigned long, unsigned long);
+extern bool strtonum(const char *, char **, unsigned int *,
+       unsigned int, unsigned int);
+extern int service_to_port(const char *name, const char *proto);
+extern u_int16_t parse_port(const char *port, const char *proto);
+extern void
+parse_interface(const char *arg, char *vianame, unsigned char *mask);
+
+enum exittype {
+       OTHER_PROBLEM = 1,
+       PARAMETER_PROBLEM,
+       VERSION_PROBLEM,
+       RESOURCE_PROBLEM,
+       P_ONLY_ONCE,
+       P_NO_INVERT,
+       P_BAD_VALUE,
+       P_ONE_ACTION,
+};
+
+/* this is a special 64bit data type that is 8-byte aligned */
+#define aligned_u64 u_int64_t __attribute__((aligned(8)))
+
+int check_inverse(const char option[], int *invert, int *my_optind, int argc);
+void exit_error(enum exittype, const char *, ...)__attribute__((noreturn,
+                                                         format(printf,2,3)));
+extern void param_act(unsigned int, const char *, ...);
+extern const char *program_name, *program_version;
+
+extern const char *ipaddr_to_numeric(const struct in_addr *);
+extern const char *ipaddr_to_anyname(const struct in_addr *);
+extern const char *ipmask_to_numeric(const struct in_addr *);
+extern struct in_addr *numeric_to_ipaddr(const char *);
+extern struct in_addr *numeric_to_ipmask(const char *);
+extern void ipparse_hostnetworkmask(const char *, struct in_addr **,
+       struct in_addr *, unsigned int *);
+
+extern struct in6_addr *numeric_to_ip6addr(const char *);
+extern const char *ip6addr_to_numeric(const struct in6_addr *);
+extern const char *ip6addr_to_anyname(const struct in6_addr *);
+extern const char *ip6mask_to_numeric(const struct in6_addr *);
+extern void ip6parse_hostnetworkmask(const char *, struct in6_addr **,
+       struct in6_addr *, unsigned int *);
+
+/**
+ * Print the specified value to standard output, quoting dangerous
+ * characters if required.
+ */
+extern void save_string(const char *value);
+
+#ifdef NO_SHARED_LIBS
+#      ifdef _INIT
+#              undef _init
+#              define _init _INIT
+#      endif
+       extern void init_extensions(void);
+#else
+#      define _init __attribute__((constructor)) _INIT
+#endif
+
+/* Present in both iptables.c and ip6tables.c */
+extern u_int16_t parse_protocol(const char *s);
+
+#ifdef XTABLES_INTERNAL
+#      include <xtables/internal.h>
+#endif
+
+#endif /* _XTABLES_H */
index e1a4430..b7372a1 100644 (file)
@@ -50,7 +50,6 @@ static void load_submit (gauge_t snum, gauge_t mnum, gauge_t lnum)
 
        vl.values = values;
        vl.values_len = STATIC_ARRAY_SIZE (values);
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "load", sizeof (vl.plugin));
        sstrncpy (vl.type, "load", sizeof (vl.type));
diff --git a/src/match_regex.c b/src/match_regex.c
new file mode 100644 (file)
index 0000000..1fca889
--- /dev/null
@@ -0,0 +1,300 @@
+/**
+ * collectd - src/match_regex.c
+ * Copyright (C) 2008  Sebastian Harl
+ * Copyright (C) 2008  Florian 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:
+ *   Sebastian Harl <sh at tokkee.org>
+ *   Florian Forster <octo at verplant.org>
+ **/
+
+/*
+ * This module allows to filter and rewrite value lists based on
+ * Perl-compatible regular expressions.
+ */
+
+#include "collectd.h"
+#include "filter_chain.h"
+
+#include <sys/types.h>
+#include <regex.h>
+
+#define log_err(...) ERROR ("`regex' match: " __VA_ARGS__)
+#define log_warn(...) WARNING ("`regex' match: " __VA_ARGS__)
+
+/*
+ * private data types
+ */
+
+struct mr_regex_s;
+typedef struct mr_regex_s mr_regex_t;
+struct mr_regex_s
+{
+       regex_t re;
+       char *re_str;
+
+       mr_regex_t *next;
+};
+
+struct mr_match_s;
+typedef struct mr_match_s mr_match_t;
+struct mr_match_s
+{
+       mr_regex_t *host;
+       mr_regex_t *plugin;
+       mr_regex_t *plugin_instance;
+       mr_regex_t *type;
+       mr_regex_t *type_instance;
+};
+
+/*
+ * internal helper functions
+ */
+static void mr_free_regex (mr_regex_t *r) /* {{{ */
+{
+       if (r == NULL)
+               return;
+
+       regfree (&r->re);
+       memset (&r->re, 0, sizeof (r->re));
+       free (r->re_str);
+
+       if (r->next != NULL)
+               mr_free_regex (r->next);
+} /* }}} void mr_free_regex */
+
+static void mr_free_match (mr_match_t *m) /* {{{ */
+{
+       if (m == NULL)
+               return;
+
+       mr_free_regex (m->host);
+       mr_free_regex (m->plugin);
+       mr_free_regex (m->plugin_instance);
+       mr_free_regex (m->type);
+       mr_free_regex (m->type_instance);
+
+       free (m);
+} /* }}} void mr_free_match */
+
+static int mr_match_regexen (mr_regex_t *re_head, /* {{{ */
+               const char *string)
+{
+       mr_regex_t *re;
+
+       if (re_head == NULL)
+               return (FC_MATCH_MATCHES);
+
+       for (re = re_head; re != NULL; re = re->next)
+       {
+               int status;
+
+               status = regexec (&re->re, string,
+                               /* nmatch = */ 0, /* pmatch = */ NULL,
+                               /* eflags = */ 0);
+               if (status == 0)
+               {
+                       DEBUG ("regex match: Regular expression `%s' matches `%s'.",
+                                       re->re_str, string);
+               }
+               else
+               {
+                       DEBUG ("regex match: Regular expression `%s' does not match `%s'.",
+                                       re->re_str, string);
+                       return (FC_MATCH_NO_MATCH);
+               }
+
+       }
+
+       return (FC_MATCH_MATCHES);
+} /* }}} int mr_match_regexen */
+
+static int mr_config_add_regex (mr_regex_t **re_head, /* {{{ */
+               oconfig_item_t *ci)
+{
+       mr_regex_t *re;
+       int status;
+
+       if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+       {
+               log_warn ("`%s' needs exactly one string argument.", ci->key);
+               return (-1);
+       }
+
+       re = (mr_regex_t *) malloc (sizeof (*re));
+       if (re == NULL)
+       {
+               log_err ("mr_config_add_regex: malloc failed.");
+               return (-1);
+       }
+       memset (re, 0, sizeof (*re));
+       re->next = NULL;
+
+       re->re_str = strdup (ci->values[0].value.string);
+       if (re->re_str == NULL)
+       {
+               free (re);
+               log_err ("mr_config_add_regex: strdup failed.");
+               return (-1);
+       }
+
+       status = regcomp (&re->re, re->re_str, REG_EXTENDED | REG_NOSUB);
+       if (status != 0)
+       {
+               char errmsg[1024];
+               regerror (status, &re->re, errmsg, sizeof (errmsg));
+               errmsg[sizeof (errmsg) - 1] = 0;
+               log_err ("Compiling regex `%s' for `%s' failed: %s.", 
+                               re->re_str, ci->key, errmsg);
+               free (re->re_str);
+               free (re);
+               return (-1);
+       }
+
+       if (*re_head == NULL)
+       {
+               *re_head = re;
+       }
+       else
+       {
+               mr_regex_t *ptr;
+
+               ptr = *re_head;
+               while (ptr->next != NULL)
+                       ptr = ptr->next;
+
+               ptr->next = re;
+       }
+
+       return (0);
+} /* }}} int mr_config_add_regex */
+
+static int mr_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+       mr_match_t *m;
+       int status;
+       int i;
+
+       m = (mr_match_t *) malloc (sizeof (*m));
+       if (m == NULL)
+       {
+               log_err ("mr_create: malloc failed.");
+               return (-ENOMEM);
+       }
+       memset (m, 0, sizeof (*m));
+
+       status = 0;
+       for (i = 0; i < ci->children_num; i++)
+       {
+               oconfig_item_t *child = ci->children + i;
+
+               if ((strcasecmp ("Host", child->key) == 0)
+                               || (strcasecmp ("Hostname", child->key) == 0))
+                       status = mr_config_add_regex (&m->host, child);
+               else if (strcasecmp ("Plugin", child->key) == 0)
+                       status = mr_config_add_regex (&m->plugin, child);
+               else if (strcasecmp ("PluginInstance", child->key) == 0)
+                       status = mr_config_add_regex (&m->plugin_instance, child);
+               else if (strcasecmp ("Type", child->key) == 0)
+                       status = mr_config_add_regex (&m->type, child);
+               else if (strcasecmp ("TypeInstance", child->key) == 0)
+                       status = mr_config_add_regex (&m->type_instance, child);
+               else
+               {
+                       log_err ("The `%s' configuration option is not understood and "
+                                       "will be ignored.", child->key);
+                       status = 0;
+               }
+
+               if (status != 0)
+                       break;
+       }
+
+       /* Additional sanity-checking */
+       while (status == 0)
+       {
+               if ((m->host == NULL)
+                               && (m->plugin == NULL)
+                               && (m->plugin_instance == NULL)
+                               && (m->type == NULL)
+                               && (m->type_instance == NULL))
+               {
+                       log_err ("No (valid) regular expressions have been configured. "
+                                       "This match will be ignored.");
+                       status = -1;
+               }
+
+               break;
+       }
+
+       if (status != 0)
+       {
+               mr_free_match (m);
+               return (status);
+       }
+
+       *user_data = m;
+       return (0);
+} /* }}} int mr_create */
+
+static int mr_destroy (void **user_data) /* {{{ */
+{
+       if ((user_data != NULL) && (*user_data != NULL))
+               mr_free_match (*user_data);
+       return (0);
+} /* }}} int mr_destroy */
+
+static int mr_match (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+               const value_list_t *vl,
+               notification_meta_t __attribute__((unused)) **meta,
+               void **user_data)
+{
+       mr_match_t *m;
+
+       if ((user_data == NULL) || (*user_data == NULL))
+               return (-1);
+
+       m = *user_data;
+
+       if (mr_match_regexen (m->host, vl->host) == FC_MATCH_NO_MATCH)
+               return (FC_MATCH_NO_MATCH);
+       if (mr_match_regexen (m->plugin, vl->plugin) == FC_MATCH_NO_MATCH)
+               return (FC_MATCH_NO_MATCH);
+       if (mr_match_regexen (m->plugin_instance,
+                               vl->plugin_instance) == FC_MATCH_NO_MATCH)
+               return (FC_MATCH_NO_MATCH);
+       if (mr_match_regexen (m->type, vl->type) == FC_MATCH_NO_MATCH)
+               return (FC_MATCH_NO_MATCH);
+       if (mr_match_regexen (m->type_instance,
+                               vl->type_instance) == FC_MATCH_NO_MATCH)
+               return (FC_MATCH_NO_MATCH);
+
+       return (FC_MATCH_MATCHES);
+} /* }}} int mr_match */
+
+void module_register (void)
+{
+       match_proc_t mproc;
+
+       memset (&mproc, 0, sizeof (mproc));
+       mproc.create  = mr_create;
+       mproc.destroy = mr_destroy;
+       mproc.match   = mr_match;
+       fc_register_match ("regex", mproc);
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab fdm=marker : */
+
diff --git a/src/match_timediff.c b/src/match_timediff.c
new file mode 100644 (file)
index 0000000..4ac944a
--- /dev/null
@@ -0,0 +1,169 @@
+/**
+ * collectd - src/match_timediff.c
+ * Copyright (C) 2008,2009  Florian 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 Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "filter_chain.h"
+
+#define SATISFY_ALL 0
+#define SATISFY_ANY 1
+
+/*
+ * private data types
+ */
+struct mt_match_s;
+typedef struct mt_match_s mt_match_t;
+struct mt_match_s
+{
+  time_t future;
+  time_t past;
+};
+
+/*
+ * internal helper functions
+ */
+static int mt_config_add_time_t (time_t *ret_value, /* {{{ */
+    oconfig_item_t *ci)
+{
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    ERROR ("timediff match: `%s' needs exactly one numeric argument.",
+        ci->key);
+    return (-1);
+  }
+
+  *ret_value = (time_t) ci->values[0].value.number;
+
+  return (0);
+} /* }}} int mt_config_add_time_t */
+
+static int mt_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  mt_match_t *m;
+  int status;
+  int i;
+
+  m = (mt_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("mt_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (m, 0, sizeof (*m));
+
+  m->future = 0;
+  m->past = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Future", child->key) == 0)
+      status = mt_config_add_time_t (&m->future, child);
+    else if (strcasecmp ("Past", child->key) == 0)
+      status = mt_config_add_time_t (&m->past, child);
+    else
+    {
+      ERROR ("timediff match: The `%s' configuration option is not "
+          "understood and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((m->future == 0) && (m->past == 0))
+    {
+      ERROR ("timediff match: Either `Future' or `Past' must be configured. "
+          "This match will be ignored.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    free (m);
+    return (status);
+  }
+
+  *user_data = m;
+  return (0);
+} /* }}} int mt_create */
+
+static int mt_destroy (void **user_data) /* {{{ */
+{
+  if (user_data != NULL)
+  {
+    sfree (*user_data);
+  }
+
+  return (0);
+} /* }}} int mt_destroy */
+
+static int mt_match (const data_set_t __attribute__((unused)) *ds, /* {{{ */
+    const value_list_t *vl,
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  mt_match_t *m;
+  time_t now;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (-1);
+
+  m = *user_data;
+  now = time (NULL);
+
+  if (m->future != 0)
+  {
+    if (vl->time >= (now + m->future))
+      return (FC_MATCH_MATCHES);
+  }
+
+  if (m->past != 0)
+  {
+    if (vl->time <= (now - m->past))
+      return (FC_MATCH_MATCHES);
+  }
+
+  return (FC_MATCH_NO_MATCH);
+} /* }}} int mt_match */
+
+void module_register (void)
+{
+  match_proc_t mproc;
+
+  memset (&mproc, 0, sizeof (mproc));
+  mproc.create  = mt_create;
+  mproc.destroy = mt_destroy;
+  mproc.match   = mt_match;
+  fc_register_match ("timediff", mproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
diff --git a/src/match_value.c b/src/match_value.c
new file mode 100644 (file)
index 0000000..9f02226
--- /dev/null
@@ -0,0 +1,349 @@
+/**
+ * collectd - src/match_value.c
+ * Copyright (C) 2008  Florian 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 Forster <octo at verplant.org>
+ **/
+
+/*
+ * This module allows to filter and rewrite value lists based on
+ * Perl-compatible regular expressions.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_cache.h"
+#include "filter_chain.h"
+
+#define SATISFY_ALL 0
+#define SATISFY_ANY 1
+
+/*
+ * private data types
+ */
+struct mv_match_s;
+typedef struct mv_match_s mv_match_t;
+struct mv_match_s
+{
+  gauge_t min;
+  gauge_t max;
+  int invert;
+  int satisfy;
+
+  char **data_sources;
+  size_t data_sources_num;
+};
+
+/*
+ * internal helper functions
+ */
+static void mv_free_match (mv_match_t *m) /* {{{ */
+{
+  if (m == NULL)
+    return;
+
+  free (m);
+} /* }}} void mv_free_match */
+
+static int mv_config_add_satisfy (mv_match_t *m, /* {{{ */
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("`value' match: `%s' needs exactly one string argument.",
+        ci->key);
+    return (-1);
+  }
+
+  if (strcasecmp ("All", ci->values[0].value.string) == 0)
+    m->satisfy = SATISFY_ALL;
+  else if (strcasecmp ("Any", ci->values[0].value.string) == 0)
+    m->satisfy = SATISFY_ANY;
+  else
+  {
+    ERROR ("`value' match: Passing `%s' to the `%s' option is invalid. "
+        "The argument must either be `All' or `Any'.",
+        ci->values[0].value.string, ci->key);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int mv_config_add_satisfy */
+
+static int mv_config_add_data_source (mv_match_t *m, /* {{{ */
+    oconfig_item_t *ci)
+{
+  size_t new_data_sources_num;
+  char **temp;
+  int i;
+
+  /* Check number of arbuments. */
+  if (ci->values_num < 1)
+  {
+    ERROR ("`value' match: `%s' needs at least one argument.",
+        ci->key);
+    return (-1);
+  }
+
+  /* Check type of arguments */
+  for (i = 0; i < ci->values_num; i++)
+  {
+    if (ci->values[i].type == OCONFIG_TYPE_STRING)
+      continue;
+
+    ERROR ("`value' match: `%s' accepts only string arguments "
+        "(argument %i is a %s).",
+        ci->key, i + 1,
+        (ci->values[i].type == OCONFIG_TYPE_BOOLEAN)
+        ? "truth value" : "number");
+    return (-1);
+  }
+
+  /* Allocate space for the char pointers */
+  new_data_sources_num = m->data_sources_num + ((size_t) ci->values_num);
+  temp = (char **) realloc (m->data_sources,
+      new_data_sources_num * sizeof (char *));
+  if (temp == NULL)
+  {
+    ERROR ("`value' match: realloc failed.");
+    return (-1);
+  }
+  m->data_sources = temp;
+
+  /* Copy the strings, allocating memory as needed. */
+  for (i = 0; i < ci->values_num; i++)
+  {
+    size_t j;
+
+    /* If we get here, there better be memory for us to write to. */
+    assert (m->data_sources_num < new_data_sources_num);
+
+    j = m->data_sources_num;
+    m->data_sources[j] = sstrdup (ci->values[i].value.string);
+    if (m->data_sources[j] == NULL)
+    {
+      ERROR ("`value' match: sstrdup failed.");
+      continue;
+    }
+    m->data_sources_num++;
+  }
+
+  return (0);
+} /* }}} int mv_config_add_data_source */
+
+static int mv_config_add_gauge (gauge_t *ret_value, /* {{{ */
+    oconfig_item_t *ci)
+{
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    ERROR ("`value' match: `%s' needs exactly one numeric argument.",
+        ci->key);
+    return (-1);
+  }
+
+  *ret_value = ci->values[0].value.number;
+
+  return (0);
+} /* }}} int mv_config_add_gauge */
+
+static int mv_config_add_boolean (int *ret_value, /* {{{ */
+    oconfig_item_t *ci)
+{
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    ERROR ("`value' match: `%s' needs exactly one boolean argument.",
+        ci->key);
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    *ret_value = 1;
+  else
+    *ret_value = 0;
+
+  return (0);
+} /* }}} int mv_config_add_boolean */
+
+static int mv_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  mv_match_t *m;
+  int status;
+  int i;
+
+  m = (mv_match_t *) malloc (sizeof (*m));
+  if (m == NULL)
+  {
+    ERROR ("mv_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (m, 0, sizeof (*m));
+
+  m->min = NAN;
+  m->max = NAN;
+  m->invert = 0;
+  m->satisfy = SATISFY_ALL;
+  m->data_sources = NULL;
+  m->data_sources_num = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Min", child->key) == 0)
+      status = mv_config_add_gauge (&m->min, child);
+    else if (strcasecmp ("Max", child->key) == 0)
+      status = mv_config_add_gauge (&m->max, child);
+    else if (strcasecmp ("Invert", child->key) == 0)
+      status = mv_config_add_boolean (&m->invert, child);
+    else if (strcasecmp ("Satisfy", child->key) == 0)
+      status = mv_config_add_satisfy (m, child);
+    else if (strcasecmp ("DataSource", child->key) == 0)
+      status = mv_config_add_data_source (m, child);
+    else
+    {
+      ERROR ("`value' match: The `%s' configuration option is not "
+          "understood and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if (isnan (m->min) && isnan (m->max))
+    {
+      ERROR ("`value' match: Neither minimum nor maximum are defined. "
+          "This match will be ignored.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    mv_free_match (m);
+    return (status);
+  }
+
+  *user_data = m;
+  return (0);
+} /* }}} int mv_create */
+
+static int mv_destroy (void **user_data) /* {{{ */
+{
+  if ((user_data != NULL) && (*user_data != NULL))
+    mv_free_match (*user_data);
+  return (0);
+} /* }}} int mv_destroy */
+
+static int mv_match (const data_set_t *ds, const value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  mv_match_t *m;
+  gauge_t *values;
+  int status;
+  int i;
+
+  if ((user_data == NULL) || (*user_data == NULL))
+    return (-1);
+
+  m = *user_data;
+
+  values = uc_get_rate (ds, vl);
+  if (values == NULL)
+  {
+    ERROR ("`value' match: Retrieving the current rate from the cache "
+        "failed.");
+    return (-1);
+  }
+
+  status = FC_MATCH_NO_MATCH;
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    int value_matches = 0;
+
+    /* Check if this data source is relevant. */
+    if (m->data_sources != NULL)
+    {
+      size_t j;
+
+      for (j = 0; j < m->data_sources_num; j++)
+        if (strcasecmp (ds->ds[i].name, m->data_sources[j]) == 0)
+          break;
+
+      /* No match, ignore this data source. */
+      if (j >=  m->data_sources_num)
+        continue;
+    }
+
+    DEBUG ("`value' match: current = %g; min = %g; max = %g; invert = %s;",
+        values[i], m->min, m->max,
+        m->invert ? "true" : "false");
+
+    if ((!isnan (m->min) && (values[i] < m->min))
+        || (!isnan (m->max) && (values[i] > m->max)))
+      value_matches = 0;
+    else
+      value_matches = 1;
+
+    if (m->invert)
+    {
+      if (value_matches)
+        value_matches = 0;
+      else
+        value_matches = 1;
+    }
+
+    if (value_matches != 0)
+    {
+      status = FC_MATCH_MATCHES;
+      if (m->satisfy == SATISFY_ANY)
+        break;
+    }
+    else if (value_matches == 0)
+    {
+      status = FC_MATCH_NO_MATCH;
+      if (m->satisfy == SATISFY_ALL)
+        break;
+    }
+  } /* for (i = 0; i < ds->ds_num; i++) */
+
+  free (values);
+  return (status);
+} /* }}} int mv_match */
+
+void module_register (void)
+{
+  match_proc_t mproc;
+
+  memset (&mproc, 0, sizeof (mproc));
+  mproc.create  = mv_create;
+  mproc.destroy = mv_destroy;
+  mproc.match   = mv_match;
+  fc_register_match ("value", mproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
index 43590a8..90226bb 100644 (file)
@@ -229,7 +229,6 @@ static void mbmon_submit (const char *type, const char *type_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "mbmon", sizeof (vl.plugin));
        sstrncpy (vl.type, type, sizeof (vl.type));
index 23054aa..5af2091 100644 (file)
@@ -235,7 +235,6 @@ static void submit_counter (const char *type, const char *type_inst,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "memcached", sizeof (vl.plugin));
        sstrncpy (vl.type, type, sizeof (vl.type));
@@ -322,6 +321,8 @@ static int memcached_read (void) /* {{{ */
 
        gauge_t bytes_used = NAN;
        gauge_t bytes_total = NAN;
+       gauge_t hits = NAN;
+       gauge_t gets = NAN;
        counter_t rusage_user = 0;
        counter_t rusage_syst = 0;
        counter_t octets_rx = 0;
@@ -431,6 +432,8 @@ static int memcached_read (void) /* {{{ */
                {
                        const char *name = fields[1] + 4;
                        submit_counter ("memcached_command", name, atoll (fields[2]));
+                       if (strcmp (name, "get") == 0)
+                               gets = atof (fields[2]);
                }
 
                /*
@@ -439,6 +442,7 @@ static int memcached_read (void) /* {{{ */
                else if (FIELD_IS ("get_hits"))
                {
                        submit_counter ("memcached_ops", "hits", atoll (fields[2]));
+                       hits = atof (fields[2]);
                }
                else if (FIELD_IS ("get_misses"))
                {
@@ -471,6 +475,16 @@ static int memcached_read (void) /* {{{ */
        if ((octets_rx != 0) || (octets_tx != 0))
                submit_counter2 ("memcached_octets", NULL, octets_rx, octets_tx);
        
+       if (!isnan (gets) && !isnan (hits))
+       {
+               gauge_t rate = NAN;
+
+               if (gets != 0.0)
+                       rate = 100.0 * hits / gets;
+
+               submit_gauge ("percent", "hitratio", rate);
+       }
+
        return 0;
 }
 /* }}} */
index 991726c..6ea6205 100644 (file)
@@ -111,7 +111,6 @@ static void memory_submit (const char *type_instance, gauge_t value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "memory", sizeof (vl.plugin));
        sstrncpy (vl.type, "memory", sizeof (vl.type));
diff --git a/src/meta_data.c b/src/meta_data.c
new file mode 100644 (file)
index 0000000..2c085e3
--- /dev/null
@@ -0,0 +1,498 @@
+/**
+ * collectd - src/meta_data.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "meta_data.h"
+
+#include <pthread.h>
+
+/*
+ * Defines
+ */
+#define MD_TYPE_STRING       1
+#define MD_TYPE_SIGNED_INT   2
+#define MD_TYPE_UNSIGNED_INT 3
+#define MD_TYPE_DOUBLE       4
+
+/*
+ * Data types
+ */
+union meta_value_u
+{
+  char    *mv_string;
+  int64_t  mv_signed_int;
+  uint64_t mv_unsigned_int;
+  double   mv_double;
+};
+typedef union meta_value_u meta_value_t;
+
+struct meta_entry_s;
+typedef struct meta_entry_s meta_entry_t;
+struct meta_entry_s
+{
+  char         *key;
+  meta_value_t  value;
+  int           type;
+  meta_entry_t *next;
+};
+
+struct meta_data_s
+{
+  meta_entry_t   *head;
+  pthread_mutex_t lock;
+};
+
+/*
+ * Private functions
+ */
+static char *md_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *md_strdup */
+
+static meta_entry_t *md_entry_alloc (const char *key) /* {{{ */
+{
+  meta_entry_t *e;
+
+  e = (meta_entry_t *) malloc (sizeof (*e));
+  if (e == NULL)
+  {
+    ERROR ("md_entry_alloc: malloc failed.");
+    return (NULL);
+  }
+  memset (e, 0, sizeof (*e));
+
+  e->key = md_strdup (key);
+  if (e->key == NULL)
+  {
+    free (e);
+    ERROR ("md_entry_alloc: md_strdup failed.");
+    return (NULL);
+  }
+
+  e->type = 0;
+  e->next = NULL;
+
+  return (e);
+} /* }}} meta_entry_t *md_entry_alloc */
+
+static void md_entry_free (meta_entry_t *e) /* {{{ */
+{
+  if (e == NULL)
+    return;
+
+  free (e->key);
+
+  if (e->type == MD_TYPE_STRING)
+    free (e->value.mv_string);
+
+  if (e->next != NULL)
+    md_entry_free (e->next);
+
+  free (e);
+} /* }}} void md_entry_free */
+
+static int md_entry_insert (meta_data_t *md, meta_entry_t *e) /* {{{ */
+{
+  meta_entry_t *this;
+  meta_entry_t *prev;
+
+  if ((md == NULL) || (e == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  prev = NULL;
+  this = md->head;
+  while (this != NULL)
+  {
+    if (strcasecmp (e->key, this->key) == 0)
+      break;
+
+    prev = this;
+    this = this->next;
+  }
+
+  if (this == NULL)
+  {
+    /* This key does not exist yet. */
+    if (md->head == NULL)
+      md->head = e;
+    else
+    {
+      assert (prev != NULL);
+      prev->next = e;
+    }
+
+    e->next = NULL;
+  }
+  else /* (this != NULL) */
+  {
+    if (prev == NULL)
+      md->head = e;
+    else
+      prev->next = e;
+
+    e->next = this->next;
+  }
+
+  pthread_mutex_unlock (&md->lock);
+
+  if (this != NULL)
+  {
+    this->next = NULL;
+    md_entry_free (this);
+  }
+
+  return (0);
+} /* }}} int md_entry_insert */
+
+/* XXX: The lock on md must be held while calling this function! */
+static meta_entry_t *md_entry_lookup (meta_data_t *md, /* {{{ */
+    const char *key)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (NULL);
+
+  for (e = md->head; e != NULL; e = e->next)
+    if (strcasecmp (key, e->key) == 0)
+      break;
+
+  return (e);
+} /* }}} meta_entry_t *md_entry_lookup */
+
+/*
+ * Public functions
+ */
+meta_data_t *meta_data_create (void) /* {{{ */
+{
+  meta_data_t *md;
+
+  md = (meta_data_t *) malloc (sizeof (*md));
+  if (md == NULL)
+  {
+    ERROR ("meta_data_create: malloc failed.");
+    return (NULL);
+  }
+  memset (md, 0, sizeof (*md));
+
+  md->head = NULL;
+  pthread_mutex_init (&md->lock, /* attr = */ NULL);
+
+  return (md);
+} /* }}} meta_data_t *meta_data_create */
+
+void meta_data_destroy (meta_data_t *md) /* {{{ */
+{
+  if (md == NULL)
+    return;
+
+  md_entry_free (md->head);
+  free (md);
+} /* }}} void meta_data_destroy */
+
+int meta_data_exists (meta_data_t *md, const char *key) /* {{{ */
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  for (e = md->head; e != NULL; e = e->next)
+  {
+    if (strcasecmp (key, e->key) == 0)
+    {
+      pthread_mutex_unlock (&md->lock);
+      return (1);
+    }
+  }
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_exists */
+
+int meta_data_delete (meta_data_t *md, const char *key) /* {{{ */
+{
+  meta_entry_t *this;
+  meta_entry_t *prev;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  prev = NULL;
+  this = md->head;
+  while (this != NULL)
+  {
+    if (strcasecmp (key, this->key) == 0)
+      break;
+
+    prev = this;
+    this = this->next;
+  }
+
+  if (this == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  if (prev == NULL)
+    md->head = this->next;
+  else
+    prev->next = this->next;
+
+  pthread_mutex_unlock (&md->lock);
+
+  this->next = NULL;
+  md_entry_free (this);
+
+  return (0);
+} /* }}} int meta_data_delete */
+
+int meta_data_add_string (meta_data_t *md, /* {{{ */
+    const char *key, const char *value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL) || (value == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_string = md_strdup (value);
+  if (e->value.mv_string == NULL)
+  {
+    ERROR ("meta_data_add_string: md_strdup failed.");
+    md_entry_free (e);
+    return (-ENOMEM);
+  }
+  e->type = MD_TYPE_STRING;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_string */
+
+int meta_data_add_signed_int (meta_data_t *md, /* {{{ */
+    const char *key, int64_t value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_signed_int = value;
+  e->type = MD_TYPE_SIGNED_INT;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_signed_int */
+
+int meta_data_add_unsigned_int (meta_data_t *md, /* {{{ */
+    const char *key, uint64_t value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_unsigned_int = value;
+  e->type = MD_TYPE_UNSIGNED_INT;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_unsigned_int */
+
+int meta_data_add_double (meta_data_t *md, /* {{{ */
+    const char *key, double value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL))
+    return (-EINVAL);
+
+  e = md_entry_alloc (key);
+  if (e == NULL)
+    return (-ENOMEM);
+
+  e->value.mv_double = value;
+  e->type = MD_TYPE_DOUBLE;
+
+  return (md_entry_insert (md, e));
+} /* }}} int meta_data_add_double */
+
+int meta_data_get_string (meta_data_t *md, /* {{{ */
+    const char *key, char **value)
+{
+  meta_entry_t *e;
+  char *temp;
+
+  if ((md == NULL) || (key == NULL) || (value == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  e = md_entry_lookup (md, key);
+  if (e == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  if (e->type != MD_TYPE_SIGNED_INT)
+  {
+    ERROR ("meta_data_get_signed_int: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  temp = md_strdup (e->value.mv_string);
+  if (temp == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    ERROR ("meta_data_get_string: md_strdup failed.");
+    return (-ENOMEM);
+  }
+  pthread_mutex_unlock (&md->lock);
+
+  *value = temp;
+
+  return (0);
+} /* }}} int meta_data_get_string */
+
+int meta_data_get_signed_int (meta_data_t *md, /* {{{ */
+    const char *key, int64_t *value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL) || (value == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  e = md_entry_lookup (md, key);
+  if (e == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  if (e->type != MD_TYPE_SIGNED_INT)
+  {
+    ERROR ("meta_data_get_signed_int: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  *value = e->value.mv_signed_int;
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_get_signed_int */
+
+int meta_data_get_unsigned_int (meta_data_t *md, /* {{{ */
+    const char *key, uint64_t *value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL) || (value == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  e = md_entry_lookup (md, key);
+  if (e == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  if (e->type != MD_TYPE_UNSIGNED_INT)
+  {
+    ERROR ("meta_data_get_unsigned_int: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  *value = e->value.mv_unsigned_int;
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_get_unsigned_int */
+
+int meta_data_get_double (meta_data_t *md, /* {{{ */
+    const char *key, double *value)
+{
+  meta_entry_t *e;
+
+  if ((md == NULL) || (key == NULL) || (value == NULL))
+    return (-EINVAL);
+
+  pthread_mutex_lock (&md->lock);
+
+  e = md_entry_lookup (md, key);
+  if (e == NULL)
+  {
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  if (e->type != MD_TYPE_DOUBLE)
+  {
+    ERROR ("meta_data_get_double: Type mismatch for key `%s'", e->key);
+    pthread_mutex_unlock (&md->lock);
+    return (-ENOENT);
+  }
+
+  *value = e->value.mv_double;
+
+  pthread_mutex_unlock (&md->lock);
+  return (0);
+} /* }}} int meta_data_get_double */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/meta_data.h b/src/meta_data.h
new file mode 100644 (file)
index 0000000..30e7285
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * collectd - src/meta_data.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef META_DATA_H
+#define META_DATA_H
+
+#include "collectd.h"
+
+struct meta_data_s;
+typedef struct meta_data_s meta_data_t;
+
+meta_data_t *meta_data_create (void);
+void meta_data_destroy (meta_data_t *md);
+
+int meta_data_exists (meta_data_t *md, const char *key);
+int meta_data_delete (meta_data_t *md, const char *key);
+
+int meta_data_add_string (meta_data_t *md,
+    const char *key,
+    const char *value);
+int meta_data_add_signed_int (meta_data_t *md,
+    const char *key,
+    int64_t value);
+int meta_data_add_unsigned_int (meta_data_t *md,
+    const char *key,
+    uint64_t value);
+int meta_data_add_double (meta_data_t *md,
+    const char *key,
+    double value);
+
+int meta_data_get_string (meta_data_t *md,
+    const char *key,
+    char **value);
+int meta_data_get_signed_int (meta_data_t *md,
+    const char *key,
+    int64_t *value);
+int meta_data_get_unsigned_int (meta_data_t *md,
+    const char *key,
+    uint64_t *value);
+int meta_data_get_double (meta_data_t *md,
+    const char *key,
+    double *value);
+
+#endif /* META_DATA_H */
+/* vim: set sw=2 sts=2 et : */
index 9c9c2c1..775eb57 100644 (file)
 
 static int fd = -1;
 
-static int multimeter_timeval_sub (struct timeval *tv1, struct timeval *tv2,
-                struct timeval *res)
-{
-        if ((tv1->tv_sec < tv2->tv_sec) ||
-           ((tv1->tv_sec == tv2->tv_sec) && (tv1->tv_usec < tv2->tv_usec)))
-               return (-1);
-
-        res->tv_sec  = tv1->tv_sec  - tv2->tv_sec;
-        res->tv_usec = tv1->tv_usec - tv2->tv_usec;
-
-        assert ((res->tv_sec > 0) || ((res->tv_sec == 0) && (res->tv_usec > 0)));
-
-        while (res->tv_usec < 0)
-        {
-               res->tv_usec += 1000000;
-                res->tv_sec--;
-        }
-       return (0);
-}
-
 #define LINE_LENGTH 14
 static int multimeter_read_value(double *value)
 {
@@ -105,7 +85,7 @@ static int multimeter_read_value(double *value)
                                                        sizeof (errbuf)));
                                return (-1);
                        }
-                       if (multimeter_timeval_sub (&time_end, &time_now, &timeout) == -1)
+                       if (timeval_cmp (time_end, time_now, &timeout) < 0)
                                break;
 
                        status = select(fd+1, &rfds, NULL, NULL, &timeout);
@@ -220,7 +200,6 @@ static void multimeter_submit (double value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "multimeter", sizeof (vl.plugin));
        sstrncpy (vl.type, "multimeter", sizeof (vl.type));
index 4e72b5b..cd1ff9d 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/mysql.c
- * Copyright (C) 2006,2007  Florian octo Forster
+ * Copyright (C) 2006–2008  Florian octo Forster
+ * Copyright (C) 2008       Mirko Buffoni
  *
  * 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
@@ -17,6 +18,7 @@
  *
  * Authors:
  *   Florian octo Forster <octo at verplant.org>
+ *   Mirko Buffoni <briareos at eswat.org>
  **/
 
 #include "collectd.h"
@@ -38,14 +40,18 @@ static const char *config_keys[] =
        "User",
        "Password",
        "Database",
+       "Port",
+       "Socket",
        NULL
 };
-static int config_keys_num = 4;
+static int config_keys_num = 6;
 
 static char *host = "localhost";
 static char *user;
 static char *pass;
 static char *db = NULL;
+static char *socket = NULL;
+static int   port = 0;
 
 static MYSQL *getconnection (void)
 {
@@ -88,7 +94,7 @@ static MYSQL *getconnection (void)
                return (NULL);
        }
 
-       if (mysql_real_connect (con, host, user, pass, db, 0, NULL, 0) == NULL)
+       if (mysql_real_connect (con, host, user, pass, db, port, socket, 0) == NULL)
        {
                ERROR ("mysql_real_connect failed: %s", mysql_error (con));
                state = 0;
@@ -113,9 +119,36 @@ static int config (const char *key, const char *value)
                return ((pass = strdup (value)) == NULL ? 1 : 0);
        else if (strcasecmp (key, "database") == 0)
                return ((db = strdup (value)) == NULL ? 1 : 0);
+       else if (strcasecmp (key, "socket") == 0)
+               return ((socket = strdup (value)) == NULL ? 1 : 0);
+       else if (strcasecmp (key, "port") == 0)
+       {
+           char *endptr = NULL;
+           int temp;
+
+           errno = 0;
+           temp = strtol (value, &endptr, 0);
+           if ((errno != 0) || (value == endptr))
+           {
+               ERROR ("mysql plugin: Invalid \"Port\" argument: %s",
+                       value);
+               port = 0;
+               return (1);
+           }
+           else if ((temp < 0) || (temp >= 65535))
+           {
+               ERROR ("mysql plugin: Port number out of range: %i",
+                       temp);
+               port = 0;
+               return (1);
+           }
+
+           port = temp;
+           return (0);
+       }
        else
                return (-1);
-}
+} /* int config */
 
 static void counter_submit (const char *type, const char *type_instance,
                counter_t value)
@@ -127,7 +160,6 @@ static void counter_submit (const char *type, const char *type_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
        sstrncpy (vl.type, type, sizeof (vl.type));
@@ -151,7 +183,6 @@ static void qcache_submit (counter_t hits, counter_t inserts,
 
        vl.values = values;
        vl.values_len = 5;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
        sstrncpy (vl.type, "mysql_qcache", sizeof (vl.type));
@@ -172,7 +203,6 @@ static void threads_submit (gauge_t running, gauge_t connected, gauge_t cached,
 
        vl.values = values;
        vl.values_len = 4;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
        sstrncpy (vl.type, "mysql_threads", sizeof (vl.type));
@@ -190,7 +220,6 @@ static void traffic_submit (counter_t rx, counter_t tx)
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "mysql", sizeof (vl.plugin));
        sstrncpy (vl.type, "mysql_octets", sizeof (vl.type));
index d14e510..b15768e 100644 (file)
@@ -170,7 +170,6 @@ static void submit_one (const char *dev, const char *type,
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "netlink", sizeof (vl.plugin));
   sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
@@ -194,7 +193,6 @@ static void submit_two (const char *dev, const char *type,
 
   vl.values = values;
   vl.values_len = 2;
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "netlink", sizeof (vl.plugin));
   sstrncpy (vl.plugin_instance, dev, sizeof (vl.plugin_instance));
@@ -206,8 +204,8 @@ static void submit_two (const char *dev, const char *type,
   plugin_dispatch_values (&vl);
 } /* void submit_two */
 
-static int link_filter (const struct sockaddr_nl *sa,
-    struct nlmsghdr *nmh, void *args)
+static int link_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
+    struct nlmsghdr *nmh, void __attribute__((unused)) *args)
 {
   struct ifinfomsg *msg;
   int msg_len;
@@ -248,7 +246,7 @@ static int link_filter (const struct sockaddr_nl *sa,
 
   /* Update the `iflist'. It's used to know which interfaces exist and query
    * them later for qdiscs and classes. */
-  if (msg->ifi_index >= iflist_len)
+  if ((msg->ifi_index >= 0) && ((size_t) msg->ifi_index >= iflist_len))
   {
     char **temp;
 
@@ -316,7 +314,7 @@ static int link_filter (const struct sockaddr_nl *sa,
   return (0);
 } /* int link_filter */
 
-static int qos_filter (const struct sockaddr_nl *sa,
+static int qos_filter (const struct sockaddr_nl __attribute__((unused)) *sa,
     struct nlmsghdr *nmh, void *args)
 {
   struct tcmsg *msg;
@@ -361,7 +359,8 @@ static int qos_filter (const struct sockaddr_nl *sa,
     return (0);
   }
 
-  if (msg->tcm_ifindex >= iflist_len)
+  if ((msg->tcm_ifindex >= 0)
+      && ((size_t) msg->tcm_ifindex >= iflist_len))
   {
     ERROR ("netlink plugin: qos_filter: msg->tcm_ifindex = %i "
        ">= iflist_len = %zu",
@@ -582,9 +581,9 @@ static int ir_read (void)
 
   /* `link_filter' will update `iflist' which is used here to iterate over all
    * interfaces. */
-  for (ifindex = 0; ifindex < iflist_len; ifindex++)
+  for (ifindex = 0; (size_t) ifindex < iflist_len; ifindex++)
   {
-    int type_index;
+    size_t type_index;
 
     if (iflist[ifindex] == NULL)
       continue;
index c996b07..902f270 100644 (file)
@@ -498,7 +498,7 @@ static int parse_part_values (void **ret_buffer, int *ret_buffer_len,
 
        exp_size = 3 * sizeof (uint16_t)
                + pkg_numval * (sizeof (uint8_t) + sizeof (value_t));
-       if (buffer_len < exp_size)
+       if ((buffer_len < 0) || ((size_t) buffer_len < exp_size))
        {
                WARNING ("network plugin: parse_part_values: "
                                "Packet too short: "
@@ -562,7 +562,7 @@ static int parse_part_number (void **ret_buffer, int *ret_buffer_len,
        uint16_t pkg_length;
        uint16_t pkg_type;
 
-       if (buffer_len < exp_size)
+       if ((buffer_len < 0) || ((size_t) buffer_len < exp_size))
        {
                WARNING ("network plugin: parse_part_number: "
                                "Packet too short: "
@@ -602,7 +602,7 @@ static int parse_part_string (void **ret_buffer, int *ret_buffer_len,
        uint16_t pkg_length;
        uint16_t pkg_type;
 
-       if (buffer_len < header_size)
+       if ((buffer_len < 0) || ((size_t) buffer_len < header_size))
        {
                WARNING ("network plugin: parse_part_string: "
                                "Packet too short: "
@@ -644,7 +644,8 @@ static int parse_part_string (void **ret_buffer, int *ret_buffer_len,
        /* Check that the package data fits into the output buffer.
         * The previous if-statement ensures that:
         * `pkg_length > header_size' */
-       if ((pkg_length - header_size) > output_len)
+       if ((output_len < 0)
+                       || ((size_t) output_len < ((size_t) pkg_length - header_size)))
        {
                WARNING ("network plugin: parse_part_string: "
                                "Output buffer too small.");
@@ -1241,7 +1242,7 @@ static int network_add_sending_socket (const char *node, const char *service)
        return (0);
 } /* int network_get_listen_socket */
 
-static void *dispatch_thread (void *arg)
+static void *dispatch_thread (void __attribute__((unused)) *arg)
 {
   while (42)
   {
@@ -1280,6 +1281,9 @@ static int network_receive (void)
        int i;
        int status;
 
+       receive_list_entry_t *private_list_head;
+       receive_list_entry_t *private_list_tail;
+
        if (listen_sockets_num == 0)
                network_add_listen_socket (NULL, NULL);
 
@@ -1289,6 +1293,9 @@ static int network_receive (void)
                return (-1);
        }
 
+       private_list_head = NULL;
+       private_list_tail = NULL;
+
        while (listen_loop == 0)
        {
                status = poll (listen_sockets, listen_sockets_num, -1);
@@ -1329,7 +1336,8 @@ static int network_receive (void)
                                ERROR ("network plugin: malloc failed.");
                                return (-1);
                        }
-                       memset (ent, '\0', sizeof (receive_list_entry_t));
+                       memset (ent, 0, sizeof (receive_list_entry_t));
+                       ent->next = NULL;
 
                        /* Hopefully this be optimized out by the compiler. It
                         * might help prevent stupid bugs in the future though.
@@ -1339,26 +1347,53 @@ static int network_receive (void)
                        memcpy (ent->data, buffer, buffer_len);
                        ent->data_len = buffer_len;
 
-                       pthread_mutex_lock (&receive_list_lock);
-                       if (receive_list_head == NULL)
-                       {
-                               receive_list_head = ent;
-                               receive_list_tail = ent;
-                       }
+                       if (private_list_head == NULL)
+                               private_list_head = ent;
                        else
+                               private_list_tail->next = ent;
+                       private_list_tail = ent;
+
+                       /* Do not block here. Blocking here has led to
+                        * insufficient performance in the past. */
+                       if (pthread_mutex_trylock (&receive_list_lock) == 0)
                        {
-                               receive_list_tail->next = ent;
-                               receive_list_tail = ent;
+                               if (receive_list_head == NULL)
+                                       receive_list_head = private_list_head;
+                               else
+                                       receive_list_tail->next = private_list_head;
+                               receive_list_tail = private_list_tail;
+
+                               private_list_head = NULL;
+                               private_list_tail = NULL;
+
+                               pthread_cond_signal (&receive_list_cond);
+                               pthread_mutex_unlock (&receive_list_lock);
                        }
-                       pthread_cond_signal (&receive_list_cond);
-                       pthread_mutex_unlock (&receive_list_lock);
                } /* for (listen_sockets) */
        } /* while (listen_loop == 0) */
 
+       /* Make sure everything is dispatched before exiting. */
+       if (private_list_head != NULL)
+       {
+               pthread_mutex_lock (&receive_list_lock);
+
+               if (receive_list_head == NULL)
+                       receive_list_head = private_list_head;
+               else
+                       receive_list_tail->next = private_list_head;
+               receive_list_tail = private_list_tail;
+
+               private_list_head = NULL;
+               private_list_tail = NULL;
+
+               pthread_cond_signal (&receive_list_cond);
+               pthread_mutex_unlock (&receive_list_lock);
+       }
+
        return (0);
 }
 
-static void *receive_thread (void *arg)
+static void *receive_thread (void __attribute__((unused)) *arg)
 {
        return (network_receive () ? (void *) 1 : (void *) 0);
 } /* void *receive_thread */
@@ -1775,7 +1810,8 @@ static int network_init (void)
  * just send the buffer if `flush'  is called - if the requested value was in
  * there, good. If not, well, then there is nothing to flush.. -octo
  */
-static int network_flush (int timeout, const char *identifier)
+static int network_flush (int timeout,
+               const char __attribute__((unused)) *identifier)
 {
        pthread_mutex_lock (&send_buffer_lock);
 
index f2db895..e4d12e3 100644 (file)
--- a/src/nfs.c
+++ b/src/nfs.c
@@ -187,7 +187,6 @@ static void nfs_procedures_submit (const char *plugin_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "nfs", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance,
index ea5c126..cea3c84 100644 (file)
@@ -54,7 +54,8 @@ static const char *config_keys[] =
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
-static size_t nginx_curl_callback (void *buf, size_t size, size_t nmemb, void *stream)
+static size_t nginx_curl_callback (void *buf, size_t size, size_t nmemb,
+    void __attribute__((unused)) *stream)
 {
   size_t len = size * nmemb;
 
@@ -124,8 +125,9 @@ static int init (void)
 
   if (user != NULL)
   {
-    if (ssnprintf (credentials, sizeof (credentials),
-         "%s:%s", user, pass == NULL ? "" : pass) >= sizeof (credentials))
+    int status = ssnprintf (credentials, sizeof (credentials),
+       "%s:%s", user, pass == NULL ? "" : pass);
+    if ((status < 0) || ((size_t) status >= sizeof (credentials)))
     {
       ERROR ("nginx plugin: Credentials would have been truncated.");
       return (-1);
@@ -179,7 +181,6 @@ static void submit (char *type, char *inst, long long value)
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "nginx", sizeof (vl.plugin));
   sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
index dc4e47f..32bd916 100644 (file)
@@ -61,7 +61,7 @@ static char *email_subject = NULL;
 
 /* Callback to get username and password */
 static int authinteract (auth_client_request_t request, char **result,
-    int fields, void *arg)
+    int fields, void __attribute__((unused)) *arg)
 {               
   int i;
   for (i = 0; i < fields; i++)
@@ -78,7 +78,7 @@ static int authinteract (auth_client_request_t request, char **result,
 
 /* Callback to print the recipient status */
 static void print_recipient_status (smtp_recipient_t recipient,
-    const char *mailbox, void *arg)
+    const char *mailbox, void __attribute__((unused)) *arg)
 {
   const smtp_status_t *status;
 
@@ -90,7 +90,8 @@ static void print_recipient_status (smtp_recipient_t recipient,
 } /* void print_recipient_status */
 
 /* Callback to monitor SMTP activity */
-static void monitor_cb (const char *buf, int buflen, int writing, void *arg)
+static void monitor_cb (const char *buf, int buflen, int writing,
+    void __attribute__((unused)) *arg)
 {
   char log_str[MAXSTRING];
 
index 4bc3139..f51ef5a 100644 (file)
@@ -300,7 +300,6 @@ static void ntpd_submit (char *type, char *type_inst, double value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "ntpd", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
@@ -310,31 +309,6 @@ static void ntpd_submit (char *type, char *type_inst, double value)
        plugin_dispatch_values (&vl);
 }
 
-/* returns `tv0 - tv1' in milliseconds or 0 if `tv1 > tv0' */
-static int timeval_sub (const struct timeval *tv0, const struct timeval *tv1)
-{
-       int sec;
-       int usec;
-
-       if ((tv0->tv_sec < tv1->tv_sec)
-                       || ((tv0->tv_sec == tv1->tv_sec) && (tv0->tv_usec < tv1->tv_usec)))
-               return (0);
-
-       sec  = tv0->tv_sec  - tv1->tv_sec;
-       usec = tv0->tv_usec - tv1->tv_usec;
-
-       while (usec < 0)
-       {
-               usec += 1000000;
-               sec  -= 1;
-       }
-
-       if (sec < 0)
-               return (0);
-
-       return ((sec * 1000) + ((usec + 500) / 1000));
-}
-
 static int ntpd_connect (void)
 {
        char *host;
@@ -459,6 +433,8 @@ static int ntpd_receive_response (int *res_items, int *res_size,
        done = 0;
        while (done == 0)
        {
+               struct timeval time_left;
+
                if (gettimeofday (&time_now, NULL) < 0)
                {
                        char errbuf[1024];
@@ -467,8 +443,14 @@ static int ntpd_receive_response (int *res_items, int *res_size,
                        return (-1);
                }
 
+               if (timeval_cmp (time_end, time_now, &time_left) <= 0)
+                       timeout = 0;
+               else
+                       timeout = 1000 * time_left.tv_sec
+                               + ((time_left.tv_usec + 500) / 1000);
+
                /* timeout reached */
-               if ((timeout = timeval_sub (&time_end, &time_now)) == 0)
+               if (timeout <= 0)
                        break;
 
                poll_s.fd      = sd;
index 8796d58..edc48c6 100644 (file)
--- a/src/nut.c
+++ b/src/nut.c
@@ -123,7 +123,6 @@ static void nut_submit (nut_ups_t *ups, const char *type,
 
   vl.values = values;
   vl.values_len = STATIC_ARRAY_SIZE (values);
-  vl.time = time (NULL);
   sstrncpy (vl.host,
       (strcasecmp (ups->hostname, "localhost") == 0)
       ? hostname_g
index dd7f527..c40d5ad 100644 (file)
@@ -149,7 +149,6 @@ static int cow_read_values (const char *path, const char *name,
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
 
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "onewire", sizeof (vl.plugin));
diff --git a/src/openvpn.c b/src/openvpn.c
new file mode 100644 (file)
index 0000000..9f386f0
--- /dev/null
@@ -0,0 +1,235 @@
+/**
+ * collectd - src/openvpn.c
+ * Copyright (C) 2008  Doug MacEachern
+ * 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:
+ *   Doug MacEachern <dougm at hyperic.com>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#define DEFAULT_STATUS_FILE "/etc/openvpn/openvpn-status.log"
+#define CLIENT_LIST_PREFIX  "CLIENT_LIST,"
+
+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;
+
+static const char *config_keys[] =
+{
+       "StatusFile"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+/* 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);
+} /* int openvpn_strsplit */
+
+static void openvpn_submit (char *name, 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, "openvpn", sizeof (vl.plugin));
+       sstrncpy (vl.plugin_instance, name, sizeof (vl.plugin_instance));
+       sstrncpy (vl.type, "if_octets", sizeof (vl.type));
+
+       plugin_dispatch_values (&vl);
+} /* void openvpn_submit */
+
+static void compression_submit (char *type_instance, gauge_t ratio)
+{
+       value_t values[1];
+       value_list_t vl = VALUE_LIST_INIT;
+
+       values[0].gauge = ratio;
+
+       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));
+
+       plugin_dispatch_values (&vl);
+} /* void compression_submit */
+
+static int openvpn_read (void)
+{
+       FILE *fh;
+       char buffer[1024];
+       char *fields[10];
+       const int max_fields = STATIC_ARRAY_SIZE (fields);
+       int   fields_num;
+
+       counter_t pre_compress_new    = 0;
+       counter_t post_compress_new   = 0;
+       counter_t pre_decompress_new  = 0;
+       counter_t post_decompress_new = 0;
+
+       /* Clear the least significant four bits, just to make sure all four
+        * counters above are considered to be invalid. */
+       compression_counter_valid &= ~0x0f;
+
+       fh = fopen ((status_file != NULL)
+                       ? status_file
+                       : DEFAULT_STATUS_FILE, "r");
+       if (fh == NULL)
+               return (-1);
+
+        /* 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
+         */
+       while (fgets (buffer, sizeof (buffer), fh) != NULL)
+       {
+               fields_num = openvpn_strsplit (buffer, fields, max_fields);
+
+               /* Expect at least ``key,value''. */
+               if (fields_num < 2)
+                       continue;
+
+               if (strcmp (fields[0], "CLIENT_LIST") == 0)
+               {
+                       char *name;
+                       counter_t rx;
+                       counter_t tx;
+
+                       /* The line we're expecting has 8 fields. We ignore all lines
+                        * with more or less fields. */
+                       if (fields_num != 8)
+                               continue;
+
+                       name =      fields[1];  /* "Common Name" */
+                       rx = atoll (fields[4]); /* "Bytes Received */
+                       tx = atoll (fields[5]); /* "Bytes Sent" */
+                       openvpn_submit (name, rx, tx);
+               }
+               else if (strcmp (fields[0], "pre-compress") == 0)
+               {
+                       pre_compress_new = atoll (fields[1]);
+                       compression_counter_valid |= 0x01;
+               }
+               else if (strcmp (fields[0], "post-compress") == 0)
+               {
+                       post_compress_new = atoll (fields[1]);
+                       compression_counter_valid |= 0x02;
+               }
+               else if (strcmp (fields[0], "pre-decompress") == 0)
+               {
+                       pre_decompress_new = atoll (fields[1]);
+                       compression_counter_valid |= 0x04;
+               }
+               else if (strcmp (fields[0], "post-decompress") == 0)
+               {
+                       post_decompress_new = atoll (fields[1]);
+                       compression_counter_valid |= 0x08;
+               }
+       }
+       fclose (fh);
+
+       /* Check that all four counters are valid, {pre,post}_*_{old,new}. */
+       if ((compression_counter_valid & 0x33) == 0x33)
+       {
+               counter_t pre_diff;
+               counter_t post_diff;
+
+               pre_diff = counter_diff (pre_compress_old, pre_compress_new);
+               post_diff = counter_diff (post_compress_old, post_compress_new);
+
+               /* If we compress, we're sending. */
+               compression_submit ("tx",
+                               ((gauge_t) post_diff) / ((gauge_t) pre_diff));
+       }
+
+       /* Now check the other found counters. */
+       if ((compression_counter_valid & 0xcc) == 0xcc)
+       {
+               counter_t pre_diff;
+               counter_t post_diff;
+
+               pre_diff = counter_diff (pre_decompress_old, pre_decompress_new);
+               post_diff = counter_diff (post_decompress_old, post_decompress_new);
+
+               /* If we decompress, we're receiving. */
+               compression_submit ("rx",
+                               ((gauge_t) pre_diff) / ((gauge_t) post_diff));
+       }
+
+       /* 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;
+
+       return (0);
+} /* int openvpn_read */
+
+static int openvpn_config (const char *key, const char *value)
+{
+       if (strcasecmp ("StatusFile", key) == 0)
+       {
+               sfree (status_file);
+               status_file = sstrdup (value);
+       }
+       else
+       {
+               return (-1);
+       }
+        return (0);
+} /* int openvpn_config */
+
+void module_register (void)
+{
+       plugin_register_config ("openvpn", openvpn_config,
+                               config_keys, config_keys_num);
+       plugin_register_read ("openvpn", openvpn_read);
+} /* void module_register */
diff --git a/src/oracle.c b/src/oracle.c
new file mode 100644 (file)
index 0000000..78a09ff
--- /dev/null
@@ -0,0 +1,680 @@
+/**
+ * collectd - src/oracle.c
+ * Copyright (C) 2008,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
+ *
+ * Linking src/oracle.c ("the oracle plugin") statically or dynamically with
+ * other modules is making a combined work based on the oracle plugin. Thus,
+ * the terms and conditions of the GNU General Public License cover the whole
+ * combination.
+ *
+ * In addition, as a special exception, the copyright holders of the oracle
+ * plugin give you permission to combine the oracle plugin with free software
+ * programs or libraries that are released under the GNU LGPL and with code
+ * included in the standard release of the Oracle® Call Interface (OCI) under
+ * the Oracle® Technology Network (OTN) License (or modified versions of such
+ * code, with unchanged license). You may copy and distribute such a system
+ * following the terms of the GNU GPL for the oracle plugin and the licenses of
+ * the other code concerned.
+ *
+ * Note that people who make modified versions of the oracle plugin are not
+ * obligated to grant this special exception for their modified versions; it is
+ * their choice whether to do so. The GNU General Public License gives
+ * permission to release a modified version without this exception; this
+ * exception also makes it possible to release a modified version which carries
+ * forward this exception. However, without this exception the OTN License does
+ * not allow linking with code licensed under the GNU General Public License.
+ *
+ * Oracle® is a registered trademark of Oracle Corporation and/or its
+ * affiliates. Other names may be trademarks of their respective owners.
+ *
+ * Authors:
+ *   Florian octo Forster <octo at noris.net>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_db_query.h"
+
+#include <oci.h>
+
+/*
+ * Data types
+ */
+struct o_database_s
+{
+  char *name;
+  char *connect_id;
+  char *username;
+  char *password;
+
+  udb_query_t **queries;
+  size_t        queries_num;
+
+  OCISvcCtx *oci_service_context;
+};
+typedef struct o_database_s o_database_t;
+
+/*
+ * Global variables
+ */
+static udb_query_t  **queries       = NULL;
+static size_t         queries_num   = 0;
+static o_database_t **databases     = NULL;
+static size_t         databases_num = 0;
+
+OCIEnv   *oci_env = NULL;
+OCIError *oci_error = NULL;
+
+/*
+ * Functions
+ */
+static void o_report_error (const char *where, /* {{{ */
+    const char *what, OCIError *eh)
+{
+  char buffer[2048];
+  sb4 error_code;
+  int status;
+
+  status = OCIErrorGet (eh, /* record number = */ 1,
+      /* sqlstate = */ NULL,
+      &error_code,
+      (text *) &buffer[0],
+      (ub4) sizeof (buffer),
+      OCI_HTYPE_ERROR);
+  buffer[sizeof (buffer) - 1] = 0;
+
+  if (status == OCI_SUCCESS)
+  {
+    size_t buffer_length;
+
+    buffer_length = strlen (buffer);
+    while ((buffer_length > 0) && (buffer[buffer_length - 1] < 32))
+    {
+      buffer_length--;
+      buffer[buffer_length] = 0;
+    }
+
+    ERROR ("oracle plugin: %s: %s failed: %s",
+        where, what, buffer);
+  }
+  else
+  {
+    ERROR ("oracle plugin: %s: %s failed. Additionally, OCIErrorGet failed with status %i.",
+        where, what, status);
+  }
+} /* }}} void o_report_error */
+
+static void o_database_free (o_database_t *db) /* {{{ */
+{
+  if (db == NULL)
+    return;
+
+  sfree (db->name);
+  sfree (db->connect_id);
+  sfree (db->username);
+  sfree (db->password);
+  sfree (db->queries);
+
+  sfree (db);
+} /* }}} void o_database_free */
+
+/* Configuration handling functions {{{
+ *
+ * <Plugin oracle>
+ *   <Query "plugin_instance0">
+ *     Statement "SELECT name, value FROM table"
+ *     <Result>
+ *       Type "gauge"
+ *       InstancesFrom "name"
+ *       ValuesFrom "value"
+ *     </Result>
+ *   </Query>
+ *     
+ *   <Database "plugin_instance1">
+ *     ConnectID "db01"
+ *     Username "oracle"
+ *     Password "secret"
+ *     Query "plugin_instance0"
+ *   </Database>
+ * </Plugin>
+ */
+
+static int o_config_set_string (char **ret_string, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char *string;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("oracle plugin: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  string = strdup (ci->values[0].value.string);
+  if (string == NULL)
+  {
+    ERROR ("oracle plugin: strdup failed.");
+    return (-1);
+  }
+
+  if (*ret_string != NULL)
+    free (*ret_string);
+  *ret_string = string;
+
+  return (0);
+} /* }}} int o_config_set_string */
+
+static int o_config_add_database (oconfig_item_t *ci) /* {{{ */
+{
+  o_database_t *db;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("oracle plugin: The `Database' block "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  db = (o_database_t *) malloc (sizeof (*db));
+  if (db == NULL)
+  {
+    ERROR ("oracle plugin: malloc failed.");
+    return (-1);
+  }
+  memset (db, 0, sizeof (*db));
+
+  status = o_config_set_string (&db->name, ci);
+  if (status != 0)
+  {
+    sfree (db);
+    return (status);
+  }
+
+  /* Fill the `o_database_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("ConnectID", child->key) == 0)
+      status = o_config_set_string (&db->connect_id, child);
+    else if (strcasecmp ("Username", child->key) == 0)
+      status = o_config_set_string (&db->username, child);
+    else if (strcasecmp ("Password", child->key) == 0)
+      status = o_config_set_string (&db->password, child);
+    else if (strcasecmp ("Query", child->key) == 0)
+      status = udb_query_pick_from_list (child, queries, queries_num,
+          &db->queries, &db->queries_num);
+    else
+    {
+      WARNING ("oracle plugin: Option `%s' not allowed here.", child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  while (status == 0)
+  {
+    if (db->connect_id == NULL)
+    {
+      WARNING ("oracle plugin: `ConnectID' not given for query `%s'", db->name);
+      status = -1;
+    }
+    if (db->username == NULL)
+    {
+      WARNING ("oracle plugin: `Username' not given for query `%s'", db->name);
+      status = -1;
+    }
+    if (db->password == NULL)
+    {
+      WARNING ("oracle plugin: `Password' not given for query `%s'", db->name);
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  /* If all went well, add this query to the list of queries within the
+   * database structure. */
+  if (status == 0)
+  {
+    o_database_t **temp;
+
+    temp = (o_database_t **) realloc (databases,
+        sizeof (*databases) * (databases_num + 1));
+    if (temp == NULL)
+    {
+      ERROR ("oracle plugin: realloc failed");
+      status = -1;
+    }
+    else
+    {
+      databases = temp;
+      databases[databases_num] = db;
+      databases_num++;
+    }
+  }
+
+  if (status != 0)
+  {
+    o_database_free (db);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int o_config_add_database */
+
+static int o_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Query", child->key) == 0)
+      udb_query_create (&queries, &queries_num, child,
+          /* callback = */ NULL, /* legacy mode = */ 0);
+    else if (strcasecmp ("Database", child->key) == 0)
+      o_config_add_database (child);
+    else
+    {
+      WARNING ("oracle plugin: Ignoring unknown config option `%s'.", child->key);
+    }
+
+    if (queries_num > 0)
+    {
+      DEBUG ("oracle plugin: o_config: queries_num = %zu; queries[0] = %p; udb_query_get_user_data (queries[0]) = %p;",
+          queries_num, (void *) queries[0], udb_query_get_user_data (queries[0]));
+    }
+  } /* for (ci->children) */
+
+  return (0);
+} /* }}} int o_config */
+
+/* }}} End of configuration handling functions */
+
+static int o_init (void) /* {{{ */
+{
+  int status;
+
+  if (oci_env != NULL)
+    return (0);
+
+  status = OCIEnvCreate (&oci_env,
+      /* mode = */ OCI_THREADED,
+      /* context        = */ NULL,
+      /* malloc         = */ NULL,
+      /* realloc        = */ NULL,
+      /* free           = */ NULL,
+      /* user_data_size = */ 0,
+      /* user_data_ptr  = */ NULL);
+  if (status != 0)
+  {
+    ERROR ("oracle plugin: OCIEnvCreate failed with status %i.", status);
+    return (-1);
+  }
+
+  status = OCIHandleAlloc (oci_env, (void *) &oci_error, OCI_HTYPE_ERROR,
+      /* user_data_size = */ 0, /* user_data = */ NULL);
+  if (status != OCI_SUCCESS)
+  {
+    ERROR ("oracle plugin: OCIHandleAlloc (OCI_HTYPE_ERROR) failed "
+        "with status %i.", status);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int o_init */
+
+static int o_read_database_query (o_database_t *db, /* {{{ */
+    udb_query_t *q)
+{
+  char **column_names;
+  char **column_values;
+  size_t column_num;
+
+  OCIStmt *oci_statement;
+
+  /* List of `OCIDefine' pointers. These defines map columns to the buffer
+   * space declared above. */
+  OCIDefine **oci_defines;
+
+  int status;
+  size_t i;
+
+  oci_statement = udb_query_get_user_data (q);
+
+  /* Prepare the statement */
+  if (oci_statement == NULL) /* {{{ */
+  {
+    const char *statement;
+
+    statement = udb_query_get_statement (q);
+    assert (statement != NULL);
+
+    status = OCIHandleAlloc (oci_env, (void *) &oci_statement,
+        OCI_HTYPE_STMT, /* user_data_size = */ 0, /* user_data = */ NULL);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIHandleAlloc", oci_error);
+      oci_statement = NULL;
+      return (-1);
+    }
+
+    status = OCIStmtPrepare (oci_statement, oci_error,
+        (text *) statement, (ub4) strlen (statement),
+        /* language = */ OCI_NTV_SYNTAX,
+        /* mode     = */ OCI_DEFAULT);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIStmtPrepare", oci_error);
+      OCIHandleFree (oci_statement, OCI_HTYPE_STMT);
+      oci_statement = NULL;
+      return (-1);
+    }
+    udb_query_set_user_data (q, oci_statement);
+
+    DEBUG ("oracle plugin: o_read_database_query (%s, %s): "
+        "Successfully allocated statement handle.",
+        db->name, udb_query_get_name (q));
+  } /* }}} */
+
+  assert (oci_statement != NULL);
+
+  /* Execute the statement */
+  status = OCIStmtExecute (db->oci_service_context, /* {{{ */
+      oci_statement,
+      oci_error,
+      /* iters = */ 0,
+      /* rowoff = */ 0,
+      /* snap_in = */ NULL, /* snap_out = */ NULL,
+      /* mode = */ OCI_DEFAULT);
+  if (status != OCI_SUCCESS)
+  {
+    DEBUG ("oracle plugin: o_read_database_query: status = %i (%#x)", status, status);
+    o_report_error ("o_read_database_query", "OCIStmtExecute", oci_error);
+    ERROR ("oracle plugin: o_read_database_query: "
+        "Failing statement was: %s", udb_query_get_statement (q));
+    return (-1);
+  } /* }}} */
+
+  /* Acquire the number of columns returned. */
+  do /* {{{ */
+  {
+    ub4 param_counter = 0;
+    status = OCIAttrGet (oci_statement, OCI_HTYPE_STMT, /* {{{ */
+        &param_counter, /* size pointer = */ NULL, 
+        OCI_ATTR_PARAM_COUNT, oci_error);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIAttrGet", oci_error);
+      return (-1);
+    } /* }}} */
+
+    column_num = (size_t) param_counter;
+  } while (0); /* }}} */
+
+  /* Allocate the following buffers:
+   * 
+   *  +---------------+-----------------------------------+
+   *  ! Name          ! Size                              !
+   *  +---------------+-----------------------------------+
+   *  ! column_names  ! column_num x DATA_MAX_NAME_LEN    !
+   *  ! column_values ! column_num x DATA_MAX_NAME_LEN    !
+   *  ! oci_defines   ! column_num x sizeof (OCIDefine *) !
+   *  +---------------+-----------------------------------+
+   *
+   * {{{ */
+#define NUMBER_BUFFER_SIZE 64
+
+#define FREE_ALL \
+  if (column_names != NULL) { \
+    sfree (column_names[0]); \
+    sfree (column_names); \
+  } \
+  if (column_values != NULL) { \
+    sfree (column_values[0]); \
+    sfree (column_values); \
+  } \
+  sfree (oci_defines)
+
+#define ALLOC_OR_FAIL(ptr, ptr_size) \
+  do { \
+    size_t alloc_size = (size_t) ((ptr_size)); \
+    (ptr) = malloc (alloc_size); \
+    if ((ptr) == NULL) { \
+      FREE_ALL; \
+      ERROR ("oracle plugin: o_read_database_query: malloc failed."); \
+      return (-1); \
+    } \
+    memset ((ptr), 0, alloc_size); \
+  } while (0)
+
+  /* Initialize everything to NULL so the above works. */
+  column_names  = NULL;
+  column_values = NULL;
+  oci_defines   = NULL;
+
+  ALLOC_OR_FAIL (column_names, column_num * sizeof (char *));
+  ALLOC_OR_FAIL (column_names[0], column_num * DATA_MAX_NAME_LEN
+      * sizeof (char));
+  for (i = 1; i < column_num; i++)
+    column_names[i] = column_names[i - 1] + DATA_MAX_NAME_LEN;
+
+  ALLOC_OR_FAIL (column_values, column_num * sizeof (char *));
+  ALLOC_OR_FAIL (column_values[0], column_num * DATA_MAX_NAME_LEN
+      * sizeof (char));
+  for (i = 1; i < column_num; i++)
+    column_values[i] = column_values[i - 1] + DATA_MAX_NAME_LEN;
+
+  ALLOC_OR_FAIL (oci_defines, column_num * sizeof (OCIDefine *));
+  /* }}} End of buffer allocations. */
+
+  /* ``Define'' the returned data, i. e. bind the columns to the buffers
+   * allocated above. */
+  for (i = 0; i < column_num; i++) /* {{{ */
+  {
+    char *column_name;
+    ub4 column_name_length;
+    OCIParam *oci_param;
+
+    oci_param = NULL;
+
+    status = OCIParamGet (oci_statement, OCI_HTYPE_STMT, oci_error,
+        (void *) &oci_param, (ub4) (i + 1));
+    if (status != OCI_SUCCESS)
+    {
+      /* This is probably alright */
+      DEBUG ("oracle plugin: o_read_database_query: status = %#x (= %i);", status, status);
+      o_report_error ("o_read_database_query", "OCIParamGet", oci_error);
+      status = OCI_SUCCESS;
+      break;
+    }
+
+    column_name = NULL;
+    column_name_length = 0;
+    status = OCIAttrGet (oci_param, OCI_DTYPE_PARAM,
+        &column_name, &column_name_length, OCI_ATTR_NAME, oci_error);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIAttrGet (OCI_ATTR_NAME)",
+          oci_error);
+      continue;
+    }
+
+    /* Copy the name to column_names. Warning: The ``string'' returned by OCI
+     * may not be null terminated! */
+    memset (column_names[i], 0, DATA_MAX_NAME_LEN);
+    if (column_name_length >= DATA_MAX_NAME_LEN)
+      column_name_length = DATA_MAX_NAME_LEN - 1;
+    memcpy (column_names[i], column_name, column_name_length);
+    column_names[i][column_name_length] = 0;
+
+    DEBUG ("oracle plugin: o_read_database_query: column_names[%zu] = %s; "
+        "column_name_length = %"PRIu32";",
+        i, column_names[i], (uint32_t) column_name_length);
+
+    status = OCIDefineByPos (oci_statement,
+        &oci_defines[i], oci_error, (ub4) (i + 1),
+        column_values[i], DATA_MAX_NAME_LEN, SQLT_STR,
+        NULL, NULL, NULL, OCI_DEFAULT);
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database_query", "OCIDefineByPos", oci_error);
+      continue;
+    }
+  } /* for (j = 1; j <= param_counter; j++) */
+  /* }}} End of the ``define'' stuff. */
+
+  status = udb_query_prepare_result (q, hostname_g, /* plugin = */ "oracle",
+      db->name, column_names, column_num);
+  if (status != 0)
+  {
+    ERROR ("oracle plugin: o_read_database_query (%s, %s): "
+        "udb_query_prepare_result failed.",
+        db->name, udb_query_get_name (q));
+    FREE_ALL;
+    return (-1);
+  }
+
+  /* Fetch and handle all the rows that matched the query. */
+  while (42) /* {{{ */
+  {
+    status = OCIStmtFetch2 (oci_statement, oci_error,
+        /* nrows = */ 1, /* orientation = */ OCI_FETCH_NEXT,
+        /* fetch offset = */ 0, /* mode = */ OCI_DEFAULT);
+    if (status == OCI_NO_DATA)
+    {
+      status = OCI_SUCCESS;
+      break;
+    }
+    else if ((status != OCI_SUCCESS) && (status != OCI_SUCCESS_WITH_INFO))
+    {
+      o_report_error ("o_read_database_query", "OCIStmtFetch2", oci_error);
+      break;
+    }
+
+    status = udb_query_handle_result (q, column_values);
+    if (status != 0)
+    {
+      WARNING ("oracle plugin: o_read_database_query (%s, %s): "
+          "udb_query_handle_result failed.",
+          db->name, udb_query_get_name (q));
+    }
+  } /* }}} while (42) */
+
+  /* DEBUG ("oracle plugin: o_read_database_query: This statement succeeded: %s", q->statement); */
+  FREE_ALL;
+
+  return (0);
+#undef FREE_ALL
+#undef ALLOC_OR_FAIL
+} /* }}} int o_read_database_query */
+
+static int o_read_database (o_database_t *db) /* {{{ */
+{
+  size_t i;
+  int status;
+
+  if (db->oci_service_context == NULL)
+  {
+    status = OCILogon (oci_env, oci_error,
+        &db->oci_service_context,
+        (OraText *) db->username, (ub4) strlen (db->username),
+        (OraText *) db->password, (ub4) strlen (db->password),
+        (OraText *) db->connect_id, (ub4) strlen (db->connect_id));
+    if (status != OCI_SUCCESS)
+    {
+      o_report_error ("o_read_database", "OCILogon", oci_error);
+      DEBUG ("oracle plugin: OCILogon (%s): db->oci_service_context = %p;",
+          db->connect_id, db->oci_service_context);
+      db->oci_service_context = NULL;
+      return (-1);
+    }
+    assert (db->oci_service_context != NULL);
+  }
+
+  DEBUG ("oracle plugin: o_read_database: db->connect_id = %s; db->oci_service_context = %p;",
+      db->connect_id, db->oci_service_context);
+
+  for (i = 0; i < db->queries_num; i++)
+    o_read_database_query (db, db->queries[i]);
+
+  return (0);
+} /* }}} int o_read_database */
+
+static int o_read (void) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < databases_num; i++)
+    o_read_database (databases[i]);
+
+  return (0);
+} /* }}} int o_read */
+
+static int o_shutdown (void) /* {{{ */
+{
+  size_t i;
+
+  for (i = 0; i < databases_num; i++)
+    if (databases[i]->oci_service_context != NULL)
+    {
+      OCIHandleFree (databases[i]->oci_service_context, OCI_HTYPE_SVCCTX);
+      databases[i]->oci_service_context = NULL;
+    }
+  
+  for (i = 0; i < queries_num; i++)
+  {
+    OCIStmt *oci_statement;
+
+    oci_statement = udb_query_get_user_data (queries[i]);
+    if (oci_statement != NULL)
+    {
+      OCIHandleFree (oci_statement, OCI_HTYPE_STMT);
+      udb_query_set_user_data (queries[i], NULL);
+    }
+  }
+  
+  OCIHandleFree (oci_env, OCI_HTYPE_ENV);
+
+  udb_query_free (queries, queries_num);
+  queries = NULL;
+  queries_num = 0;
+
+  return (0);
+} /* }}} int o_shutdown */
+
+void module_register (void) /* {{{ */
+{
+  plugin_register_complex_config ("oracle", o_config);
+  plugin_register_init ("oracle", o_init);
+  plugin_register_read ("oracle", o_read);
+  plugin_register_shutdown ("oracle", o_shutdown);
+} /* }}} void module_register */
+
+/*
+ * vim: shiftwidth=2 softtabstop=2 et fdm=marker
+ */
index 343c83a..81ef720 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/perl.c
- * Copyright (C) 2007, 2008  Sebastian Harl
+ * Copyright (C) 2007-2009  Sebastian Harl
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -51,6 +51,8 @@
 #include "plugin.h"
 #include "common.h"
 
+#include "filter_chain.h"
+
 #include <pthread.h>
 
 #if !defined(USE_ITHREADS)
 #define PLUGIN_CONFIG   254
 #define PLUGIN_DATASET  255
 
+#define FC_MATCH  0
+#define FC_TARGET 1
+
+#define FC_TYPES  2
+
+#define FC_CB_CREATE  0
+#define FC_CB_DESTROY 1
+#define FC_CB_EXEC    2
+
+#define FC_CB_TYPES   3
+
 #define log_debug(...) DEBUG ("perl: " __VA_ARGS__)
 #define log_info(...) INFO ("perl: " __VA_ARGS__)
 #define log_warn(...) WARNING ("perl: " __VA_ARGS__)
@@ -85,9 +98,11 @@ void boot_DynaLoader (PerlInterpreter *, CV *);
 static XS (Collectd_plugin_register_ds);
 static XS (Collectd_plugin_unregister_ds);
 static XS (Collectd_plugin_dispatch_values);
+static XS (Collectd__plugin_write);
 static XS (Collectd__plugin_flush);
 static XS (Collectd_plugin_dispatch_notification);
 static XS (Collectd_plugin_log);
+static XS (Collectd__fc_register);
 static XS (Collectd_call_by_name);
 
 /*
@@ -115,6 +130,20 @@ typedef struct {
        pthread_mutex_t mutex;
 } c_ithread_list_t;
 
+/* name / user_data for Perl matches / targets */
+typedef struct {
+       char *name;
+       SV   *user_data;
+} pfc_user_data_t;
+
+#define PFC_USER_DATA_FREE(data) \
+       do { \
+               sfree ((data)->name); \
+               if (NULL != (data)->user_data) \
+                       sv_free ((data)->user_data); \
+               sfree (data); \
+       } while (0)
+
 /*
  * private variables
  */
@@ -139,10 +168,12 @@ static struct {
        { "Collectd::plugin_register_data_set",   Collectd_plugin_register_ds },
        { "Collectd::plugin_unregister_data_set", Collectd_plugin_unregister_ds },
        { "Collectd::plugin_dispatch_values",     Collectd_plugin_dispatch_values },
+       { "Collectd::_plugin_write",              Collectd__plugin_write },
        { "Collectd::_plugin_flush",              Collectd__plugin_flush },
        { "Collectd::plugin_dispatch_notification",
                Collectd_plugin_dispatch_notification },
        { "Collectd::plugin_log",                 Collectd_plugin_log },
+       { "Collectd::_fc_register",               Collectd__fc_register },
        { "Collectd::call_by_name",               Collectd_call_by_name },
        { "", NULL }
 };
@@ -152,25 +183,35 @@ struct {
        int  value;
 } constants[] =
 {
-       { "Collectd::TYPE_INIT",       PLUGIN_INIT },
-       { "Collectd::TYPE_READ",       PLUGIN_READ },
-       { "Collectd::TYPE_WRITE",      PLUGIN_WRITE },
-       { "Collectd::TYPE_SHUTDOWN",   PLUGIN_SHUTDOWN },
-       { "Collectd::TYPE_LOG",        PLUGIN_LOG },
-       { "Collectd::TYPE_NOTIF",      PLUGIN_NOTIF },
-       { "Collectd::TYPE_FLUSH",      PLUGIN_FLUSH },
-       { "Collectd::TYPE_CONFIG",     PLUGIN_CONFIG },
-       { "Collectd::TYPE_DATASET",    PLUGIN_DATASET },
-       { "Collectd::DS_TYPE_COUNTER", DS_TYPE_COUNTER },
-       { "Collectd::DS_TYPE_GAUGE",   DS_TYPE_GAUGE },
-       { "Collectd::LOG_ERR",         LOG_ERR },
-       { "Collectd::LOG_WARNING",     LOG_WARNING },
-       { "Collectd::LOG_NOTICE",      LOG_NOTICE },
-       { "Collectd::LOG_INFO",        LOG_INFO },
-       { "Collectd::LOG_DEBUG",       LOG_DEBUG },
-       { "Collectd::NOTIF_FAILURE",   NOTIF_FAILURE },
-       { "Collectd::NOTIF_WARNING",   NOTIF_WARNING },
-       { "Collectd::NOTIF_OKAY",      NOTIF_OKAY },
+       { "Collectd::TYPE_INIT",          PLUGIN_INIT },
+       { "Collectd::TYPE_READ",          PLUGIN_READ },
+       { "Collectd::TYPE_WRITE",         PLUGIN_WRITE },
+       { "Collectd::TYPE_SHUTDOWN",      PLUGIN_SHUTDOWN },
+       { "Collectd::TYPE_LOG",           PLUGIN_LOG },
+       { "Collectd::TYPE_NOTIF",         PLUGIN_NOTIF },
+       { "Collectd::TYPE_FLUSH",         PLUGIN_FLUSH },
+       { "Collectd::TYPE_CONFIG",        PLUGIN_CONFIG },
+       { "Collectd::TYPE_DATASET",       PLUGIN_DATASET },
+       { "Collectd::DS_TYPE_COUNTER",    DS_TYPE_COUNTER },
+       { "Collectd::DS_TYPE_GAUGE",      DS_TYPE_GAUGE },
+       { "Collectd::LOG_ERR",            LOG_ERR },
+       { "Collectd::LOG_WARNING",        LOG_WARNING },
+       { "Collectd::LOG_NOTICE",         LOG_NOTICE },
+       { "Collectd::LOG_INFO",           LOG_INFO },
+       { "Collectd::LOG_DEBUG",          LOG_DEBUG },
+       { "Collectd::FC_MATCH",           FC_MATCH },
+       { "Collectd::FC_TARGET",          FC_TARGET },
+       { "Collectd::FC_CB_CREATE",       FC_CB_CREATE },
+       { "Collectd::FC_CB_DESTROY",      FC_CB_DESTROY },
+       { "Collectd::FC_CB_EXEC",         FC_CB_EXEC },
+       { "Collectd::FC_MATCH_NO_MATCH",  FC_MATCH_NO_MATCH },
+       { "Collectd::FC_MATCH_MATCHES",   FC_MATCH_MATCHES },
+       { "Collectd::FC_TARGET_CONTINUE", FC_TARGET_CONTINUE },
+       { "Collectd::FC_TARGET_STOP",     FC_TARGET_STOP },
+       { "Collectd::FC_TARGET_RETURN",   FC_TARGET_RETURN },
+       { "Collectd::NOTIF_FAILURE",      NOTIF_FAILURE },
+       { "Collectd::NOTIF_WARNING",      NOTIF_WARNING },
+       { "Collectd::NOTIF_OKAY",         NOTIF_OKAY },
        { "", 0 }
 };
 
@@ -245,7 +286,7 @@ static int hv2data_source (pTHX_ HV *hash, data_source_t *ds)
        else
                ds->max = NAN;
        return 0;
-} /* static data_source_t *hv2data_source (HV *) */
+} /* static int hv2data_source (HV *, data_source_t *) */
 
 static int av2value (pTHX_ char *name, AV *array, value_t *value, int len)
 {
@@ -289,6 +330,253 @@ static int av2value (pTHX_ char *name, AV *array, value_t *value, int len)
        return len;
 } /* static int av2value (char *, AV *, value_t *, int) */
 
+/*
+ * value list:
+ * {
+ *   values => [ @values ],
+ *   time   => $time,
+ *   host   => $host,
+ *   plugin => $plugin,
+ *   plugin_instance => $pinstance,
+ *   type_instance   => $tinstance,
+ * }
+ */
+static int hv2value_list (pTHX_ HV *hash, value_list_t *vl)
+{
+       SV **tmp;
+
+       if ((NULL == hash) || (NULL == vl))
+               return -1;
+
+       if (NULL == (tmp = hv_fetch (hash, "type", 4, 0))) {
+               log_err ("hv2value_list: No type given.");
+               return -1;
+       }
+
+       sstrncpy (vl->type, SvPV_nolen (*tmp), sizeof (vl->type));
+
+       if ((NULL == (tmp = hv_fetch (hash, "values", 6, 0)))
+                       || (! (SvROK (*tmp) && (SVt_PVAV == SvTYPE (SvRV (*tmp)))))) {
+               log_err ("hv2value_list: No valid values given.");
+               return -1;
+       }
+
+       {
+               AV  *array = (AV *)SvRV (*tmp);
+               int len    = av_len (array) + 1;
+
+               if (len <= 0)
+                       return -1;
+
+               vl->values     = (value_t *)smalloc (len * sizeof (value_t));
+               vl->values_len = av2value (aTHX_ vl->type, (AV *)SvRV (*tmp),
+                               vl->values, len);
+
+               if (-1 == vl->values_len) {
+                       sfree (vl->values);
+                       return -1;
+               }
+       }
+
+       if (NULL != (tmp = hv_fetch (hash, "time", 4, 0)))
+               vl->time = (time_t)SvIV (*tmp);
+
+       if (NULL != (tmp = hv_fetch (hash, "interval", 8, 0)))
+               vl->interval = SvIV (*tmp);
+
+       if (NULL != (tmp = hv_fetch (hash, "host", 4, 0)))
+               sstrncpy (vl->host, SvPV_nolen (*tmp), sizeof (vl->host));
+       else
+               sstrncpy (vl->host, hostname_g, sizeof (vl->host));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin", 6, 0)))
+               sstrncpy (vl->plugin, SvPV_nolen (*tmp), sizeof (vl->plugin));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin_instance", 15, 0)))
+               sstrncpy (vl->plugin_instance, SvPV_nolen (*tmp),
+                               sizeof (vl->plugin_instance));
+
+       if (NULL != (tmp = hv_fetch (hash, "type_instance", 13, 0)))
+               sstrncpy (vl->type_instance, SvPV_nolen (*tmp),
+                               sizeof (vl->type_instance));
+       return 0;
+} /* static int hv2value_list (pTHX_ HV *, value_list_t *) */
+
+static int av2data_set (pTHX_ AV *array, char *name, data_set_t *ds)
+{
+       int len, i;
+
+       if ((NULL == array) || (NULL == name) || (NULL == ds))
+               return -1;
+
+       len = av_len (array);
+
+       if (-1 == len) {
+               log_err ("av2data_set: Invalid data set.");
+               return -1;
+       }
+
+       ds->ds = (data_source_t *)smalloc ((len + 1) * sizeof (data_source_t));
+       ds->ds_num = len + 1;
+
+       for (i = 0; i <= len; ++i) {
+               SV **elem = av_fetch (array, i, 0);
+
+               if (NULL == elem) {
+                       log_err ("av2data_set: Failed to fetch data source %i.", i);
+                       return -1;
+               }
+
+               if (! (SvROK (*elem) && (SVt_PVHV == SvTYPE (SvRV (*elem))))) {
+                       log_err ("av2data_set: Invalid data source.");
+                       return -1;
+               }
+
+               if (-1 == hv2data_source (aTHX_ (HV *)SvRV (*elem), &ds->ds[i]))
+                       return -1;
+
+               log_debug ("av2data_set: "
+                               "DS.name = \"%s\", DS.type = %i, DS.min = %f, DS.max = %f",
+                               ds->ds[i].name, ds->ds[i].type, ds->ds[i].min, ds->ds[i].max);
+       }
+
+       sstrncpy (ds->type, name, sizeof (ds->type));
+       return 0;
+} /* static int av2data_set (pTHX_ AV *, data_set_t *) */
+
+/*
+ * notification:
+ * {
+ *   severity => $severity,
+ *   time     => $time,
+ *   message  => $msg,
+ *   host     => $host,
+ *   plugin   => $plugin,
+ *   type     => $type,
+ *   plugin_instance => $instance,
+ *   type_instance   => $type_instance,
+ *   meta     => [ { name => <name>, value => <value> }, ... ]
+ * }
+ */
+static int av2notification_meta (pTHX_ AV *array, notification_meta_t **meta)
+{
+       notification_meta_t **m = meta;
+
+       int len = av_len (array);
+       int i;
+
+       for (i = 0; i <= len; ++i) {
+               SV **tmp = av_fetch (array, i, 0);
+               HV  *hash;
+
+               if (NULL == tmp)
+                       return -1;
+
+               if (! (SvROK (*tmp) && (SVt_PVHV == SvTYPE (SvRV (*tmp))))) {
+                       log_warn ("av2notification_meta: Skipping invalid "
+                                       "meta information.");
+                       continue;
+               }
+
+               hash = (HV *)SvRV (*tmp);
+
+               *m = (notification_meta_t *)smalloc (sizeof (**m));
+
+               if (NULL == (tmp = hv_fetch (hash, "name", 4, 0))) {
+                       log_warn ("av2notification_meta: Skipping invalid "
+                                       "meta information.");
+                       free (*m);
+                       continue;
+               }
+               sstrncpy ((*m)->name, SvPV_nolen (*tmp), sizeof ((*m)->name));
+
+               if (NULL == (tmp = hv_fetch (hash, "value", 5, 0))) {
+                       log_warn ("av2notification_meta: Skipping invalid "
+                                       "meta information.");
+                       free ((*m)->name);
+                       free (*m);
+                       continue;
+               }
+
+               if (SvNOK (*tmp)) {
+                       (*m)->nm_value.nm_double = SvNVX (*tmp);
+                       (*m)->type = NM_TYPE_DOUBLE;
+               }
+               else if (SvUOK (*tmp)) {
+                       (*m)->nm_value.nm_unsigned_int = SvUVX (*tmp);
+                       (*m)->type = NM_TYPE_UNSIGNED_INT;
+               }
+               else if (SvIOK (*tmp)) {
+                       (*m)->nm_value.nm_signed_int = SvIVX (*tmp);
+                       (*m)->type = NM_TYPE_SIGNED_INT;
+               }
+               else {
+                       (*m)->nm_value.nm_string = sstrdup (SvPV_nolen (*tmp));
+                       (*m)->type = NM_TYPE_STRING;
+               }
+
+               (*m)->next = NULL;
+               m = &((*m)->next);
+       }
+       return 0;
+} /* static int av2notification_meta (AV *, notification_meta_t *) */
+
+static int hv2notification (pTHX_ HV *hash, notification_t *n)
+{
+       SV **tmp = NULL;
+
+       if ((NULL == hash) || (NULL == n))
+               return -1;
+
+       if (NULL != (tmp = hv_fetch (hash, "severity", 8, 0)))
+               n->severity = SvIV (*tmp);
+       else
+               n->severity = NOTIF_FAILURE;
+
+       if (NULL != (tmp = hv_fetch (hash, "time", 4, 0)))
+               n->time = (time_t)SvIV (*tmp);
+       else
+               n->time = time (NULL);
+
+       if (NULL != (tmp = hv_fetch (hash, "message", 7, 0)))
+               sstrncpy (n->message, SvPV_nolen (*tmp), sizeof (n->message));
+
+       if (NULL != (tmp = hv_fetch (hash, "host", 4, 0)))
+               sstrncpy (n->host, SvPV_nolen (*tmp), sizeof (n->host));
+       else
+               sstrncpy (n->host, hostname_g, sizeof (n->host));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin", 6, 0)))
+               sstrncpy (n->plugin, SvPV_nolen (*tmp), sizeof (n->plugin));
+
+       if (NULL != (tmp = hv_fetch (hash, "plugin_instance", 15, 0)))
+               sstrncpy (n->plugin_instance, SvPV_nolen (*tmp),
+                               sizeof (n->plugin_instance));
+
+       if (NULL != (tmp = hv_fetch (hash, "type", 4, 0)))
+               sstrncpy (n->type, SvPV_nolen (*tmp), sizeof (n->type));
+
+       if (NULL != (tmp = hv_fetch (hash, "type_instance", 13, 0)))
+               sstrncpy (n->type_instance, SvPV_nolen (*tmp),
+                               sizeof (n->type_instance));
+
+       n->meta = NULL;
+       while (NULL != (tmp = hv_fetch (hash, "meta", 4, 0))) {
+               if (! (SvROK (*tmp) && (SVt_PVAV == SvTYPE (SvRV (*tmp))))) {
+                       log_warn ("hv2notification: Ignoring invalid meta information.");
+                       break;
+               }
+
+               if (0 != av2notification_meta (aTHX_ (AV *)SvRV (*tmp), &n->meta)) {
+                       plugin_notification_meta_free (n->meta);
+                       n->meta = NULL;
+                       return -1;
+               }
+               break;
+       }
+       return 0;
+} /* static int hv2notification (pTHX_ HV *, notification_t *) */
+
 static int data_set2av (pTHX_ data_set_t *ds, AV *array)
 {
        int i = 0;
@@ -365,6 +653,9 @@ static int value_list2hv (pTHX_ value_list_t *vl, data_set_t *ds, HV *hash)
                if (NULL == hv_store (hash, "time", 4, newSViv (vl->time), 0))
                        return -1;
 
+       if (NULL == hv_store (hash, "interval", 8, newSViv (vl->interval), 0))
+               return -1;
+
        if ('\0' != vl->host[0])
                if (NULL == hv_store (hash, "host", 4, newSVpv (vl->host, 0), 0))
                        return -1;
@@ -389,6 +680,52 @@ static int value_list2hv (pTHX_ value_list_t *vl, data_set_t *ds, HV *hash)
        return 0;
 } /* static int value2av (value_list_t *, data_set_t *, HV *) */
 
+static int notification_meta2av (pTHX_ notification_meta_t *meta, AV *array)
+{
+       int meta_num = 0;
+       int i;
+
+       while (meta) {
+               ++meta_num;
+               meta = meta->next;
+       }
+
+       av_extend (array, meta_num);
+
+       for (i = 0; NULL != meta; meta = meta->next, ++i) {
+               HV *m = newHV ();
+               SV *value;
+
+               if (NULL == hv_store (m, "name", 4, newSVpv (meta->name, 0), 0))
+                       return -1;
+
+               if (NM_TYPE_STRING == meta->type)
+                       value = newSVpv (meta->nm_value.nm_string, 0);
+               else if (NM_TYPE_SIGNED_INT == meta->type)
+                       value = newSViv (meta->nm_value.nm_signed_int);
+               else if (NM_TYPE_UNSIGNED_INT == meta->type)
+                       value = newSVuv (meta->nm_value.nm_unsigned_int);
+               else if (NM_TYPE_DOUBLE == meta->type)
+                       value = newSVnv (meta->nm_value.nm_double);
+               else if (NM_TYPE_BOOLEAN == meta->type)
+                       value = meta->nm_value.nm_boolean ? &PL_sv_yes : &PL_sv_no;
+               else
+                       return -1;
+
+               if (NULL == hv_store (m, "value", 5, value, 0)) {
+                       sv_free (value);
+                       return -1;
+               }
+
+               if (NULL == av_store (array, i, newRV_noinc ((SV *)m))) {
+                       hv_clear (m);
+                       hv_undef (m);
+                       return -1;
+               }
+       }
+       return 0;
+} /* static int notification_meta2av (notification_meta_t *, AV *) */
+
 static int notification2hv (pTHX_ notification_t *n, HV *hash)
 {
        if (NULL == hv_store (hash, "severity", 8, newSViv (n->severity), 0))
@@ -423,6 +760,17 @@ static int notification2hv (pTHX_ notification_t *n, HV *hash)
                if (NULL == hv_store (hash, "type_instance", 13,
                                newSVpv (n->type_instance, 0), 0))
                        return -1;
+
+       if (NULL != n->meta) {
+               AV *meta = newAV ();
+               if ((0 != notification_meta2av (aTHX_ n->meta, meta))
+                               || (NULL == hv_store (hash, "meta", 4,
+                                               newRV_noinc ((SV *)meta), 0))) {
+                       av_clear (meta);
+                       av_undef (meta);
+                       return -1;
+               }
+       }
        return 0;
 } /* static int notification2hv (notification_t *, HV *) */
 
@@ -521,52 +869,19 @@ static char *get_module_name (char *buf, size_t buf_len, const char *module) {
  */
 static int pplugin_register_data_set (pTHX_ char *name, AV *dataset)
 {
-       int len = -1;
        int ret = 0;
-       int i   = 0;
 
-       data_source_t *ds  = NULL;
-       data_set_t    *set = NULL;
+       data_set_t ds;
 
        if ((NULL == name) || (NULL == dataset))
                return -1;
 
-       len = av_len (dataset);
-
-       if (-1 == len)
+       if (0 != av2data_set (aTHX_ dataset, name, &ds))
                return -1;
 
-       ds  = (data_source_t *)smalloc ((len + 1) * sizeof (data_source_t));
-       set = (data_set_t *)smalloc (sizeof (data_set_t));
-
-       for (i = 0; i <= len; ++i) {
-               SV **elem = av_fetch (dataset, i, 0);
-
-               if (NULL == elem)
-                       return -1;
-
-               if (! (SvROK (*elem) && (SVt_PVHV == SvTYPE (SvRV (*elem))))) {
-                       log_err ("pplugin_register_data_set: Invalid data source.");
-                       return -1;
-               }
-
-               if (-1 == hv2data_source (aTHX_ (HV *)SvRV (*elem), &ds[i]))
-                       return -1;
-
-               log_debug ("pplugin_register_data_set: "
-                               "DS.name = \"%s\", DS.type = %i, DS.min = %f, DS.max = %f",
-                               ds[i].name, ds[i].type, ds[i].min, ds[i].max);
-       }
-
-       sstrncpy (set->type, name, sizeof (set->type));
+       ret = plugin_register_data_set (&ds);
 
-       set->ds_num = len + 1;
-       set->ds = ds;
-
-       ret = plugin_register_data_set (set);
-
-       free (ds);
-       free (set);
+       free (ds.ds);
        return ret;
 } /* static int pplugin_register_data_set (char *, SV *) */
 
@@ -582,149 +897,76 @@ static int pplugin_unregister_data_set (char *name)
 
 /*
  * Submit the values to the write functions.
- *
- * value list:
- * {
- *   values => [ @values ],
- *   time   => $time,
- *   host   => $host,
- *   plugin => $plugin,
- *   plugin_instance => $pinstance,
- *   type_instance   => $tinstance,
- * }
  */
 static int pplugin_dispatch_values (pTHX_ HV *values)
 {
-       value_list_t list = VALUE_LIST_INIT;
-       value_t      *val = NULL;
-
-       SV **tmp = NULL;
+       value_list_t vl = VALUE_LIST_INIT;
 
        int ret = 0;
 
        if (NULL == values)
                return -1;
 
-       if (NULL == (tmp = hv_fetch (values, "type", 4, 0))) {
-               log_err ("pplugin_dispatch_values: No type given.");
-               return -1;
-       }
-
-       sstrncpy (list.type, SvPV_nolen (*tmp), sizeof (list.type));
-
-       if ((NULL == (tmp = hv_fetch (values, "values", 6, 0)))
-                       || (! (SvROK (*tmp) && (SVt_PVAV == SvTYPE (SvRV (*tmp)))))) {
-               log_err ("pplugin_dispatch_values: No valid values given.");
+       if (0 != hv2value_list (aTHX_ values, &vl))
                return -1;
-       }
-
-       {
-               AV  *array = (AV *)SvRV (*tmp);
-               int len    = av_len (array) + 1;
-
-               if (len <= 0)
-                       return -1;
-
-               val = (value_t *)smalloc (len * sizeof (value_t));
 
-               list.values_len = av2value (aTHX_ list.type, (AV *)SvRV (*tmp),
-                               val, len);
-               list.values = val;
+       ret = plugin_dispatch_values (&vl);
 
-               if (-1 == list.values_len) {
-                       sfree (val);
-                       return -1;
-               }
-       }
+       sfree (vl.values);
+       return ret;
+} /* static int pplugin_dispatch_values (char *, HV *) */
 
-       if (NULL != (tmp = hv_fetch (values, "time", 4, 0))) {
-               list.time = (time_t)SvIV (*tmp);
-       }
-       else {
-               list.time = time (NULL);
-       }
+/*
+ * Submit the values to a single write function.
+ */
+static int pplugin_write (pTHX_ const char *plugin, AV *data_set, HV *values)
+{
+       data_set_t   ds;
+       value_list_t vl = VALUE_LIST_INIT;
 
-       if (NULL != (tmp = hv_fetch (values, "host", 4, 0))) {
-               sstrncpy (list.host, SvPV_nolen (*tmp), sizeof (list.host));
-       }
-       else {
-               sstrncpy (list.host, hostname_g, sizeof (list.host));
-       }
+       int ret;
 
-       if (NULL != (tmp = hv_fetch (values, "plugin", 6, 0)))
-               sstrncpy (list.plugin, SvPV_nolen (*tmp), sizeof (list.plugin));
+       if (NULL == values)
+               return -1;
 
-       if (NULL != (tmp = hv_fetch (values, "plugin_instance", 15, 0)))
-               sstrncpy (list.plugin_instance, SvPV_nolen (*tmp),
-                               sizeof (list.plugin_instance));
+       if (0 != hv2value_list (aTHX_ values, &vl))
+               return -1;
 
-       if (NULL != (tmp = hv_fetch (values, "type_instance", 13, 0)))
-               sstrncpy (list.type_instance, SvPV_nolen (*tmp),
-                               sizeof (list.type_instance));
+       if ((NULL != data_set)
+                       && (0 != av2data_set (aTHX_ data_set, vl.type, &ds)))
+               return -1;
 
-       ret = plugin_dispatch_values (&list);
+       ret = plugin_write (plugin, NULL == data_set ? NULL : &ds, &vl);
+       if (0 != ret)
+               log_warn ("Dispatching value to plugin \"%s\" failed with status %i.",
+                               NULL == plugin ? "<any>" : plugin, ret);
 
-       sfree (val);
+       if (NULL != data_set)
+               sfree (ds.ds);
+       sfree (vl.values);
        return ret;
-} /* static int pplugin_dispatch_values (char *, HV *) */
+} /* static int pplugin_write (const char *plugin, HV *, HV *) */
 
 /*
  * Dispatch a notification.
- *
- * notification:
- * {
- *   severity => $severity,
- *   time     => $time,
- *   message  => $msg,
- *   host     => $host,
- *   plugin   => $plugin,
- *   type     => $type,
- *   plugin_instance => $instance,
- *   type_instance   => $type_instance
- * }
  */
 static int pplugin_dispatch_notification (pTHX_ HV *notif)
 {
        notification_t n;
 
-       SV **tmp = NULL;
+       int ret;
 
        if (NULL == notif)
                return -1;
 
        memset (&n, 0, sizeof (n));
 
-       if (NULL != (tmp = hv_fetch (notif, "severity", 8, 0)))
-               n.severity = SvIV (*tmp);
-       else
-               n.severity = NOTIF_FAILURE;
-
-       if (NULL != (tmp = hv_fetch (notif, "time", 4, 0)))
-               n.time = (time_t)SvIV (*tmp);
-       else
-               n.time = time (NULL);
-
-       if (NULL != (tmp = hv_fetch (notif, "message", 7, 0)))
-               sstrncpy (n.message, SvPV_nolen (*tmp), sizeof (n.message));
-
-       if (NULL != (tmp = hv_fetch (notif, "host", 4, 0)))
-               sstrncpy (n.host, SvPV_nolen (*tmp), sizeof (n.host));
-       else
-               sstrncpy (n.host, hostname_g, sizeof (n.host));
-
-       if (NULL != (tmp = hv_fetch (notif, "plugin", 6, 0)))
-               sstrncpy (n.plugin, SvPV_nolen (*tmp), sizeof (n.plugin));
-
-       if (NULL != (tmp = hv_fetch (notif, "plugin_instance", 15, 0)))
-               sstrncpy (n.plugin_instance, SvPV_nolen (*tmp),
-                               sizeof (n.plugin_instance));
-
-       if (NULL != (tmp = hv_fetch (notif, "type", 4, 0)))
-               sstrncpy (n.type, SvPV_nolen (*tmp), sizeof (n.type));
+       if (0 != hv2notification (aTHX_ notif, &n))
+               return -1;
 
-       if (NULL != (tmp = hv_fetch (notif, "type_instance", 13, 0)))
-               sstrncpy (n.type_instance, SvPV_nolen (*tmp), sizeof (n.type_instance));
-       return plugin_dispatch_notification (&n);
+       ret = plugin_dispatch_notification (&n);
+       plugin_notification_meta_free (n.meta);
+       return ret;
 } /* static int pplugin_dispatch_notification (HV *) */
 
 /*
@@ -783,91 +1025,487 @@ static int pplugin_call_all (pTHX_ int type, ...)
                AV *pds = newAV ();
                HV *pvl = newHV ();
 
-               ds = va_arg (ap, data_set_t *);
-               vl = va_arg (ap, value_list_t *);
+               ds = va_arg (ap, data_set_t *);
+               vl = va_arg (ap, value_list_t *);
+
+               if (-1 == data_set2av (aTHX_ ds, pds)) {
+                       av_clear (pds);
+                       av_undef (pds);
+                       pds = (AV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               if (-1 == value_list2hv (aTHX_ vl, ds, pvl)) {
+                       hv_clear (pvl);
+                       hv_undef (pvl);
+                       pvl = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               XPUSHs (sv_2mortal (newSVpv (ds->type, 0)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pds)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pvl)));
+       }
+       else if (PLUGIN_LOG == type) {
+               /*
+                * $_[0] = $level;
+                *
+                * $_[1] = $message;
+                */
+               XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
+               XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
+       }
+       else if (PLUGIN_NOTIF == type) {
+               /*
+                * $_[0] =
+                * {
+                *   severity => $severity,
+                *   time     => $time,
+                *   message  => $msg,
+                *   host     => $host,
+                *   plugin   => $plugin,
+                *   type     => $type,
+                *   plugin_instance => $instance,
+                *   type_instance   => $type_instance
+                * };
+                */
+               notification_t *n;
+               HV *notif = newHV ();
+
+               n = va_arg (ap, notification_t *);
+
+               if (-1 == notification2hv (aTHX_ n, notif)) {
+                       hv_clear (notif);
+                       hv_undef (notif);
+                       notif = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)notif)));
+       }
+       else if (PLUGIN_FLUSH == type) {
+               /*
+                * $_[0] = $timeout;
+                * $_[1] = $identifier;
+                */
+               XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
+               XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
+       }
+
+       PUTBACK;
+
+       retvals = call_pv ("Collectd::plugin_call_all", G_SCALAR);
+
+       SPAGAIN;
+       if (0 < retvals) {
+               SV *tmp = POPs;
+               if (! SvTRUE (tmp))
+                       ret = -1;
+       }
+
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+
+       va_end (ap);
+       return ret;
+} /* static int pplugin_call_all (int, ...) */
+
+/*
+ * collectd's perl interpreter based thread implementation.
+ *
+ * This has been inspired by Perl's ithreads introduced in version 5.6.0.
+ */
+
+/* must be called with perl_threads->mutex locked */
+static void c_ithread_destroy (c_ithread_t *ithread)
+{
+       dTHXa (ithread->interp);
+
+       assert (NULL != perl_threads);
+
+       PERL_SET_CONTEXT (aTHX);
+       log_debug ("Shutting down Perl interpreter %p...", aTHX);
+
+#if COLLECT_DEBUG
+       sv_report_used ();
+
+       --perl_threads->number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       perl_destruct (aTHX);
+       perl_free (aTHX);
+
+       if (NULL == ithread->prev)
+               perl_threads->head = ithread->next;
+       else
+               ithread->prev->next = ithread->next;
+
+       if (NULL == ithread->next)
+               perl_threads->tail = ithread->prev;
+       else
+               ithread->next->prev = ithread->prev;
+
+       sfree (ithread);
+       return;
+} /* static void c_ithread_destroy (c_ithread_t *) */
+
+static void c_ithread_destructor (void *arg)
+{
+       c_ithread_t *ithread = (c_ithread_t *)arg;
+       c_ithread_t *t = NULL;
+
+       if (NULL == perl_threads)
+               return;
+
+       pthread_mutex_lock (&perl_threads->mutex);
+
+       for (t = perl_threads->head; NULL != t; t = t->next)
+               if (t == ithread)
+                       break;
+
+       /* the ithread no longer exists */
+       if (NULL == t)
+               return;
+
+       c_ithread_destroy (ithread);
+
+       pthread_mutex_unlock (&perl_threads->mutex);
+       return;
+} /* static void c_ithread_destructor (void *) */
+
+/* must be called with perl_threads->mutex locked */
+static c_ithread_t *c_ithread_create (PerlInterpreter *base)
+{
+       c_ithread_t *t = NULL;
+       dTHXa (NULL);
+
+       assert (NULL != perl_threads);
+
+       t = (c_ithread_t *)smalloc (sizeof (c_ithread_t));
+       memset (t, 0, sizeof (c_ithread_t));
+
+       t->interp = (NULL == base)
+               ? NULL
+               : perl_clone (base, CLONEf_KEEP_PTR_TABLE);
+
+       aTHX = t->interp;
+
+       if ((NULL != base) && (NULL != PL_endav)) {
+               av_clear (PL_endav);
+               av_undef (PL_endav);
+               PL_endav = Nullav;
+       }
+
+#if COLLECT_DEBUG
+       ++perl_threads->number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       t->next = NULL;
+
+       if (NULL == perl_threads->tail) {
+               perl_threads->head = t;
+               t->prev = NULL;
+       }
+       else {
+               perl_threads->tail->next = t;
+               t->prev = perl_threads->tail;
+       }
+
+       perl_threads->tail = t;
+
+       pthread_setspecific (perl_thr_key, (const void *)t);
+       return t;
+} /* static c_ithread_t *c_ithread_create (PerlInterpreter *) */
+
+/*
+ * Filter chains implementation.
+ */
+
+static int fc_call (pTHX_ int type, int cb_type, pfc_user_data_t *data, ...)
+{
+       int retvals = 0;
+
+       va_list ap;
+       int ret = 0;
+
+       notification_meta_t **meta  = NULL;
+       AV                   *pmeta = NULL;
+
+       dSP;
+
+       if ((type < 0) || (type >= FC_TYPES))
+               return -1;
+
+       if ((cb_type < 0) || (cb_type >= FC_CB_TYPES))
+               return -1;
+
+       va_start (ap, data);
+
+       ENTER;
+       SAVETMPS;
+
+       PUSHMARK (SP);
+
+       XPUSHs (sv_2mortal (newSViv ((IV)type)));
+       XPUSHs (sv_2mortal (newSVpv (data->name, 0)));
+       XPUSHs (sv_2mortal (newSViv ((IV)cb_type)));
+
+       if (FC_CB_CREATE == cb_type) {
+               /*
+                * $_[0] = $ci;
+                * $_[1] = $user_data;
+                */
+               oconfig_item_t *ci;
+               HV *config = newHV ();
+
+               ci = va_arg (ap, oconfig_item_t *);
+
+               if (0 != oconfig_item2hv (aTHX_ ci, config)) {
+                       hv_clear (config);
+                       hv_undef (config);
+                       config = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)config)));
+       }
+       else if (FC_CB_DESTROY == cb_type) {
+               /*
+                * $_[1] = $user_data;
+                */
+
+               /* nothing to be done - the user data pointer
+                * is pushed onto the stack later */
+       }
+       else if (FC_CB_EXEC == cb_type) {
+               /*
+                * $_[0] = $ds;
+                * $_[1] = $vl;
+                * $_[2] = $meta;
+                * $_[3] = $user_data;
+                */
+               data_set_t   *ds;
+               value_list_t *vl;
+
+               AV *pds = newAV ();
+               HV *pvl = newHV ();
+
+               ds   = va_arg (ap, data_set_t *);
+               vl   = va_arg (ap, value_list_t *);
+               meta = va_arg (ap, notification_meta_t **);
+
+               if (0 != data_set2av (aTHX_ ds, pds)) {
+                       av_clear (pds);
+                       av_undef (pds);
+                       pds = (AV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               if (0 != value_list2hv (aTHX_ vl, ds, pvl)) {
+                       hv_clear (pvl);
+                       hv_undef (pvl);
+                       pvl = (HV *)&PL_sv_undef;
+                       ret = -1;
+               }
+
+               if (NULL != meta) {
+                       pmeta = newAV ();
+
+                       if (0 != notification_meta2av (aTHX_ *meta, pmeta)) {
+                               av_clear (pmeta);
+                               av_undef (pmeta);
+                               pmeta = (AV *)&PL_sv_undef;
+                               ret = -1;
+                       }
+               }
+               else {
+                       pmeta = (AV *)&PL_sv_undef;
+               }
+
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pds)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pvl)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pmeta)));
+       }
+
+       XPUSHs (sv_2mortal (newRV_inc (data->user_data)));
+
+       PUTBACK;
+
+       retvals = call_pv ("Collectd::fc_call", G_SCALAR);
+
+       if ((FC_CB_EXEC == cb_type) && (meta != NULL)) {
+               assert (pmeta != NULL);
+
+               plugin_notification_meta_free (*meta);
+               av2notification_meta (aTHX_ pmeta, meta);
+       }
+
+       SPAGAIN;
+       if (0 < retvals) {
+               SV *tmp = POPs;
+
+               /* the exec callbacks return a status, while
+                * the others return a boolean value */
+               if (FC_CB_EXEC == cb_type)
+                       ret = SvIV (tmp);
+               else if (! SvTRUE (tmp))
+                       ret = -1;
+       }
+
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+
+       va_end (ap);
+       return ret;
+} /* static int fc_call (int, int, pfc_user_data_t *, ...) */
+
+static int fc_create (int type, const oconfig_item_t *ci, void **user_data)
+{
+       pfc_user_data_t *data;
+
+       int ret = 0;
+
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("fc_create: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_warn ("A \"%s\" block expects a single string argument.",
+                               (FC_MATCH == type) ? "Match" : "Target");
+               return -1;
+       }
+
+       data = (pfc_user_data_t *)smalloc (sizeof (*data));
+       data->name      = sstrdup (ci->values[0].value.string);
+       data->user_data = newSV (0);
+
+       ret = fc_call (aTHX_ type, FC_CB_CREATE, data, ci);
+
+       if (0 != ret)
+               PFC_USER_DATA_FREE (data);
+       else
+               *user_data = data;
+       return ret;
+} /* static int fc_create (int, const oconfig_item_t *, void **) */
+
+static int fc_destroy (int type, void **user_data)
+{
+       pfc_user_data_t *data = *(pfc_user_data_t **)user_data;
+
+       int ret = 0;
+
+       dTHX;
+
+       if ((NULL == perl_threads) || (NULL == data))
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("fc_destroy: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+
+       ret = fc_call (aTHX_ type, FC_CB_DESTROY, data);
+
+       PFC_USER_DATA_FREE (data);
+       *user_data = NULL;
+       return ret;
+} /* static int fc_destroy (int, void **) */
+
+static int fc_exec (int type, const data_set_t *ds, const value_list_t *vl,
+               notification_meta_t **meta, void **user_data)
+{
+       pfc_user_data_t *data = *(pfc_user_data_t **)user_data;
+
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       assert (NULL != data);
 
-               if (-1 == data_set2av (aTHX_ ds, pds)) {
-                       av_clear (pds);
-                       av_undef (pds);
-                       pds = Nullav;
-                       ret = -1;
-               }
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
 
-               if (-1 == value_list2hv (aTHX_ vl, ds, pvl)) {
-                       hv_clear (pvl);
-                       hv_undef (pvl);
-                       pvl = Nullhv;
-                       ret = -1;
-               }
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
 
-               XPUSHs (sv_2mortal (newSVpv (ds->type, 0)));
-               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pds)));
-               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pvl)));
-       }
-       else if (PLUGIN_LOG == type) {
-               /*
-                * $_[0] = $level;
-                *
-                * $_[1] = $message;
-                */
-               XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
-               XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
+               aTHX = t->interp;
        }
-       else if (PLUGIN_NOTIF == type) {
-               /*
-                * $_[0] =
-                * {
-                *   severity => $severity,
-                *   time     => $time,
-                *   message  => $msg,
-                *   host     => $host,
-                *   plugin   => $plugin,
-                *   type     => $type,
-                *   plugin_instance => $instance,
-                *   type_instance   => $type_instance
-                * };
-                */
-               notification_t *n;
-               HV *notif = newHV ();
 
-               n = va_arg (ap, notification_t *);
+       log_debug ("fc_exec: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
 
-               if (-1 == notification2hv (aTHX_ n, notif)) {
-                       hv_clear (notif);
-                       hv_undef (notif);
-                       notif = Nullhv;
-                       ret = -1;
-               }
+       return fc_call (aTHX_ type, FC_CB_EXEC, data, ds, vl, meta);
+} /* static int fc_exec (int, const data_set_t *, const value_list_t *,
+               notification_meta_t **, void **) */
 
-               XPUSHs (sv_2mortal (newRV_noinc ((SV *)notif)));
-       }
-       else if (PLUGIN_FLUSH == type) {
-               /*
-                * $_[0] = $timeout;
-                * $_[1] = $identifier;
-                */
-               XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
-               XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
-       }
+static int pmatch_create (const oconfig_item_t *ci, void **user_data)
+{
+       return fc_create (FC_MATCH, ci, user_data);
+} /* static int pmatch_create (const oconfig_item_t *, void **) */
 
-       PUTBACK;
+static int pmatch_destroy (void **user_data)
+{
+       return fc_destroy (FC_MATCH, user_data);
+} /* static int pmatch_destroy (void **) */
 
-       retvals = call_pv ("Collectd::plugin_call_all", G_SCALAR);
+static int pmatch_match (const data_set_t *ds, const value_list_t *vl,
+               notification_meta_t **meta, void **user_data)
+{
+       return fc_exec (FC_MATCH, ds, vl, meta, user_data);
+} /* static int pmatch_match (const data_set_t *, const value_list_t *,
+               notification_meta_t **, void **) */
 
-       SPAGAIN;
-       if (0 < retvals) {
-               SV *tmp = POPs;
-               if (! SvTRUE (tmp))
-                       ret = -1;
-       }
+static match_proc_t pmatch = {
+       pmatch_create, pmatch_destroy, pmatch_match
+};
 
-       PUTBACK;
-       FREETMPS;
-       LEAVE;
+static int ptarget_create (const oconfig_item_t *ci, void **user_data)
+{
+       return fc_create (FC_TARGET, ci, user_data);
+} /* static int ptarget_create (const oconfig_item_t *, void **) */
 
-       va_end (ap);
-       return ret;
-} /* static int pplugin_call_all (int, ...) */
+static int ptarget_destroy (void **user_data)
+{
+       return fc_destroy (FC_TARGET, user_data);
+} /* static int ptarget_destroy (void **) */
+
+static int ptarget_invoke (const data_set_t *ds, value_list_t *vl,
+               notification_meta_t **meta, void **user_data)
+{
+       return fc_exec (FC_TARGET, ds, vl, meta, user_data);
+} /* static int ptarget_invoke (const data_set_t *, value_list_t *,
+               notification_meta_t **, void **) */
+
+static target_proc_t ptarget = {
+       ptarget_create, ptarget_destroy, ptarget_invoke
+};
 
 /*
  * Exported Perl API.
@@ -994,6 +1632,65 @@ static XS (Collectd_plugin_dispatch_values)
                XSRETURN_EMPTY;
 } /* static XS (Collectd_plugin_dispatch_values) */
 
+/* Collectd::plugin_write (plugin, ds, vl).
+ *
+ * plugin:
+ *   name of the plugin to call, may be 'undef'
+ *
+ * ds:
+ *   data-set that describes the submitted values, may be 'undef'
+ *
+ * vl:
+ *   value-list to be written
+ */
+static XS (Collectd__plugin_write)
+{
+       char *plugin;
+       SV   *ds, *vl;
+       AV   *ds_array;
+
+       int ret;
+
+       dXSARGS;
+
+       if (3 != items) {
+               log_err ("Usage: Collectd::plugin_write(plugin, ds, vl)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_write: plugin=\"%s\", ds=\"%s\", vl=\"%s\"",
+                       SvPV_nolen (ST (0)), SvOK (ST (1)) ? SvPV_nolen (ST (1)) : "",
+                       SvPV_nolen (ST (2)));
+
+       if (! SvOK (ST (0)))
+               plugin = NULL;
+       else
+               plugin = SvPV_nolen (ST (0));
+
+       ds = ST (1);
+       if (SvROK (ds) && (SVt_PVAV == SvTYPE (SvRV (ds))))
+               ds_array = (AV *)SvRV (ds);
+       else if (! SvOK (ds))
+               ds_array = NULL;
+       else {
+               log_err ("Collectd::plugin_write: Invalid data-set.");
+               XSRETURN_EMPTY;
+       }
+
+       vl = ST (2);
+       if (! (SvROK (vl) && (SVt_PVHV == SvTYPE (SvRV (vl))))) {
+               log_err ("Collectd::plugin_write: Invalid value-list.");
+               XSRETURN_EMPTY;
+       }
+
+       ret = pplugin_write (aTHX_ plugin, ds_array, (HV *)SvRV (vl));
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd__plugin_write) */
+
 /*
  * Collectd::_plugin_flush (plugin, timeout, identifier).
  *
@@ -1097,6 +1794,43 @@ static XS (Collectd_plugin_log)
 } /* static XS (Collectd_plugin_log) */
 
 /*
+ * Collectd::_fc_register (type, name)
+ *
+ * type:
+ *   match | target
+ *
+ * name:
+ *   name of the match
+ */
+static XS (Collectd__fc_register)
+{
+       int   type;
+       char *name;
+
+       int ret = 0;
+
+       dXSARGS;
+
+       if (2 != items) {
+               log_err ("Usage: Collectd::_fc_register(type, name)");
+               XSRETURN_EMPTY;
+       }
+
+       type = SvIV (ST (0));
+       name = SvPV_nolen (ST (1));
+
+       if (FC_MATCH == type)
+               ret = fc_register_match (name, pmatch);
+       else if (FC_TARGET == type)
+               ret = fc_register_target (name, ptarget);
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_fc_register) */
+
+/*
  * Collectd::call_by_name (...).
  *
  * Call a Perl sub identified by its name passed through $Collectd::cb_name.
@@ -1126,113 +1860,6 @@ static XS (Collectd_call_by_name)
 } /* static XS (Collectd_call_by_name) */
 
 /*
- * collectd's perl interpreter based thread implementation.
- *
- * This has been inspired by Perl's ithreads introduced in version 5.6.0.
- */
-
-/* must be called with perl_threads->mutex locked */
-static void c_ithread_destroy (c_ithread_t *ithread)
-{
-       dTHXa (ithread->interp);
-
-       assert (NULL != perl_threads);
-
-       PERL_SET_CONTEXT (aTHX);
-       log_debug ("Shutting down Perl interpreter %p...", aTHX);
-
-#if COLLECT_DEBUG
-       sv_report_used ();
-
-       --perl_threads->number_of_threads;
-#endif /* COLLECT_DEBUG */
-
-       perl_destruct (aTHX);
-       perl_free (aTHX);
-
-       if (NULL == ithread->prev)
-               perl_threads->head = ithread->next;
-       else
-               ithread->prev->next = ithread->next;
-
-       if (NULL == ithread->next)
-               perl_threads->tail = ithread->prev;
-       else
-               ithread->next->prev = ithread->prev;
-
-       sfree (ithread);
-       return;
-} /* static void c_ithread_destroy (c_ithread_t *) */
-
-static void c_ithread_destructor (void *arg)
-{
-       c_ithread_t *ithread = (c_ithread_t *)arg;
-       c_ithread_t *t = NULL;
-
-       if (NULL == perl_threads)
-               return;
-
-       pthread_mutex_lock (&perl_threads->mutex);
-
-       for (t = perl_threads->head; NULL != t; t = t->next)
-               if (t == ithread)
-                       break;
-
-       /* the ithread no longer exists */
-       if (NULL == t)
-               return;
-
-       c_ithread_destroy (ithread);
-
-       pthread_mutex_unlock (&perl_threads->mutex);
-       return;
-} /* static void c_ithread_destructor (void *) */
-
-/* must be called with perl_threads->mutex locked */
-static c_ithread_t *c_ithread_create (PerlInterpreter *base)
-{
-       c_ithread_t *t = NULL;
-       dTHXa (NULL);
-
-       assert (NULL != perl_threads);
-
-       t = (c_ithread_t *)smalloc (sizeof (c_ithread_t));
-       memset (t, 0, sizeof (c_ithread_t));
-
-       t->interp = (NULL == base)
-               ? NULL
-               : perl_clone (base, CLONEf_KEEP_PTR_TABLE);
-
-       aTHX = t->interp;
-
-       if ((NULL != base) && (NULL != PL_endav)) {
-               av_clear (PL_endav);
-               av_undef (PL_endav);
-               PL_endav = Nullav;
-       }
-
-#if COLLECT_DEBUG
-       ++perl_threads->number_of_threads;
-#endif /* COLLECT_DEBUG */
-
-       t->next = NULL;
-
-       if (NULL == perl_threads->tail) {
-               perl_threads->head = t;
-               t->prev = NULL;
-       }
-       else {
-               perl_threads->tail->next = t;
-               t->prev = perl_threads->tail;
-       }
-
-       perl_threads->tail = t;
-
-       pthread_setspecific (perl_thr_key, (const void *)t);
-       return t;
-} /* static c_ithread_t *c_ithread_create (PerlInterpreter *) */
-
-/*
  * Interface to collectd.
  */
 
@@ -1457,8 +2084,18 @@ static int g_iv_set (pTHX_ SV *var, MAGIC *mg)
        return 0;
 } /* static int g_iv_set (pTHX_ SV *, MAGIC *) */
 
-static MGVTBL g_pv_vtbl = { g_pv_get, g_pv_set, NULL, NULL, NULL, NULL, NULL };
-static MGVTBL g_iv_vtbl = { g_iv_get, g_iv_set, NULL, NULL, NULL, NULL, NULL };
+static MGVTBL g_pv_vtbl = {
+       g_pv_get, g_pv_set, NULL, NULL, NULL, NULL, NULL
+#if HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL
+               , NULL
+#endif
+};
+static MGVTBL g_iv_vtbl = {
+       g_iv_get, g_iv_set, NULL, NULL, NULL, NULL, NULL
+#if HAVE_PERL_STRUCT_MGVTBL_SVT_LOCAL
+               , NULL
+#endif
+};
 
 /* bootstrap the Collectd module */
 static void xs_init (pTHX)
@@ -1751,7 +2388,7 @@ static int perl_config_plugin (pTHX_ oconfig_item_t *ci)
                hv_undef (config);
 
                log_err ("Unable to convert configuration to a Perl hash value.");
-               config = Nullhv;
+               config = (HV *)&PL_sv_undef;
        }
 
        ENTER;
@@ -1806,7 +2443,10 @@ static int perl_config (oconfig_item_t *ci)
                else if (0 == strcasecmp (c->key, "Plugin"))
                        current_status = perl_config_plugin (aTHX_ c);
                else
+               {
                        log_warn ("Ignoring unknown config key \"%s\".", c->key);
+                       current_status = 0;
+               }
 
                /* fatal error - it's up to perl_config_* to clean up */
                if (0 > current_status) {
index 70a8373..084e456 100644 (file)
@@ -185,7 +185,6 @@ static void ping_submit (char *host, double latency)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "ping", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
index 20f3aa2..74565c3 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/plugin.c
- * Copyright (C) 2005-2008  Florian octo Forster
+ * Copyright (C) 2005-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
@@ -36,6 +36,7 @@
 #include "utils_llist.h"
 #include "utils_cache.h"
 #include "utils_threshold.h"
+#include "filter_chain.h"
 
 /*
  * Private structures
@@ -60,6 +61,9 @@ static llist_t *list_shutdown;
 static llist_t *list_log;
 static llist_t *list_notification;
 
+static fc_chain_t *pre_cache_chain = NULL;
+static fc_chain_t *post_cache_chain = NULL;
+
 static c_avl_tree_t *data_sets;
 
 static char *plugindir = NULL;
@@ -167,7 +171,7 @@ static int plugin_load_file (char *file)
        return (0);
 }
 
-static void *plugin_read_thread (void *args)
+static void *plugin_read_thread (void __attribute__((unused)) *args)
 {
        llentry_t   *le;
        read_func_t *rf;
@@ -274,6 +278,9 @@ static void stop_threads (void)
 {
        int i;
 
+       if (read_threads == NULL)
+               return;
+
        pthread_mutex_lock (&read_lock);
        read_loop = 0;
        DEBUG ("plugin: stop_threads: Signalling `read_cond'");
@@ -321,6 +328,7 @@ int plugin_load (const char *type)
        int   ret;
        struct stat    statbuf;
        struct dirent *de;
+       int status;
 
        DEBUG ("type = %s", type);
 
@@ -329,8 +337,8 @@ int plugin_load (const char *type)
 
        /* `cpu' should not match `cpufreq'. To solve this we add `.so' to the
         * type when matching the filename */
-       if (ssnprintf (typename, sizeof (typename),
-                       "%s.so", type) >= sizeof (typename))
+       status = ssnprintf (typename, sizeof (typename), "%s.so", type);
+       if ((status < 0) || ((size_t) status >= sizeof (typename)))
        {
                WARNING ("snprintf: truncated: `%s.so'", type);
                return (-1);
@@ -350,8 +358,9 @@ int plugin_load (const char *type)
                if (strncasecmp (de->d_name, typename, typename_len))
                        continue;
 
-               if (ssnprintf (filename, sizeof (filename),
-                               "%s/%s", dir, de->d_name) >= sizeof (filename))
+               status = ssnprintf (filename, sizeof (filename),
+                               "%s/%s", dir, de->d_name);
+               if ((status < 0) || ((size_t) status >= sizeof (filename)))
                {
                        WARNING ("snprintf: truncated: `%s/%s'", dir, de->d_name);
                        continue;
@@ -581,6 +590,7 @@ int plugin_unregister_notification (const char *name)
 
 void plugin_init_all (void)
 {
+       const char *chain_name;
        int (*callback) (void);
        llentry_t *le;
        int status;
@@ -588,6 +598,13 @@ void plugin_init_all (void)
        /* Init the value cache */
        uc_init ();
 
+       chain_name = global_option_get ("PreCacheChain");
+       pre_cache_chain = fc_chain_get_by_name (chain_name);
+
+       chain_name = global_option_get ("PostCacheChain");
+       post_cache_chain = fc_chain_get_by_name (chain_name);
+
+
        if ((list_init == NULL) && (list_read == NULL))
                return;
 
@@ -623,7 +640,8 @@ void plugin_init_all (void)
                int num;
                rt = global_option_get ("ReadThreads");
                num = atoi (rt);
-               start_threads ((num > 0) ? num : 5);
+               if (num != -1)
+                       start_threads ((num > 0) ? num : 5);
        }
 } /* void plugin_init_all */
 
@@ -666,6 +684,104 @@ void plugin_read_all (void)
        pthread_mutex_unlock (&read_lock);
 } /* void plugin_read_all */
 
+/* Read function called when the `-T' command line argument is given. */
+int plugin_read_all_once (void)
+{
+       llentry_t   *le;
+       read_func_t *rf;
+       int status;
+       int return_status = 0;
+
+       if (list_read == NULL)
+       {
+               NOTICE ("No read-functions are registered.");
+               return (0);
+       }
+
+       for (le = llist_head (list_read);
+            le != NULL;
+            le = le->next)
+       {
+               rf = (read_func_t *) le->value;
+               status = rf->callback ();
+               if (status != 0)
+               {
+                       NOTICE ("read-function of plugin `%s' failed.",
+                               le->key);
+                       return_status = -1;
+               }
+       }
+
+       return (return_status);
+} /* int plugin_read_all_once */
+
+int plugin_write (const char *plugin, /* {{{ */
+               const data_set_t *ds, const value_list_t *vl)
+{
+  int (*callback) (const data_set_t *ds, const value_list_t *vl);
+  llentry_t *le;
+  int status;
+
+  if (vl == NULL)
+    return (EINVAL);
+
+  if (list_write == NULL)
+    return (ENOENT);
+
+  if (ds == NULL)
+  {
+    ds = plugin_get_ds (vl->type);
+    if (ds == NULL)
+    {
+      ERROR ("plugin_write: Unable to lookup type `%s'.", vl->type);
+      return (ENOENT);
+    }
+  }
+
+  if (plugin == NULL)
+  {
+    int success = 0;
+    int failure = 0;
+
+    le = llist_head (list_write);
+    while (le != NULL)
+    {
+      callback = le->value;
+      status = (*callback) (ds, vl);
+      if (status != 0)
+        failure++;
+      else
+        success++;
+
+      le = le->next;
+    }
+
+    if ((success == 0) && (failure != 0))
+      status = -1;
+    else
+      status = 0;
+  }
+  else /* plugin != NULL */
+  {
+    le = llist_head (list_write);
+    while (le != NULL)
+    {
+      if (strcasecmp (plugin, le->key) == 0)
+        break;
+
+      le = le->next;
+    }
+
+    if (le == NULL)
+      return (ENOENT);
+
+    callback = le->value;
+    status = (*callback) (ds, vl);
+  }
+
+  return (status);
+} /* }}} int plugin_write */
+
 int plugin_flush (const char *plugin, int timeout, const char *identifier)
 {
   int (*callback) (int timeout, const char *identifier);
@@ -719,13 +835,17 @@ void plugin_shutdown_all (void)
 
 int plugin_dispatch_values (value_list_t *vl)
 {
-       static c_complain_t no_write_complaint = C_COMPLAIN_INIT;
+       int status;
+       static c_complain_t no_write_complaint = C_COMPLAIN_INIT_STATIC;
+
+       value_t *saved_values;
+       int      saved_values_len;
 
-       int (*callback) (const data_set_t *, const value_list_t *);
        data_set_t *ds;
-       llentry_t *le;
 
-       if ((vl == NULL) || (*vl->type == '\0')) {
+       if ((vl == NULL) || (vl->type[0] == 0)
+                       || (vl->values == NULL) || (vl->values_len < 1))
+       {
                ERROR ("plugin_dispatch_values: Invalid value list.");
                return (-1);
        }
@@ -750,6 +870,9 @@ int plugin_dispatch_values (value_list_t *vl)
                return (-1);
        }
 
+       if (vl->time == 0)
+               vl->time = time (NULL);
+
        DEBUG ("plugin_dispatch_values: time = %u; interval = %i; "
                        "host = %s; "
                        "plugin = %s; plugin_instance = %s; "
@@ -786,17 +909,79 @@ int plugin_dispatch_values (value_list_t *vl)
        escape_slashes (vl->type, sizeof (vl->type));
        escape_slashes (vl->type_instance, sizeof (vl->type_instance));
 
+       /* Copy the values. This way, we can assure `targets' that they get
+        * dynamically allocated values, which they can free and replace if
+        * they like. */
+       if ((pre_cache_chain != NULL) || (post_cache_chain != NULL))
+       {
+               saved_values     = vl->values;
+               saved_values_len = vl->values_len;
+
+               vl->values = (value_t *) calloc (vl->values_len,
+                               sizeof (*vl->values));
+               if (vl->values == NULL)
+               {
+                       ERROR ("plugin_dispatch_values: calloc failed.");
+                       vl->values = saved_values;
+                       return (-1);
+               }
+               memcpy (vl->values, saved_values,
+                               vl->values_len * sizeof (*vl->values));
+       }
+       else /* if ((pre == NULL) && (post == NULL)) */
+       {
+               saved_values     = NULL;
+               saved_values_len = 0;
+       }
+
+       if (pre_cache_chain != NULL)
+       {
+               status = fc_process_chain (ds, vl, pre_cache_chain);
+               if (status < 0)
+               {
+                       WARNING ("plugin_dispatch_values: Running the "
+                                       "pre-cache chain failed with "
+                                       "status %i (%#x).",
+                                       status, status);
+               }
+               else if (status == FC_TARGET_STOP)
+               {
+                       /* Restore the state of the value_list so that plugins
+                        * don't get confused.. */
+                       if (saved_values != NULL)
+                       {
+                               free (vl->values);
+                               vl->values     = saved_values;
+                               vl->values_len = saved_values_len;
+                       }
+                       return (0);
+               }
+       }
+
        /* Update the value cache */
        uc_update (ds, vl);
-       ut_check_threshold (ds, vl);
 
-       le = llist_head (list_write);
-       while (le != NULL)
+       if (post_cache_chain != NULL)
        {
-               callback = (int (*) (const data_set_t *, const value_list_t *)) le->value;
-               (*callback) (ds, vl);
+               status = fc_process_chain (ds, vl, post_cache_chain);
+               if (status < 0)
+               {
+                       WARNING ("plugin_dispatch_values: Running the "
+                                       "post-cache chain failed with "
+                                       "status %i (%#x).",
+                                       status, status);
+               }
+       }
+       else
+               fc_default_action (ds, vl);
 
-               le = le->next;
+       /* Restore the state of the value_list so that plugins don't get
+        * confused.. */
+       if (saved_values != NULL)
+       {
+               free (vl->values);
+               vl->values     = saved_values;
+               vl->values_len = saved_values_len;
        }
 
        return (0);
@@ -1019,7 +1204,7 @@ int plugin_notification_meta_copy (notification_t *dst,
   return (0);
 } /* int plugin_notification_meta_copy */
 
-int plugin_notification_meta_free (notification_t *n)
+int plugin_notification_meta_free (notification_meta_t *n)
 {
   notification_meta_t *this;
   notification_meta_t *next;
@@ -1030,8 +1215,7 @@ int plugin_notification_meta_free (notification_t *n)
     return (-1);
   }
 
-  this = n->meta;
-  n->meta = NULL;
+  this = n;
   while (this != NULL)
   {
     next = this->next;
index dc3bbb0..3088e06 100644 (file)
@@ -176,8 +176,39 @@ int plugin_load (const char *name);
 
 void plugin_init_all (void);
 void plugin_read_all (void);
+int plugin_read_all_once (void);
 void plugin_shutdown_all (void);
 
+/*
+ * NAME
+ *  plugin_write
+ *
+ * DESCRIPTION
+ *  Calls the write function of the given plugin with the provided data set and
+ *  value list. It differs from `plugin_dispatch_value' in that it does not
+ *  update the cache, does not do threshold checking, call the chain subsystem
+ *  and so on. It looks up the requested plugin and invokes the function, end
+ *  of story.
+ *
+ * ARGUMENTS
+ *  plugin     Name of the plugin. If NULL, the value is sent to all registered
+ *             write functions.
+ *  ds         Pointer to the data_set_t structure. If NULL, the data set is
+ *             looked up according to the `type' member in the `vl' argument.
+ *  vl         The actual value to be processed. Must not be NULL.
+ *
+ * RETURN VALUE
+ *  Returns zero upon success or non-zero if an error occurred. If `plugin' is
+ *  NULL and more than one plugin is called, an error is only returned if *all*
+ *  plugins fail.
+ *
+ * NOTES
+ *  This is the function used by the `write' built-in target. May be used by
+ *  other target plugins.
+ */
+int plugin_write (const char *plugin,
+    const data_set_t *ds, const value_list_t *vl);
+
 int plugin_flush (const char *plugin, int timeout, const char *identifier);
 
 /*
@@ -270,6 +301,6 @@ int plugin_notification_meta_add_boolean (notification_t *n,
 int plugin_notification_meta_copy (notification_t *dst,
     const notification_t *src);
 
-int plugin_notification_meta_free (notification_t *n);
+int plugin_notification_meta_free (notification_meta_t *n);
 
 #endif /* PLUGIN_H */
index faad16c..d6dd0ba 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/postgresql.c
- * Copyright (C) 2008  Sebastian Harl
+ * Copyright (C) 2008, 2009  Sebastian Harl
+ * Copyright (C) 2009        Florian 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
@@ -15,8 +16,9 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  *
- * Author:
+ * Authors:
  *   Sebastian Harl <sh at tokkee.org>
+ *   Florian Forster <octo at verplant.org>
  **/
 
 /*
@@ -29,6 +31,7 @@
 #include "configfile.h"
 #include "plugin.h"
 
+#include "utils_db_query.h"
 #include "utils_complain.h"
 
 #include <pg_config_manual.h>
@@ -80,39 +83,27 @@ typedef enum {
        C_PSQL_PARAM_HOST = 1,
        C_PSQL_PARAM_DB,
        C_PSQL_PARAM_USER,
+       C_PSQL_PARAM_INTERVAL,
 } c_psql_param_t;
 
+/* Parameter configuration. Stored as `user data' in the query objects. */
 typedef struct {
-       char *type;
-       char *type_instance;
-       int   ds_type;
-} c_psql_col_t;
-
-typedef struct {
-       char *name;
-       char *query;
-
        c_psql_param_t *params;
        int             params_num;
-
-       c_psql_col_t *cols;
-       int           cols_num;
-
-       int min_pg_version;
-       int max_pg_version;
-} c_psql_query_t;
+} c_psql_user_data_t;
 
 typedef struct {
        PGconn      *conn;
        c_complain_t conn_complaint;
 
        int proto_version;
+       int server_version;
 
        int max_params_num;
 
        /* user configuration */
-       c_psql_query_t **queries;
-       int              queries_num;
+       udb_query_t    **queries;
+       size_t           queries_num;
 
        char *host;
        char *port;
@@ -138,70 +129,12 @@ static char *def_queries[] = {
 };
 static int def_queries_num = STATIC_ARRAY_SIZE (def_queries);
 
-static c_psql_query_t *queries          = NULL;
-static int             queries_num      = 0;
+static udb_query_t      **queries       = NULL;
+static size_t             queries_num   = 0;
 
 static c_psql_database_t *databases     = NULL;
 static int                databases_num = 0;
 
-static c_psql_query_t *c_psql_query_new (const char *name)
-{
-       c_psql_query_t *query;
-
-       ++queries_num;
-       if (NULL == (queries = (c_psql_query_t *)realloc (queries,
-                               queries_num * sizeof (*queries)))) {
-               log_err ("Out of memory.");
-               exit (5);
-       }
-       query = queries + queries_num - 1;
-
-       query->name  = sstrdup (name);
-       query->query = NULL;
-
-       query->params     = NULL;
-       query->params_num = 0;
-
-       query->cols     = NULL;
-       query->cols_num = 0;
-
-       query->min_pg_version = 0;
-       query->max_pg_version = INT_MAX;
-       return query;
-} /* c_psql_query_new */
-
-static void c_psql_query_delete (c_psql_query_t *query)
-{
-       int i;
-
-       sfree (query->name);
-       sfree (query->query);
-
-       sfree (query->params);
-       query->params_num = 0;
-
-       for (i = 0; i < query->cols_num; ++i) {
-               sfree (query->cols[i].type);
-               sfree (query->cols[i].type_instance);
-       }
-       sfree (query->cols);
-       query->cols_num = 0;
-       return;
-} /* c_psql_query_delete */
-
-static c_psql_query_t *c_psql_query_get (const char *name, int server_version)
-{
-       int i;
-
-       for (i = 0; i < queries_num; ++i)
-               if (0 == strcasecmp (name, queries[i].name)
-                               && ((-1 == server_version)
-                                       || ((queries[i].min_pg_version <= server_version)
-                                               && (server_version <= queries[i].max_pg_version))))
-                       return queries + i;
-       return NULL;
-} /* c_psql_query_get */
-
 static c_psql_database_t *c_psql_database_new (const char *name)
 {
        c_psql_database_t *db;
@@ -217,15 +150,15 @@ static c_psql_database_t *c_psql_database_new (const char *name)
 
        db->conn = NULL;
 
-       db->conn_complaint.last     = 0;
-       db->conn_complaint.interval = 0;
+       C_COMPLAIN_INIT (&db->conn_complaint);
 
        db->proto_version = 0;
+       db->server_version = 0;
 
        db->max_params_num = 0;
 
-       db->queries     = NULL;
-       db->queries_num = 0;
+       db->queries        = NULL;
+       db->queries_num    = 0;
 
        db->database   = sstrdup (name);
        db->host       = NULL;
@@ -263,62 +196,6 @@ static void c_psql_database_delete (c_psql_database_t *db)
        return;
 } /* c_psql_database_delete */
 
-static void submit (const c_psql_database_t *db,
-               const char *type, const char *type_instance,
-               value_t *values, size_t values_len)
-{
-       value_list_t vl = VALUE_LIST_INIT;
-
-       vl.values     = values;
-       vl.values_len = values_len;
-       vl.time       = time (NULL);
-
-       if (C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
-                       || (0 == strcmp (db->host, "localhost")))
-               sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-       else
-               sstrncpy (vl.host, db->host, sizeof (vl.host));
-
-       sstrncpy (vl.plugin, "postgresql", sizeof (vl.plugin));
-       sstrncpy (vl.plugin_instance, db->database, sizeof (vl.plugin_instance));
-
-       sstrncpy (vl.type, type, sizeof (vl.type));
-
-       if (NULL != type_instance)
-               sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
-
-       plugin_dispatch_values (&vl);
-       return;
-} /* submit */
-
-static void submit_counter (const c_psql_database_t *db,
-               const char *type, const char *type_instance,
-               const char *value)
-{
-       value_t values[1];
-
-       if ((NULL == value) || ('\0' == *value))
-               return;
-
-       values[0].counter = atoll (value);
-       submit (db, type, type_instance, values, 1);
-       return;
-} /* submit_counter */
-
-static void submit_gauge (const c_psql_database_t *db,
-               const char *type, const char *type_instance,
-               const char *value)
-{
-       value_t values[1];
-
-       if ((NULL == value) || ('\0' == *value))
-               return;
-
-       values[0].gauge = atof (value);
-       submit (db, type, type_instance, values, 1);
-       return;
-} /* submit_gauge */
-
 static int c_psql_check_connection (c_psql_database_t *db)
 {
        /* "ping" */
@@ -344,21 +221,33 @@ static int c_psql_check_connection (c_psql_database_t *db)
                                        db->proto_version);
        }
 
+       db->server_version = PQserverVersion (db->conn);
+
        c_release (LOG_INFO, &db->conn_complaint,
                        "Successfully reconnected to database %s", PQdb (db->conn));
        return 0;
 } /* c_psql_check_connection */
 
+static PGresult *c_psql_exec_query_noparams (c_psql_database_t *db,
+               udb_query_t *q)
+{
+       return PQexec (db->conn, udb_query_get_statement (q));
+} /* c_psql_exec_query_noparams */
+
 static PGresult *c_psql_exec_query_params (c_psql_database_t *db,
-               c_psql_query_t *query)
+               udb_query_t *q, c_psql_user_data_t *data)
 {
        char *params[db->max_params_num];
+       char  interval[64];
        int   i;
 
-       assert (db->max_params_num >= query->params_num);
+       if ((data == NULL) || (data->params_num == 0))
+               return (c_psql_exec_query_noparams (db, q));
+
+       assert (db->max_params_num >= data->params_num);
 
-       for (i = 0; i < query->params_num; ++i) {
-               switch (query->params[i]) {
+       for (i = 0; i < data->params_num; ++i) {
+               switch (data->params[i]) {
                        case C_PSQL_PARAM_HOST:
                                params[i] = C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
                                        ? "localhost" : db->host;
@@ -369,85 +258,137 @@ static PGresult *c_psql_exec_query_params (c_psql_database_t *db,
                        case C_PSQL_PARAM_USER:
                                params[i] = db->user;
                                break;
+                       case C_PSQL_PARAM_INTERVAL:
+                               ssnprintf (interval, sizeof (interval), "%i", interval_g);
+                               params[i] = interval;
+                               break;
                        default:
                                assert (0);
                }
        }
 
-       return PQexecParams (db->conn, query->query, query->params_num, NULL,
-                       (const char *const *)((0 == query->params_num) ? NULL : params),
+       return PQexecParams (db->conn, udb_query_get_statement (q),
+                       data->params_num, NULL,
+                       (const char *const *) params,
                        NULL, NULL, /* return text data */ 0);
 } /* c_psql_exec_query_params */
 
-static PGresult *c_psql_exec_query_noparams (c_psql_database_t *db,
-               c_psql_query_t *query)
+static int c_psql_exec_query (c_psql_database_t *db, udb_query_t *q)
 {
-       return PQexec (db->conn, query->query);
-} /* c_psql_exec_query_noparams */
+       PGresult *res;
 
-static int c_psql_exec_query (c_psql_database_t *db, int idx)
-{
-       c_psql_query_t *query;
-       PGresult       *res;
+       c_psql_user_data_t *data;
 
-       int rows, cols;
-       int i;
+       const char *host;
 
-       if (idx >= db->queries_num)
-               return -1;
+       char **column_names;
+       char **column_values;
+       int    column_num;
 
-       query = db->queries[idx];
+       int rows_num;
+       int status;
+       int row, col;
 
+       /* The user data may hold parameter information, but may be NULL. */
+       data = udb_query_get_user_data (q);
+
+       /* Versions up to `3' don't know how to handle parameters. */
        if (3 <= db->proto_version)
-               res = c_psql_exec_query_params (db, query);
-       else if (0 == query->params_num)
-               res = c_psql_exec_query_noparams (db, query);
+               res = c_psql_exec_query_params (db, q, data);
+       else if ((NULL == data) || (0 == data->params_num))
+               res = c_psql_exec_query_noparams (db, q);
        else {
                log_err ("Connection to database \"%s\" does not support parameters "
                                "(protocol version %d) - cannot execute query \"%s\".",
-                               db->database, db->proto_version, query->name);
+                               db->database, db->proto_version,
+                               udb_query_get_name (q));
                return -1;
        }
 
+       column_names = NULL;
+       column_values = NULL;
+
+#define BAIL_OUT(status) \
+       sfree (column_names); \
+       sfree (column_values); \
+       PQclear (res); \
+       return status
+
        if (PGRES_TUPLES_OK != PQresultStatus (res)) {
                log_err ("Failed to execute SQL query: %s",
                                PQerrorMessage (db->conn));
-               log_info ("SQL query was: %s", query->query);
-               PQclear (res);
-               return -1;
+               log_info ("SQL query was: %s",
+                               udb_query_get_statement (q));
+               BAIL_OUT (-1);
        }
 
-       rows = PQntuples (res);
-       if (1 > rows) {
-               PQclear (res);
-               return 0;
+       rows_num = PQntuples (res);
+       if (1 > rows_num) {
+               BAIL_OUT (0);
        }
 
-       cols = PQnfields (res);
-       if (query->cols_num != cols) {
-               log_err ("SQL query returned wrong number of fields "
-                               "(expected: %i, got: %i)", query->cols_num, cols);
-               log_info ("SQL query was: %s", query->query);
-               PQclear (res);
-               return -1;
+       column_num = PQnfields (res);
+       column_names = (char **) calloc (column_num, sizeof (char *));
+       if (NULL == column_names) {
+               log_err ("calloc failed.");
+               BAIL_OUT (-1);
        }
 
-       for (i = 0; i < rows; ++i) {
-               int j;
+       column_values = (char **) calloc (column_num, sizeof (char *));
+       if (NULL == column_values) {
+               log_err ("calloc failed.");
+               BAIL_OUT (-1);
+       }
+       
+       for (col = 0; col < column_num; ++col) {
+               /* Pointers returned by `PQfname' are freed by `PQclear' via
+                * `BAIL_OUT'. */
+               column_names[col] = PQfname (res, col);
+               if (NULL == column_names[col]) {
+                       log_err ("Failed to resolv name of column %i.", col);
+                       BAIL_OUT (-1);
+               }
+       }
 
-               for (j = 0; j < cols; ++j) {
-                       c_psql_col_t col = query->cols[j];
+       if (C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
+                       || (0 == strcmp (db->host, "localhost")))
+               host = hostname_g;
+       else
+               host = db->host;
+
+       status = udb_query_prepare_result (q, host, "postgresql",
+                       db->database, column_names, (size_t) column_num);
+       if (0 != status) {
+               log_err ("udb_query_prepare_result failed with status %i.",
+                               status);
+               BAIL_OUT (-1);
+       }
+
+       for (row = 0; row < rows_num; ++row) {
+               for (col = 0; col < column_num; ++col) {
+                       /* Pointers returned by `PQgetvalue' are freed by `PQclear' via
+                        * `BAIL_OUT'. */
+                       column_values[col] = PQgetvalue (res, row, col);
+                       if (NULL == column_values[col]) {
+                               log_err ("Failed to get value at (row = %i, col = %i).",
+                                               row, col);
+                               break;
+                       }
+               }
 
-                       char *value = PQgetvalue (res, i, j);
+               /* check for an error */
+               if (col < column_num)
+                       continue;
 
-                       if (col.ds_type == DS_TYPE_COUNTER)
-                               submit_counter (db, col.type, col.type_instance, value);
-                       else if (col.ds_type == DS_TYPE_GAUGE)
-                               submit_gauge (db, col.type, col.type_instance, value);
+               status = udb_query_handle_result (q, column_values);
+               if (status != 0) {
+                       log_err ("udb_query_handle_result failed with status %i.",
+                                       status);
                }
-       }
-       PQclear (res);
-       return 0;
+       } /* for (row = 0; row < rows_num; ++row) */
+
+       BAIL_OUT (0);
+#undef BAIL_OUT
 } /* c_psql_exec_query */
 
 static int c_psql_read (void)
@@ -466,7 +407,17 @@ static int c_psql_read (void)
                        continue;
 
                for (j = 0; j < db->queries_num; ++j)
-                       c_psql_exec_query (db, j);
+               {
+                       udb_query_t *q;
+
+                       q = db->queries[j];
+
+                       if ((0 != db->server_version)
+                               && (udb_query_check_version (q, db->server_version) <= 0))
+                               continue;
+
+                       c_psql_exec_query (db, q);
+               }
 
                ++success;
        }
@@ -486,21 +437,16 @@ static int c_psql_shutdown (void)
        plugin_unregister_read ("postgresql");
        plugin_unregister_shutdown ("postgresql");
 
-       for (i = 0; i < databases_num; ++i) {
-               c_psql_database_t *db = databases + i;
-               c_psql_database_delete (db);
-       }
+       for (i = 0; i < databases_num; ++i)
+               c_psql_database_delete (databases + i);
 
        sfree (databases);
        databases_num = 0;
 
-       for (i = 0; i < queries_num; ++i) {
-               c_psql_query_t *query = queries + i;
-               c_psql_query_delete (query);
-       }
-
-       sfree (queries);
+       udb_query_free (queries, queries_num);
+       queries = NULL;
        queries_num = 0;
+
        return 0;
 } /* c_psql_shutdown */
 
@@ -511,33 +457,6 @@ static int c_psql_init (void)
        if ((NULL == databases) || (0 == databases_num))
                return 0;
 
-       for (i = 0; i < queries_num; ++i) {
-               c_psql_query_t *query = queries + i;
-               int j;
-
-               for (j = 0; j < query->cols_num; ++j) {
-                       c_psql_col_t     *col = query->cols + j;
-                       const data_set_t *ds;
-
-                       ds = plugin_get_ds (col->type);
-                       if (NULL == ds) {
-                               log_err ("Column: Unknown type \"%s\".", col->type);
-                               c_psql_shutdown ();
-                               return -1;
-                       }
-
-                       if (1 != ds->ds_num) {
-                               log_err ("Column: Invalid type \"%s\" - types defining "
-                                               "one data source are supported only (got: %i).",
-                                               col->type, ds->ds_num);
-                               c_psql_shutdown ();
-                               return -1;
-                       }
-
-                       col->ds_type = ds->ds[0].type;
-               }
-       }
-
        for (i = 0; i < databases_num; ++i) {
                c_psql_database_t *db = databases + i;
 
@@ -549,8 +468,6 @@ static int c_psql_init (void)
                char *server_host;
                int   server_version;
 
-               int j;
-
                /* this will happen during reinitialization */
                if (NULL != db->conn) {
                        c_psql_check_connection (db);
@@ -590,33 +507,6 @@ static int c_psql_init (void)
                if (3 > db->proto_version)
                        log_warn ("Protocol version %d does not support parameters.",
                                        db->proto_version);
-
-               /* Now that we know the PostgreSQL server version, we can get the
-                * right version of each query definition. */
-               for (j = 0; j < db->queries_num; ++j) {
-                       c_psql_query_t *tmp;
-
-                       tmp = c_psql_query_get (db->queries[j]->name, server_version);
-
-                       if (tmp == db->queries[j])
-                               continue;
-
-                       if (NULL == tmp) {
-                               log_err ("Query \"%s\" not found for server version %i - "
-                                               "please check your configuration.",
-                                               db->queries[j]->name, server_version);
-
-                               if (db->queries_num - j - 1 > 0)
-                                       memmove (db->queries + j, db->queries + j + 1,
-                                                       (db->queries_num - j - 1) * sizeof (*db->queries));
-
-                               --db->queries_num;
-                               --j;
-                               continue;
-                       }
-
-                       db->queries[j] = tmp;
-               }
        }
 
        plugin_register_read ("postgresql", c_psql_read);
@@ -637,185 +527,61 @@ static int config_set_s (char *name, char **var, const oconfig_item_t *ci)
        return 0;
 } /* config_set_s */
 
-static int config_set_i (char *name, int *var, const oconfig_item_t *ci)
+static int config_query_param_add (udb_query_t *q, oconfig_item_t *ci)
 {
-       if ((0 != ci->children_num) || (1 != ci->values_num)
-                       || (OCONFIG_TYPE_NUMBER != ci->values[0].type)) {
-               log_err ("%s expects a single number argument.", name);
-               return 1;
-       }
+       c_psql_user_data_t *data;
+       const char *param_str;
 
-       *var = (int)ci->values[0].value.number;
-       return 0;
-} /* config_set_i */
+       c_psql_param_t *tmp;
 
-static int config_set_param (c_psql_query_t *query, const oconfig_item_t *ci)
-{
-       c_psql_param_t param;
-       char          *param_str;
+       data = udb_query_get_user_data (q);
+       if (NULL == data) {
+               data = (c_psql_user_data_t *) smalloc (sizeof (*data));
+               if (NULL == data) {
+                       log_err ("Out of memory.");
+                       return -1;
+               }
+               memset (data, 0, sizeof (*data));
+               data->params = NULL;
+       }
 
-       if ((0 != ci->children_num) || (1 != ci->values_num)
-                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
-               log_err ("Param expects a single string argument.");
-               return 1;
+       tmp = (c_psql_param_t *) realloc (data->params,
+                       (data->params_num + 1) * sizeof (c_psql_param_t));
+       if (NULL == tmp) {
+               log_err ("Out of memory.");
+               return -1;
        }
+       data->params = tmp;
 
        param_str = ci->values[0].value.string;
        if (0 == strcasecmp (param_str, "hostname"))
-               param = C_PSQL_PARAM_HOST;
+               data->params[data->params_num] = C_PSQL_PARAM_HOST;
        else if (0 == strcasecmp (param_str, "database"))
-               param = C_PSQL_PARAM_DB;
+               data->params[data->params_num] = C_PSQL_PARAM_DB;
        else if (0 == strcasecmp (param_str, "username"))
-               param = C_PSQL_PARAM_USER;
+               data->params[data->params_num] = C_PSQL_PARAM_USER;
+       else if (0 == strcasecmp (param_str, "interval"))
+               data->params[data->params_num] = C_PSQL_PARAM_INTERVAL;
        else {
                log_err ("Invalid parameter \"%s\".", param_str);
                return 1;
        }
 
-       ++query->params_num;
-       if (NULL == (query->params = (c_psql_param_t *)realloc (query->params,
-                               query->params_num * sizeof (*query->params)))) {
-               log_err ("Out of memory.");
-               exit (5);
-       }
-
-       query->params[query->params_num - 1] = param;
-       return 0;
-} /* config_set_param */
-
-static int config_set_column (c_psql_query_t *query, const oconfig_item_t *ci)
-{
-       c_psql_col_t *col;
-
-       int i;
-
-       if ((0 != ci->children_num)
-                       || (1 > ci->values_num) || (2 < ci->values_num)) {
-               log_err ("Column expects either one or two arguments.");
-               return 1;
-       }
-
-       for (i = 0; i < ci->values_num; ++i) {
-               if (OCONFIG_TYPE_STRING != ci->values[i].type) {
-                       log_err ("Column expects either one or two string arguments.");
-                       return 1;
-               }
-       }
-
-       ++query->cols_num;
-       if (NULL == (query->cols = (c_psql_col_t *)realloc (query->cols,
-                               query->cols_num * sizeof (*query->cols)))) {
-               log_err ("Out of memory.");
-               exit (5);
-       }
-
-       col = query->cols + query->cols_num - 1;
-
-       col->ds_type = -1;
-
-       col->type = sstrdup (ci->values[0].value.string);
-       col->type_instance = (2 == ci->values_num)
-               ? sstrdup (ci->values[1].value.string) : NULL;
-       return 0;
-} /* config_set_column */
-
-static int set_query (c_psql_database_t *db, const char *name)
-{
-       c_psql_query_t *query;
-
-       query = c_psql_query_get (name, -1);
-       if (NULL == query) {
-               log_err ("Query \"%s\" not found - please check your configuration.",
-                               name);
-               return 1;
-       }
-
-       ++db->queries_num;
-       if (NULL == (db->queries = (c_psql_query_t **)realloc (db->queries,
-                               db->queries_num * sizeof (*db->queries)))) {
-               log_err ("Out of memory.");
-               exit (5);
-       }
-
-       if (query->params_num > db->max_params_num)
-               db->max_params_num = query->params_num;
+       data->params_num++;
+       udb_query_set_user_data (q, data);
 
-       db->queries[db->queries_num - 1] = query;
-       return 0;
-} /* set_query */
+       return (0);
+} /* config_query_param_add */
 
-static int config_set_query (c_psql_database_t *db, const oconfig_item_t *ci)
+static int config_query_callback (udb_query_t *q, oconfig_item_t *ci)
 {
-       if ((0 != ci->children_num) || (1 != ci->values_num)
-                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
-               log_err ("Query expects a single string argument.");
-               return 1;
-       }
-       return set_query (db, ci->values[0].value.string);
-} /* config_set_query */
+       if (0 == strcasecmp ("Param", ci->key))
+               return config_query_param_add (q, ci);
 
-static int c_psql_config_query (oconfig_item_t *ci)
-{
-       c_psql_query_t *query;
+       log_err ("Option not allowed within a Query block: `%s'", ci->key);
 
-       int i;
-
-       if ((1 != ci->values_num)
-                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
-               log_err ("<Query> expects a single string argument.");
-               return 1;
-       }
-
-       query = c_psql_query_new (ci->values[0].value.string);
-
-       for (i = 0; i < ci->children_num; ++i) {
-               oconfig_item_t *c = ci->children + i;
-
-               if (0 == strcasecmp (c->key, "Query"))
-                       config_set_s ("Query", &query->query, c);
-               else if (0 == strcasecmp (c->key, "Param"))
-                       config_set_param (query, c);
-               else if (0 == strcasecmp (c->key, "Column"))
-                       config_set_column (query, c);
-               else if (0 == strcasecmp (c->key, "MinPGVersion"))
-                       config_set_i ("MinPGVersion", &query->min_pg_version, c);
-               else if (0 == strcasecmp (c->key, "MaxPGVersion"))
-                       config_set_i ("MaxPGVersion", &query->max_pg_version, c);
-               else
-                       log_warn ("Ignoring unknown config key \"%s\".", c->key);
-       }
-
-       for (i = 0; i < queries_num - 1; ++i) {
-               c_psql_query_t *q = queries + i;
-
-               if ((0 == strcasecmp (q->name, query->name))
-                               && (q->min_pg_version <= query->max_pg_version)
-                               && (query->min_pg_version <= q->max_pg_version)) {
-                       log_err ("Ignoring redefinition (with overlapping version ranges) "
-                                       "of query \"%s\".", query->name);
-                       c_psql_query_delete (query);
-                       --queries_num;
-                       return 1;
-               }
-       }
-
-       if (query->min_pg_version > query->max_pg_version) {
-               log_err ("Query \"%s\": MinPGVersion > MaxPGVersion.",
-                               query->name);
-               c_psql_query_delete (query);
-               --queries_num;
-               return 1;
-       }
-
-       if (NULL == query->query) {
-               log_err ("Query \"%s\" does not include an SQL query string - "
-                               "please check your configuration.", query->name);
-               c_psql_query_delete (query);
-               --queries_num;
-               return 1;
-       }
-       return 0;
-} /* c_psql_config_query */
+       return (-1);
+} /* config_query_callback */
 
 static int c_psql_config_database (oconfig_item_t *ci)
 {
@@ -849,17 +615,29 @@ static int c_psql_config_database (oconfig_item_t *ci)
                else if (0 == strcasecmp (c->key, "Service"))
                        config_set_s ("Service", &db->service, c);
                else if (0 == strcasecmp (c->key, "Query"))
-                       config_set_query (db, c);
+                       udb_query_pick_from_list (c, queries, queries_num,
+                                       &db->queries, &db->queries_num);
                else
                        log_warn ("Ignoring unknown config key \"%s\".", c->key);
        }
 
-       if (NULL == db->queries) {
-               for (i = 0; i < def_queries_num; ++i)
-                       set_query (db, def_queries[i]);
+       /* If no `Query' options were given, add the default queries.. */
+       if (db->queries_num == 0)
+       {
+               for (i = 0; i < def_queries_num; i++)
+                       udb_query_pick_from_list_by_name (def_queries[i],
+                                       queries, queries_num,
+                                       &db->queries, &db->queries_num);
+       }
+
+       for (i = 0; (size_t)i < db->queries_num; ++i) {
+               c_psql_user_data_t *data;
+               data = udb_query_get_user_data (db->queries[i]);
+               if ((data != NULL) && (data->params_num > db->max_params_num))
+                       db->max_params_num = data->params_num;
        }
        return 0;
-}
+} /* c_psql_config_database */
 
 static int c_psql_config (oconfig_item_t *ci)
 {
@@ -887,7 +665,9 @@ static int c_psql_config (oconfig_item_t *ci)
                oconfig_item_t *c = ci->children + i;
 
                if (0 == strcasecmp (c->key, "Query"))
-                       c_psql_config_query (c);
+                       udb_query_create (&queries, &queries_num, c,
+                                       /* callback = */ config_query_callback,
+                                       /* legacy mode = */ 1);
                else if (0 == strcasecmp (c->key, "Database"))
                        c_psql_config_database (c);
                else
@@ -903,4 +683,3 @@ void module_register (void)
 } /* module_register */
 
 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
-
index 5b024ca..83a32c7 100644 (file)
 # Pre-defined queries of collectd's postgresql plugin.
+#
+# Do not edit this file. If you want to change any of the query definitions,
+# overwrite them in collectd.conf instead.
+#
+# This file is distributed under the same terms as collectd itself.
 
 <Query backends>
-       Query "SELECT count(*) \
+       Statement "SELECT count(*) AS count \
                FROM pg_stat_activity \
                WHERE datname = $1;"
 
        Param database
 
-       Column pg_numbackends
+       <Result>
+               Type "pg_numbackends"
+               ValuesFrom "count"
+       </Result>
 </Query>
 
 <Query transactions>
-       Query "SELECT xact_commit, xact_rollback \
+       Statement "SELECT xact_commit, xact_rollback \
                FROM pg_stat_database \
                WHERE datname = $1;"
 
        Param database
 
-       Column pg_xact commit
-       Column pg_xact rollback
+       <Result>
+               Type "pg_xact"
+               InstancePrefix "commit"
+               ValuesFrom "xact_commit"
+       </Result>
+       <Result>
+               Type "pg_xact"
+               InstancePrefix "rollback"
+               ValuesFrom "xact_rollback"
+       </Result>
 </Query>
 
 <Query queries>
-       Query "SELECT sum(n_tup_ins), sum(n_tup_upd), sum(n_tup_del) \
+       Statement "SELECT sum(n_tup_ins) AS ins, \
+                       sum(n_tup_upd) AS upd, \
+                       sum(n_tup_del) AS del \
                FROM pg_stat_user_tables;"
 
-       Column pg_n_tup_c ins
-       Column pg_n_tup_c upd
-       Column pg_n_tup_c del
-
-       MaxPGVersion 80299
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "ins"
+               ValuesFrom "ins"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "upd"
+               ValuesFrom "upd"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "del"
+               ValuesFrom "del"
+       </Result>
+
+       MaxVersion 80299
 </Query>
 
 <Query queries>
-       Query "SELECT sum(n_tup_ins), sum(n_tup_upd), sum(n_tup_del), \
-                       sum(n_tup_hot_upd) \
+       Statement "SELECT sum(n_tup_ins) AS ins, \
+                       sum(n_tup_upd) AS upd, \
+                       sum(n_tup_del) AS del, \
+                       sum(n_tup_hot_upd) AS hot_upd \
                FROM pg_stat_user_tables;"
 
-       Column pg_n_tup_c ins
-       Column pg_n_tup_c upd
-       Column pg_n_tup_c del
-       Column pg_n_tup_c hot_upd
-
-       MinPGVersion 80300
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "ins"
+               ValuesFrom "ins"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "upd"
+               ValuesFrom "upd"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "del"
+               ValuesFrom "del"
+       </Result>
+       <Result>
+               Type "pg_n_tup_c"
+               InstancePrefix "hot_upd"
+               ValuesFrom "hot_upd"
+       </Result>
+
+       MinVersion 80300
 </Query>
 
 <Query query_plans>
-       Query "SELECT sum(seq_scan), sum(seq_tup_read), \
-                       sum(idx_scan), sum(idx_tup_fetch) \
+       Statement "SELECT sum(seq_scan) AS seq, \
+                       sum(seq_tup_read) AS seq_tup_read, \
+                       sum(idx_scan) AS idx, \
+                       sum(idx_tup_fetch) AS idx_tup_fetch \
                FROM pg_stat_user_tables;"
 
-       Column pg_scan seq
-       Column pg_scan seq_tup_read
-       Column pg_scan idx
-       Column pg_scan idx_tup_fetch
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "seq"
+               ValuesFrom "seq"
+       </Result>
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "seq_tup_read"
+               ValuesFrom "seq_tup_read"
+       </Result>
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "idx"
+               ValuesFrom "idx"
+       </Result>
+       <Result>
+               Type "pg_scan"
+               InstancePrefix "idx_tup_fetch"
+               ValuesFrom "idx_tup_fetch"
+       </Result>
 </Query>
 
 <Query table_states>
-       Query "SELECT sum(n_live_tup), sum(n_dead_tup) \
+       Statement "SELECT sum(n_live_tup) AS live, sum(n_dead_tup) AS dead \
                FROM pg_stat_user_tables;"
 
-       Column pg_n_tup_g live
-       Column pg_n_tup_g dead
-
-       MinPGVersion 80300
+       <Result>
+               Type "pg_n_tup_g"
+               InstancePrefix "live"
+               ValuesFrom "live"
+       </Result>
+       <Result>
+               Type "pg_n_tup_g"
+               InstancePrefix "dead"
+               ValuesFrom "dead"
+       </Result>
+
+       MinVersion 80300
 </Query>
 
 <Query disk_io>
-       Query "SELECT coalesce(sum(heap_blks_read), 0), \
-                       coalesce(sum(heap_blks_hit), 0), \
-                       coalesce(sum(idx_blks_read), 0), \
-                       coalesce(sum(idx_blks_hit), 0), \
-                       coalesce(sum(toast_blks_read), 0), \
-                       coalesce(sum(toast_blks_hit), 0), \
-                       coalesce(sum(tidx_blks_read), 0), \
-                       coalesce(sum(tidx_blks_hit), 0) \
+       Statement "SELECT coalesce(sum(heap_blks_read), 0) AS heap_read, \
+                       coalesce(sum(heap_blks_hit), 0) AS heap_hit, \
+                       coalesce(sum(idx_blks_read), 0) AS idx_read, \
+                       coalesce(sum(idx_blks_hit), 0) AS idx_hit, \
+                       coalesce(sum(toast_blks_read), 0) AS toast_read, \
+                       coalesce(sum(toast_blks_hit), 0) AS toast_hit, \
+                       coalesce(sum(tidx_blks_read), 0) AS tidx_read, \
+                       coalesce(sum(tidx_blks_hit), 0) AS tidx_hit \
                FROM pg_statio_user_tables;"
 
-       Column pg_blks heap_read
-       Column pg_blks heap_hit
-       Column pg_blks idx_read
-       Column pg_blks idx_hit
-       Column pg_blks toast_read
-       Column pg_blks toast_hit
-       Column pg_blks tidx_read
-       Column pg_blks tidx_hit
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "heap_read"
+               ValuesFrom "heap_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "heap_hit"
+               ValuesFrom "heap_hit"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "idx_read"
+               ValuesFrom "idx_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "idx_hit"
+               ValuesFrom "idx_hit"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "toast_read"
+               ValuesFrom "toast_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "toast_hit"
+               ValuesFrom "toast_hit"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "tidx_read"
+               ValuesFrom "tidx_read"
+       </Result>
+       <Result>
+               Type "pg_blks"
+               InstancePrefix "tidx_hit"
+               ValuesFrom "tidx_hit"
+       </Result>
 </Query>
 
 <Query disk_usage>
-       Query "SELECT pg_database_size($1);"
+       Statement "SELECT pg_database_size($1) AS size;"
 
        Param database
 
-       Column pg_db_size
+       <Result>
+               Type pg_db_size
+               ValuesFrom "size"
+       </Result>
 </Query>
 
 # vim: set ft=config :
index 189c46f..164137b 100644 (file)
@@ -317,7 +317,6 @@ static void submit (const char *plugin_instance, /* {{{ */
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "powerdns", sizeof (vl.plugin));
   sstrncpy (vl.type, type, sizeof (vl.type));
index a76b57b..261accb 100644 (file)
@@ -23,6 +23,7 @@
  *   Lyonel Vincent <lyonel at ezix.org>
  *   Florian octo Forster <octo at verplant.org>
  *   Oleg King <king2 at kaluga.ru>
+ *   Sebastian Harl <sh at tokkee.org>
  **/
 
 #include "collectd.h"
 # include <regex.h>
 #endif
 
+#ifndef ARG_MAX
+#  define ARG_MAX 4096
+#endif
+
 #define BUFSIZE 256
 
 static const char *config_keys[] =
@@ -117,7 +122,9 @@ typedef struct procstat_entry_s
 
        unsigned long num_proc;
        unsigned long num_lwp;
+       unsigned long vmem_size;
        unsigned long vmem_rss;
+       unsigned long stack_size;
 
        unsigned long vmem_minflt;
        unsigned long vmem_majflt;
@@ -142,7 +149,9 @@ typedef struct procstat
 
        unsigned long num_proc;
        unsigned long num_lwp;
+       unsigned long vmem_size;
        unsigned long vmem_rss;
+       unsigned long stack_size;
 
        unsigned long vmem_minflt_counter;
        unsigned long vmem_majflt_counter;
@@ -314,13 +323,17 @@ static void ps_list_add (const char *name, const char *cmdline, procstat_entry_t
                }
 
                pse->age = 0;
-               pse->num_proc = entry->num_proc;
-               pse->num_lwp  = entry->num_lwp;
-               pse->vmem_rss = entry->vmem_rss;
-
-               ps->num_proc += pse->num_proc;
-               ps->num_lwp  += pse->num_lwp;
-               ps->vmem_rss += pse->vmem_rss;
+               pse->num_proc   = entry->num_proc;
+               pse->num_lwp    = entry->num_lwp;
+               pse->vmem_size  = entry->vmem_size;
+               pse->vmem_rss   = entry->vmem_rss;
+               pse->stack_size = entry->stack_size;
+
+               ps->num_proc   += pse->num_proc;
+               ps->num_lwp    += pse->num_lwp;
+               ps->vmem_size  += pse->vmem_size;
+               ps->vmem_rss   += pse->vmem_rss;
+               ps->stack_size += pse->stack_size;
 
                if ((entry->vmem_minflt_counter == 0)
                                && (entry->vmem_majflt_counter == 0))
@@ -409,7 +422,9 @@ static void ps_list_reset (void)
        {
                ps->num_proc    = 0;
                ps->num_lwp     = 0;
+               ps->vmem_size   = 0;
                ps->vmem_rss    = 0;
+               ps->stack_size  = 0;
 
                pse_prev = NULL;
                pse = ps->instances;
@@ -458,12 +473,18 @@ static int ps_config (const char *key, const char *value)
                int fields_num;
 
                new_val = strdup (value);
-               if (new_val == NULL)
+               if (new_val == NULL) {
+                       ERROR ("processes plugin: strdup failed when processing "
+                                       "`ProcessMatch %s'.", value);
                        return (1);
+               }
+
                fields_num = strsplit (new_val, fields,
                                STATIC_ARRAY_SIZE (fields));
                if (fields_num != 2)
                {
+                       ERROR ("processes plugin: `ProcessMatch' needs exactly "
+                                       "two string arguments.");
                        sfree (new_val);
                        return (1);
                }
@@ -472,6 +493,8 @@ static int ps_config (const char *key, const char *value)
        }
        else
        {
+               ERROR ("processes plugin: The `%s' configuration option is not "
+                               "understood and will be ignored.", key);
                return (-1);
        }
 
@@ -530,7 +553,6 @@ static void ps_submit_state (const char *state, double value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "processes", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
@@ -548,16 +570,25 @@ static void ps_submit_proc_list (procstat_t *ps)
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "processes", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, ps->name, sizeof (vl.plugin_instance));
 
+       sstrncpy (vl.type, "ps_vm", sizeof (vl.type));
+       vl.values[0].gauge = ps->vmem_size;
+       vl.values_len = 1;
+       plugin_dispatch_values (&vl);
+
        sstrncpy (vl.type, "ps_rss", sizeof (vl.type));
        vl.values[0].gauge = ps->vmem_rss;
        vl.values_len = 1;
        plugin_dispatch_values (&vl);
 
+       sstrncpy (vl.type, "ps_stacksize", sizeof (vl.type));
+       vl.values[0].gauge = ps->stack_size;
+       vl.values_len = 1;
+       plugin_dispatch_values (&vl);
+
        sstrncpy (vl.type, "ps_cputime", sizeof (vl.type));
        vl.values[0].counter = ps->cpu_user_counter;
        vl.values[1].counter = ps->cpu_system_counter;
@@ -663,7 +694,9 @@ int ps_read_process (int pid, procstat_t *ps, char *state)
 
        long long unsigned cpu_user_counter;
        long long unsigned cpu_system_counter;
+       long long unsigned vmem_size;
        long long unsigned vmem_rss;
+       long long unsigned stack_size;
 
        memset (ps, 0, sizeof (procstat_t));
 
@@ -730,10 +763,20 @@ int ps_read_process (int pid, procstat_t *ps, char *state)
 
        cpu_user_counter   = atoll (fields[13]);
        cpu_system_counter = atoll (fields[14]);
-       vmem_rss = atoll (fields[23]);
+       vmem_size          = atoll (fields[22]);
+       vmem_rss           = atoll (fields[23]);
        ps->vmem_minflt_counter = atol (fields[9]);
        ps->vmem_majflt_counter = atol (fields[11]);
-       
+
+       {
+               unsigned long long stack_start = atoll (fields[27]);
+               unsigned long long stack_ptr   = atoll (fields[28]);
+
+               stack_size = (stack_start > stack_ptr)
+                       ? stack_start - stack_ptr
+                       : stack_ptr - stack_start;
+       }
+
        /* Convert jiffies to useconds */
        cpu_user_counter   = cpu_user_counter   * 1000000 / CONFIG_HZ;
        cpu_system_counter = cpu_system_counter * 1000000 / CONFIG_HZ;
@@ -741,11 +784,103 @@ int ps_read_process (int pid, procstat_t *ps, char *state)
 
        ps->cpu_user_counter = (unsigned long) cpu_user_counter;
        ps->cpu_system_counter = (unsigned long) cpu_system_counter;
+       ps->vmem_size = (unsigned long) vmem_size;
        ps->vmem_rss = (unsigned long) vmem_rss;
+       ps->stack_size = (unsigned long) stack_size;
 
        /* success */
        return (0);
 } /* int ps_read_process (...) */
+
+static char *ps_get_cmdline (pid_t pid, char *name, char *buf, size_t buf_len)
+{
+       char  *buf_ptr;
+       size_t len;
+
+       char file[PATH_MAX];
+       int  fd;
+
+       size_t n;
+
+       if ((pid < 1) || (NULL == buf) || (buf_len < 2))
+               return NULL;
+
+       ssnprintf (file, sizeof (file), "/proc/%u/cmdline", pid);
+
+       fd = open (file, O_RDONLY);
+       if (fd < 0) {
+               char errbuf[4096];
+               WARNING ("processes plugin: Failed to open `%s': %s.", file,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return NULL;
+       }
+
+       buf_ptr = buf;
+       len     = buf_len;
+
+       n = 0;
+
+       while (42) {
+               ssize_t status;
+
+               status = read (fd, (void *)buf_ptr, len);
+
+               if (status < 0) {
+                       char errbuf[4096];
+
+                       if ((EAGAIN == errno) || (EINTR == errno))
+                               continue;
+
+                       WARNING ("processes plugin: Failed to read from `%s': %s.", file,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       close (fd);
+                       return NULL;
+               }
+
+               n += status;
+
+               if (status == 0)
+                       break;
+
+               buf_ptr += status;
+               len     -= status;
+
+               if (len <= 0)
+                       break;
+       }
+
+       close (fd);
+
+       if (0 == n) {
+               /* cmdline not available; e.g. kernel thread, zombie */
+               if (NULL == name)
+                       return NULL;
+
+               ssnprintf (buf, buf_len, "[%s]", name);
+               return buf;
+       }
+
+       assert (n <= buf_len);
+
+       if (n == buf_len)
+               --n;
+       buf[n] = '\0';
+
+       --n;
+       /* remove trailing whitespace */
+       while ((n > 0) && (isspace (buf[n]) || ('\0' == buf[n]))) {
+               buf[n] = '\0';
+               --n;
+       }
+
+       /* arguments are separated by '\0' in /proc/<pid>/cmdline */
+       while (n > 0) {
+               if ('\0' == buf[n])
+                       buf[n] = ' ';
+               --n;
+       }
+       return buf;
+} /* char *ps_get_cmdline (...) */
 #endif /* KERNEL_LINUX */
 
 #if HAVE_THREAD_INFO
@@ -1061,6 +1196,8 @@ static int ps_read (void)
        DIR           *proc;
        int            pid;
 
+       char cmdline[ARG_MAX];
+
        int        status;
        procstat_t ps;
        procstat_entry_t pse;
@@ -1097,9 +1234,11 @@ static int ps_read (void)
                pse.id       = pid;
                pse.age      = 0;
 
-               pse.num_proc = ps.num_proc;
-               pse.num_lwp  = ps.num_lwp;
-               pse.vmem_rss = ps.vmem_rss;
+               pse.num_proc   = ps.num_proc;
+               pse.num_lwp    = ps.num_lwp;
+               pse.vmem_size  = ps.vmem_size;
+               pse.vmem_rss   = ps.vmem_rss;
+               pse.stack_size = ps.stack_size;
 
                pse.vmem_minflt = 0;
                pse.vmem_minflt_counter = ps.vmem_minflt_counter;
@@ -1121,8 +1260,9 @@ static int ps_read (void)
                        case 'W': paging++;   break;
                }
 
-               /* FIXME: cmdline should be here instead of NULL */
-               ps_list_add (ps.name, NULL, &pse);
+               ps_list_add (ps.name,
+                               ps_get_cmdline (pid, ps.name, cmdline, sizeof (cmdline)),
+                               &pse);
        }
 
        closedir (proc);
@@ -1217,9 +1357,9 @@ static int ps_read (void)
                pse.num_proc = 1;
                pse.num_lwp  = procs[i].ki_numthreads;
 
-               /* pse.vmem_size = procs[i].ki_size; */
+               pse.vmem_size = procs[i].ki_size;
                pse.vmem_rss = procs[i].ki_rssize * getpagesize();
-               /* pse.stack_size = procs[i].ki_ssize * getpagesize(); */
+               pse.stack_size = procs[i].ki_ssize * getpagesize();
                pse.vmem_minflt = 0;
                pse.vmem_minflt_counter = procs[i].ki_rusage.ru_minflt;
                pse.vmem_majflt = 0;
diff --git a/src/rrdcached.c b/src/rrdcached.c
new file mode 100644 (file)
index 0000000..31c6352
--- /dev/null
@@ -0,0 +1,414 @@
+/**
+ * collectd - src/rrdcached.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+#include "utils_rrdcreate.h"
+
+#include <rrd_client.h>
+
+/*
+ * Private variables
+ */
+static const char *config_keys[] =
+{
+  "DaemonAddress",
+  "DataDir",
+  "CreateFiles",
+  "CollectStatistics"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static char *datadir = NULL;
+static char *daemon_address = NULL;
+static int config_create_files = 1;
+static int config_collect_stats = 1;
+static rrdcreate_config_t rrdcreate_config =
+{
+       /* stepsize = */ 0,
+       /* heartbeat = */ 0,
+       /* rrarows = */ 1200,
+       /* xff = */ 0.1,
+
+       /* timespans = */ NULL,
+       /* timespans_num = */ 0,
+
+       /* consolidation_functions = */ NULL,
+       /* consolidation_functions_num = */ 0
+};
+
+static int value_list_to_string (char *buffer, int buffer_len,
+    const data_set_t *ds, const value_list_t *vl)
+{
+  int offset;
+  int status;
+  int i;
+
+  assert (0 == strcmp (ds->type, vl->type));
+
+  memset (buffer, '\0', buffer_len);
+
+  status = ssnprintf (buffer, buffer_len, "%u", (unsigned int) vl->time);
+  if ((status < 1) || (status >= buffer_len))
+    return (-1);
+  offset = status;
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if ((ds->ds[i].type != DS_TYPE_COUNTER)
+        && (ds->ds[i].type != DS_TYPE_GAUGE))
+      return (-1);
+
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+    {
+      status = ssnprintf (buffer + offset, buffer_len - offset,
+          ":%llu", vl->values[i].counter);
+    }
+    else /* if (ds->ds[i].type == DS_TYPE_GAUGE) */
+    {
+      status = ssnprintf (buffer + offset, buffer_len - offset,
+          ":%lf", vl->values[i].gauge);
+    }
+
+    if ((status < 1) || (status >= (buffer_len - offset)))
+      return (-1);
+
+    offset += status;
+  } /* for ds->ds_num */
+
+  return (0);
+} /* int value_list_to_string */
+
+static int value_list_to_filename (char *buffer, int buffer_len,
+    const data_set_t *ds, const value_list_t *vl)
+{
+  int offset = 0;
+  int status;
+
+  assert (0 == strcmp (ds->type, vl->type));
+
+  if (datadir != NULL)
+  {
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s/", datadir);
+    if ((status < 1) || (status >= buffer_len - offset))
+      return (-1);
+    offset += status;
+  }
+
+  status = ssnprintf (buffer + offset, buffer_len - offset,
+      "%s/", vl->host);
+  if ((status < 1) || (status >= buffer_len - offset))
+    return (-1);
+  offset += status;
+
+  if (strlen (vl->plugin_instance) > 0)
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s-%s/", vl->plugin, vl->plugin_instance);
+  else
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s/", vl->plugin);
+  if ((status < 1) || (status >= buffer_len - offset))
+    return (-1);
+  offset += status;
+
+  if (strlen (vl->type_instance) > 0)
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s-%s", vl->type, vl->type_instance);
+  else
+    status = ssnprintf (buffer + offset, buffer_len - offset,
+        "%s", vl->type);
+  if ((status < 1) || (status >= buffer_len - offset))
+    return (-1);
+  offset += status;
+
+  strncpy (buffer + offset, ".rrd", buffer_len - offset);
+  buffer[buffer_len - 1] = 0;
+
+  return (0);
+} /* int value_list_to_filename */
+
+static int rc_config (const char *key, const char *value)
+{
+  if (strcasecmp ("DataDir", key) == 0)
+  {
+    if (datadir != NULL)
+      free (datadir);
+    datadir = strdup (value);
+    if (datadir != NULL)
+    {
+      int len = strlen (datadir);
+      while ((len > 0) && (datadir[len - 1] == '/'))
+      {
+        len--;
+        datadir[len] = '\0';
+      }
+      if (len <= 0)
+      {
+        free (datadir);
+        datadir = NULL;
+      }
+    }
+  }
+  else if (strcasecmp ("DaemonAddress", key) == 0)
+  {
+    sfree (daemon_address);
+    daemon_address = strdup (value);
+    if (daemon_address == NULL)
+    {
+      ERROR ("rrdcached plugin: strdup failed.");
+      return (1);
+    }
+  }
+  else if (strcasecmp ("CreateFiles", key) == 0)
+  {
+    if ((strcasecmp ("false", value) == 0)
+        || (strcasecmp ("no", value) == 0)
+        || (strcasecmp ("off", value) == 0))
+      config_create_files = 0;
+    else
+      config_create_files = 1;
+  }
+  else if (strcasecmp ("CollectStatistics", key) == 0)
+  {
+    if ((strcasecmp ("false", value) == 0)
+        || (strcasecmp ("no", value) == 0)
+        || (strcasecmp ("off", value) == 0))
+      config_collect_stats = 0;
+    else
+      config_collect_stats = 1;
+  }
+  else
+  {
+    return (-1);
+  }
+  return (0);
+} /* int rc_config */
+
+static int rc_read (void)
+{
+  int status;
+  rrdc_stats_t *head;
+  rrdc_stats_t *ptr;
+
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  if (daemon_address == NULL)
+    return (-1);
+
+  if (config_collect_stats == 0)
+    return (-1);
+
+  vl.values = values;
+  vl.values_len = 1;
+
+  if ((strncmp ("unix:", daemon_address, strlen ("unix:")) == 0)
+      || (daemon_address[0] == '/'))
+    sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+  else
+    sstrncpy (vl.host, daemon_address, sizeof (vl.host));
+  sstrncpy (vl.plugin, "rrdcached", sizeof (vl.plugin));
+
+  head = NULL;
+  status = rrdc_stats_get (&head);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_stats_get failed with status %i.", status);
+    return (-1);
+  }
+
+  for (ptr = head; ptr != NULL; ptr = ptr->next)
+  {
+    if (ptr->type == RRDC_STATS_TYPE_GAUGE)
+      values[0].gauge = (gauge_t) ptr->value.gauge;
+    else if (ptr->type == RRDC_STATS_TYPE_COUNTER)
+      values[0].counter = (counter_t) ptr->value.counter;
+    else
+      continue;
+
+    if (strcasecmp ("QueueLength", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "queue_length", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("UpdatesWritten", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "write-updates", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("DataSetsWritten", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "write-data_sets",
+          sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("TreeNodesNumber", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "gauge", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "tree_nodes", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("TreeDepth", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "gauge", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "tree_depth", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("FlushesReceived", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "receive-flush", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("JournalBytes", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "counter", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "journal-bytes", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("JournalRotate", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "counter", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "journal-rotates", sizeof (vl.type_instance));
+    }
+    else if (strcasecmp ("UpdatesReceived", ptr->name) == 0)
+    {
+      sstrncpy (vl.type, "operations", sizeof (vl.type));
+      sstrncpy (vl.type_instance, "receive-update", sizeof (vl.type_instance));
+    }
+    else
+    {
+      DEBUG ("rrdcached plugin: rc_read: Unknown statistic `%s'.", ptr->name);
+      continue;
+    }
+
+    plugin_dispatch_values (&vl);
+  } /* for (ptr = head; ptr != NULL; ptr = ptr->next) */
+
+  rrdc_stats_free (head);
+
+  return (0);
+} /* int rc_read */
+
+static int rc_init (void)
+{
+  if (config_collect_stats != 0)
+    plugin_register_read ("rrdcached", rc_read);
+
+  return (0);
+} /* int rc_init */
+
+static int rc_write (const data_set_t *ds, const value_list_t *vl)
+{
+  char filename[512];
+  char values[512];
+  char *values_array[2];
+  int status;
+
+  if (daemon_address == NULL)
+  {
+    ERROR ("rrdcached plugin: daemon_address == NULL.");
+    plugin_unregister_write ("rrdcached");
+    return (-1);
+  }
+
+  if (strcmp (ds->type, vl->type) != 0)
+  {
+    ERROR ("rrdcached plugin: DS type does not match value list type");
+    return (-1);
+  }
+
+  if (value_list_to_filename (filename, sizeof (filename), ds, vl) != 0)
+  {
+    ERROR ("rrdcached plugin: value_list_to_filename failed.");
+    return (-1);
+  }
+
+  if (value_list_to_string (values, sizeof (values), ds, vl) != 0)
+  {
+    ERROR ("rrdcached plugin: value_list_to_string failed.");
+    return (-1);
+  }
+
+  values_array[0] = values;
+  values_array[1] = NULL;
+
+  if (config_create_files != 0)
+  {
+    struct stat statbuf;
+
+    status = stat (filename, &statbuf);
+    if (status != 0)
+    {
+      if (errno != ENOENT)
+      {
+        char errbuf[1024];
+        ERROR ("rrdcached plugin: stat (%s) failed: %s",
+            filename, sstrerror (errno, errbuf, sizeof (errbuf)));
+        return (-1);
+      }
+
+      status = cu_rrd_create_file (filename, ds, vl, &rrdcreate_config);
+      if (status != 0)
+      {
+        ERROR ("rrdcached plugin: cu_rrd_create_file (%s) failed.",
+            filename);
+        return (-1);
+      }
+    }
+  }
+
+  status = rrdc_connect (daemon_address);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_connect (%s) failed with status %i.",
+        daemon_address, status);
+    return (-1);
+  }
+
+  status = rrdc_update (filename, /* values_num = */ 1, (void *) values_array);
+  if (status != 0)
+  {
+    ERROR ("rrdcached plugin: rrdc_update (%s, [%s], 1) failed with "
+        "status %i.",
+        filename, values_array[0], status);
+    return (-1);
+  }
+
+  return (0);
+} /* int rc_write */
+
+static int rc_shutdown (void)
+{
+  rrdc_disconnect ();
+  return (0);
+} /* int rc_shutdown */
+
+void module_register (void)
+{
+  plugin_register_config ("rrdcached", rc_config,
+      config_keys, config_keys_num);
+  plugin_register_init ("rrdcached", rc_init);
+  plugin_register_write ("rrdcached", rc_write);
+  plugin_register_shutdown ("rrdcached", rc_shutdown);
+} /* void module_register */
+
+/*
+ * vim: set sw=2 sts=2 et :
+ */
index 986c8b4..bbebc37 100644 (file)
@@ -23,6 +23,7 @@
 #include "plugin.h"
 #include "common.h"
 #include "utils_avltree.h"
+#include "utils_rrdcreate.h"
 
 #include <rrd.h>
 
@@ -65,27 +66,6 @@ typedef struct rrd_queue_s rrd_queue_t;
 /*
  * Private variables
  */
-static int rra_timespans[] =
-{
-       3600,
-       86400,
-       604800,
-       2678400,
-       31622400
-};
-static int rra_timespans_num = STATIC_ARRAY_SIZE (rra_timespans);
-
-static int *rra_timespans_custom = NULL;
-static int rra_timespans_custom_num = 0;
-
-static char *rra_types[] =
-{
-       "AVERAGE",
-       "MIN",
-       "MAX"
-};
-static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
-
 static const char *config_keys[] =
 {
        "CacheTimeout",
@@ -103,12 +83,21 @@ static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 /* If datadir is zero, the daemon's basedir is used. If stepsize or heartbeat
  * is zero a default, depending on the `interval' member of the value list is
  * being used. */
-static char   *datadir   = NULL;
-static int     stepsize  = 0;
-static int     heartbeat = 0;
-static int     rrarows   = 1200;
-static double  xff       = 0.1;
-static double  write_rate = 0.0;
+static char *datadir   = NULL;
+static double write_rate = 0.0;
+static rrdcreate_config_t rrdcreate_config =
+{
+       /* stepsize = */ 0,
+       /* heartbeat = */ 0,
+       /* rrarows = */ 1200,
+       /* xff = */ 0.1,
+
+       /* timespans = */ NULL,
+       /* timespans_num = */ 0,
+
+       /* consolidation_functions = */ NULL,
+       /* consolidation_functions_num = */ 0
+};
 
 /* XXX: If you need to lock both, cache_lock and queue_lock, at the same time,
  * ALWAYS lock `cache_lock' first! */
@@ -132,233 +121,7 @@ static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
 
 static int do_shutdown = 0;
 
-/* * * * * * * * * *
- * WARNING:  Magic *
- * * * * * * * * * */
-
-static void rra_free (int rra_num, char **rra_def)
-{
-       int i;
-
-       for (i = 0; i < rra_num; i++)
-       {
-               sfree (rra_def[i]);
-       }
-       sfree (rra_def);
-} /* void rra_free */
-
-static int rra_get (char ***ret, const value_list_t *vl)
-{
-       char **rra_def;
-       int rra_num;
-
-       int *rts;
-       int  rts_num;
-
-       int rra_max;
-
-       int span;
-
-       int cdp_num;
-       int cdp_len;
-       int i, j;
-
-       char buffer[64];
-
-       /* The stepsize we use here: If it is user-set, use it. If not, use the
-        * interval of the value-list. */
-       int ss;
-
-       if (rrarows <= 0)
-       {
-               *ret = NULL;
-               return (-1);
-       }
-
-       ss = (stepsize > 0) ? stepsize : vl->interval;
-       if (ss <= 0)
-       {
-               *ret = NULL;
-               return (-1);
-       }
-
-       /* Use the configured timespans or fall back to the built-in defaults */
-       if (rra_timespans_custom_num != 0)
-       {
-               rts = rra_timespans_custom;
-               rts_num = rra_timespans_custom_num;
-       }
-       else
-       {
-               rts = rra_timespans;
-               rts_num = rra_timespans_num;
-       }
-
-       rra_max = rts_num * rra_types_num;
-
-       if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
-               return (-1);
-       memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
-       rra_num = 0;
-
-       cdp_len = 0;
-       for (i = 0; i < rts_num; i++)
-       {
-               span = rts[i];
-
-               if ((span / ss) < rrarows)
-                       span = ss * rrarows;
-
-               if (cdp_len == 0)
-                       cdp_len = 1;
-               else
-                       cdp_len = (int) floor (((double) span)
-                                       / ((double) (rrarows * ss)));
-
-               cdp_num = (int) ceil (((double) span)
-                               / ((double) (cdp_len * ss)));
-
-               for (j = 0; j < rra_types_num; j++)
-               {
-                       if (rra_num >= rra_max)
-                               break;
-
-                       if (ssnprintf (buffer, sizeof (buffer), "RRA:%s:%3.1f:%u:%u",
-                                               rra_types[j], xff,
-                                               cdp_len, cdp_num) >= sizeof (buffer))
-                       {
-                               ERROR ("rra_get: Buffer would have been truncated.");
-                               continue;
-                       }
-
-                       rra_def[rra_num++] = sstrdup (buffer);
-               }
-       }
-
-#if COLLECT_DEBUG
-       DEBUG ("rra_num = %i", rra_num);
-       for (i = 0; i < rra_num; i++)
-               DEBUG ("  %s", rra_def[i]);
-#endif
-
-       *ret = rra_def;
-       return (rra_num);
-} /* int rra_get */
-
-static void ds_free (int ds_num, char **ds_def)
-{
-       int i;
-
-       for (i = 0; i < ds_num; i++)
-               if (ds_def[i] != NULL)
-                       free (ds_def[i]);
-       free (ds_def);
-}
-
-static int ds_get (char ***ret, const data_set_t *ds, const value_list_t *vl)
-{
-       char **ds_def;
-       int ds_num;
-
-       char min[32];
-       char max[32];
-       char buffer[128];
-
-       DEBUG ("ds->ds_num = %i", ds->ds_num);
-
-       ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
-       if (ds_def == NULL)
-       {
-               char errbuf[1024];
-               ERROR ("rrdtool plugin: malloc failed: %s",
-                               sstrerror (errno, errbuf, sizeof (errbuf)));
-               return (-1);
-       }
-       memset (ds_def, '\0', ds->ds_num * sizeof (char *));
-
-       for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
-       {
-               data_source_t *d = ds->ds + ds_num;
-               char *type;
-               int status;
-
-               ds_def[ds_num] = NULL;
-
-               if (d->type == DS_TYPE_COUNTER)
-                       type = "COUNTER";
-               else if (d->type == DS_TYPE_GAUGE)
-                       type = "GAUGE";
-               else
-               {
-                       ERROR ("rrdtool plugin: Unknown DS type: %i",
-                                       d->type);
-                       break;
-               }
-
-               if (isnan (d->min))
-               {
-                       sstrncpy (min, "U", sizeof (min));
-               }
-               else
-                       ssnprintf (min, sizeof (min), "%lf", d->min);
-
-               if (isnan (d->max))
-               {
-                       sstrncpy (max, "U", sizeof (max));
-               }
-               else
-                       ssnprintf (max, sizeof (max), "%lf", d->max);
-
-               status = ssnprintf (buffer, sizeof (buffer),
-                               "DS:%s:%s:%i:%s:%s",
-                               d->name, type,
-                               (heartbeat > 0) ? heartbeat : (2 * vl->interval),
-                               min, max);
-               if ((status < 1) || (status >= sizeof (buffer)))
-                       break;
-
-               ds_def[ds_num] = sstrdup (buffer);
-       } /* for ds_num = 0 .. ds->ds_num */
-
-#if COLLECT_DEBUG
-{
-       int i;
-       DEBUG ("ds_num = %i", ds_num);
-       for (i = 0; i < ds_num; i++)
-               DEBUG ("  %s", ds_def[i]);
-}
-#endif
-
-       if (ds_num != ds->ds_num)
-       {
-               ds_free (ds_num, ds_def);
-               return (-1);
-       }
-
-       *ret = ds_def;
-       return (ds_num);
-}
-
 #if HAVE_THREADSAFE_LIBRRD
-static int srrd_create (char *filename, unsigned long pdp_step, time_t last_up,
-               int argc, const char **argv)
-{
-       int status;
-
-       optind = 0; /* bug in librrd? */
-       rrd_clear_error ();
-
-       status = rrd_create_r (filename, pdp_step, last_up, argc, (void *) argv);
-
-       if (status != 0)
-       {
-               WARNING ("rrdtool plugin: rrd_create_r (%s) failed: %s",
-                               filename, rrd_get_error ());
-       }
-
-       return (status);
-} /* int srrd_create */
-
 static int srrd_update (char *filename, char *template,
                int argc, const char **argv)
 {
@@ -380,59 +143,6 @@ static int srrd_update (char *filename, char *template,
 /* #endif HAVE_THREADSAFE_LIBRRD */
 
 #else /* !HAVE_THREADSAFE_LIBRRD */
-static int srrd_create (char *filename, unsigned long pdp_step, time_t last_up,
-               int argc, const char **argv)
-{
-       int status;
-
-       int new_argc;
-       char **new_argv;
-
-       char pdp_step_str[16];
-       char last_up_str[16];
-
-       new_argc = 6 + argc;
-       new_argv = (char **) malloc ((new_argc + 1) * sizeof (char *));
-       if (new_argv == NULL)
-       {
-               ERROR ("rrdtool plugin: malloc failed.");
-               return (-1);
-       }
-
-       if (last_up == 0)
-               last_up = time (NULL) - 10;
-
-       ssnprintf (pdp_step_str, sizeof (pdp_step_str), "%lu", pdp_step);
-       ssnprintf (last_up_str, sizeof (last_up_str), "%u", (unsigned int) last_up);
-
-       new_argv[0] = "create";
-       new_argv[1] = filename;
-       new_argv[2] = "-s";
-       new_argv[3] = pdp_step_str;
-       new_argv[4] = "-b";
-       new_argv[5] = last_up_str;
-
-       memcpy (new_argv + 6, argv, argc * sizeof (char *));
-       new_argv[new_argc] = NULL;
-       
-       pthread_mutex_lock (&librrd_lock);
-       optind = 0; /* bug in librrd? */
-       rrd_clear_error ();
-
-       status = rrd_create (new_argc, new_argv);
-       pthread_mutex_unlock (&librrd_lock);
-
-       if (status != 0)
-       {
-               WARNING ("rrdtool plugin: rrd_create (%s) failed: %s",
-                               filename, rrd_get_error ());
-       }
-
-       sfree (new_argv);
-
-       return (status);
-} /* int srrd_create */
-
 static int srrd_update (char *filename, char *template,
                int argc, const char **argv)
 {
@@ -476,58 +186,6 @@ static int srrd_update (char *filename, char *template,
 } /* int srrd_update */
 #endif /* !HAVE_THREADSAFE_LIBRRD */
 
-static int rrd_create_file (char *filename, const data_set_t *ds, const value_list_t *vl)
-{
-       char **argv;
-       int argc;
-       char **rra_def;
-       int rra_num;
-       char **ds_def;
-       int ds_num;
-       int status = 0;
-
-       if (check_create_dir (filename))
-               return (-1);
-
-       if ((rra_num = rra_get (&rra_def, vl)) < 1)
-       {
-               ERROR ("rrd_create_file failed: Could not calculate RRAs");
-               return (-1);
-       }
-
-       if ((ds_num = ds_get (&ds_def, ds, vl)) < 1)
-       {
-               ERROR ("rrd_create_file failed: Could not calculate DSes");
-               return (-1);
-       }
-
-       argc = ds_num + rra_num;
-
-       if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
-       {
-               char errbuf[1024];
-               ERROR ("rrd_create failed: %s",
-                               sstrerror (errno, errbuf, sizeof (errbuf)));
-               return (-1);
-       }
-
-       memcpy (argv, ds_def, ds_num * sizeof (char *));
-       memcpy (argv + ds_num, rra_def, rra_num * sizeof (char *));
-       argv[ds_num + rra_num] = NULL;
-
-       assert (vl->time > 10);
-       status = srrd_create (filename,
-                       (stepsize > 0) ? stepsize : vl->interval,
-                       vl->time - 10,
-                       argc, (const char **)argv);
-
-       free (argv);
-       ds_free (ds_num, ds_def);
-       rra_free (rra_num, rra_def);
-
-       return (status);
-}
-
 static int value_list_to_string (char *buffer, int buffer_len,
                const data_set_t *ds, const value_list_t *vl)
 {
@@ -565,7 +223,7 @@ static int value_list_to_string (char *buffer, int buffer_len,
 } /* int value_list_to_string */
 
 static int value_list_to_filename (char *buffer, int buffer_len,
-               const data_set_t *ds, const value_list_t *vl)
+               const data_set_t __attribute__((unused)) *ds, const value_list_t *vl)
 {
        int offset = 0;
        int status;
@@ -608,7 +266,7 @@ static int value_list_to_filename (char *buffer, int buffer_len,
        return (0);
 } /* int value_list_to_filename */
 
-static void *rrd_queue_thread (void *data)
+static void *rrd_queue_thread (void __attribute__((unused)) *data)
 {
         struct timeval tv_next_update;
         struct timeval tv_now;
@@ -653,10 +311,9 @@ static void *rrd_queue_thread (void *data)
                     break;
 
                   gettimeofday (&tv_now, /* timezone = */ NULL);
-                  status = timeval_sub_timespec (&tv_next_update, &tv_now,
-                      &ts_wait);
+                  status = timeval_cmp (tv_next_update, tv_now, NULL);
                   /* We're good to go */
-                  if (status != 0)
+                  if (status <= 0)
                     break;
 
                   /* We're supposed to wait a bit with this update, so we'll
@@ -1134,7 +791,9 @@ static int rrd_write (const data_set_t *ds, const value_list_t *vl)
        {
                if (errno == ENOENT)
                {
-                       if (rrd_create_file (filename, ds, vl))
+                       status = cu_rrd_create_file (filename,
+                                       ds, vl, &rrdcreate_config);
+                       if (status != 0)
                                return (-1);
                }
                else
@@ -1223,15 +882,15 @@ static int rrd_config (const char *key, const char *value)
        }
        else if (strcasecmp ("StepSize", key) == 0)
        {
-               stepsize = atoi (value);
-               if (stepsize < 0)
-                       stepsize = 0;
+               int temp = atoi (value);
+               if (temp > 0)
+                       rrdcreate_config.stepsize = temp;
        }
        else if (strcasecmp ("HeartBeat", key) == 0)
        {
-               heartbeat = atoi (value);
-               if (heartbeat < 0)
-                       heartbeat = 0;
+               int temp = atoi (value);
+               if (temp > 0)
+                       rrdcreate_config.heartbeat = temp;
        }
        else if (strcasecmp ("RRARows", key) == 0)
        {
@@ -1244,7 +903,7 @@ static int rrd_config (const char *key, const char *value)
                                        "be greater than 0.\n");
                        return (1);
                }
-               rrarows = tmp;
+               rrdcreate_config.rrarows = tmp;
        }
        else if (strcasecmp ("RRATimespan", key) == 0)
        {
@@ -1263,8 +922,8 @@ static int rrd_config (const char *key, const char *value)
                {
                        dummy = NULL;
                        
-                       tmp_alloc = realloc (rra_timespans_custom,
-                                       sizeof (int) * (rra_timespans_custom_num + 1));
+                       tmp_alloc = realloc (rrdcreate_config.timespans,
+                                       sizeof (int) * (rrdcreate_config.timespans_num + 1));
                        if (tmp_alloc == NULL)
                        {
                                fprintf (stderr, "rrdtool: realloc failed.\n");
@@ -1272,15 +931,15 @@ static int rrd_config (const char *key, const char *value)
                                free (value_copy);
                                return (1);
                        }
-                       rra_timespans_custom = tmp_alloc;
-                       rra_timespans_custom[rra_timespans_custom_num] = atoi (ptr);
-                       if (rra_timespans_custom[rra_timespans_custom_num] != 0)
-                               rra_timespans_custom_num++;
+                       rrdcreate_config.timespans = tmp_alloc;
+                       rrdcreate_config.timespans[rrdcreate_config.timespans_num] = atoi (ptr);
+                       if (rrdcreate_config.timespans[rrdcreate_config.timespans_num] != 0)
+                               rrdcreate_config.timespans_num++;
                } /* while (strtok_r) */
 
-               qsort (/* base = */ rra_timespans_custom,
-                               /* nmemb  = */ rra_timespans_custom_num,
-                               /* size   = */ sizeof (rra_timespans_custom[0]),
+               qsort (/* base = */ rrdcreate_config.timespans,
+                               /* nmemb  = */ rrdcreate_config.timespans_num,
+                               /* size   = */ sizeof (rrdcreate_config.timespans[0]),
                                /* compar = */ rrd_compare_numeric);
 
                free (value_copy);
@@ -1296,7 +955,7 @@ static int rrd_config (const char *key, const char *value)
                                        "be in the range 0 to 1 (exclusive).");
                        return (1);
                }
-               xff = tmp;
+               rrdcreate_config.xff = tmp;
        }
        else if (strcasecmp ("WritesPerSecond", key) == 0)
        {
@@ -1350,16 +1009,18 @@ static int rrd_init (void)
 {
        int status;
 
-       if (stepsize < 0)
-               stepsize = 0;
-       if (heartbeat <= 0)
-               heartbeat = 2 * stepsize;
+       if (rrdcreate_config.stepsize < 0)
+               rrdcreate_config.stepsize = 0;
+       if (rrdcreate_config.heartbeat <= 0)
+               rrdcreate_config.heartbeat = 2 * rrdcreate_config.stepsize;
 
-       if ((heartbeat > 0) && (heartbeat < interval_g))
+       if ((rrdcreate_config.heartbeat > 0)
+                       && (rrdcreate_config.heartbeat < interval_g))
                WARNING ("rrdtool plugin: Your `heartbeat' is "
                                "smaller than your `interval'. This will "
                                "likely cause problems.");
-       else if ((stepsize > 0) && (stepsize < interval_g))
+       else if ((rrdcreate_config.stepsize > 0)
+                       && (rrdcreate_config.stepsize < interval_g))
                WARNING ("rrdtool plugin: Your `stepsize' is "
                                "smaller than your `interval'. This will "
                                "create needlessly big RRD-files.");
@@ -1395,7 +1056,10 @@ static int rrd_init (void)
        DEBUG ("rrdtool plugin: rrd_init: datadir = %s; stepsize = %i;"
                        " heartbeat = %i; rrarows = %i; xff = %lf;",
                        (datadir == NULL) ? "(null)" : datadir,
-                       stepsize, heartbeat, rrarows, xff);
+                       rrdcreate_config.stepsize,
+                       rrdcreate_config.heartbeat,
+                       rrdcreate_config.rrarows,
+                       rrdcreate_config.xff);
 
        return (0);
 } /* int rrd_init */
index a92f33c..fa461a2 100644 (file)
@@ -497,7 +497,6 @@ static void sensors_submit (const char *plugin_instance,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
 
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "sensors", sizeof (vl.plugin));
index 1c874e5..cfa26bb 100644 (file)
@@ -40,7 +40,6 @@ static void serial_submit (const char *type_instance,
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "serial", sizeof (vl.plugin));
        sstrncpy (vl.type, "serial_octets", sizeof (vl.type));
index 995f39d..352075f 100644 (file)
@@ -22,6 +22,7 @@
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
+#include "utils_complain.h"
 
 #include <pthread.h>
 
@@ -67,6 +68,7 @@ struct host_definition_s
   char *community;
   int version;
   void *sess_handle;
+  c_complain_t complaint;
   uint32_t interval;
   time_t next_update;
   data_definition_t **data_list;
@@ -116,6 +118,7 @@ static pthread_cond_t  host_cond = PTHREAD_COND_INITIALIZER;
 /*
  * Private functions
  */
+/* Many functions to handle the configuration. {{{ */
 /* First there are many functions which do configuration stuff. It's a big
  * bloated and messy, I'm afraid. */
 
@@ -527,9 +530,9 @@ static int csnmp_config_add_host_interval (host_definition_t *hd, oconfig_item_t
     return (-1);
   }
 
-  hd->interval = (int) ci->values[0].value.number;
-  if (hd->interval < 0)
-    hd->interval = 0;
+  hd->interval = ci->values[0].value.number >= 0
+    ? (uint32_t) ci->values[0].value.number
+    : 0;
 
   return (0);
 } /* int csnmp_config_add_host_interval */
@@ -551,6 +554,7 @@ static int csnmp_config_add_host (oconfig_item_t *ci)
     return (-1);
   memset (hd, '\0', sizeof (host_definition_t));
   hd->version = 2;
+  C_COMPLAIN_INIT (&hd->complaint);
 
   hd->name = strdup (ci->values[0].value.string);
   if (hd->name == NULL)
@@ -653,7 +657,7 @@ static int csnmp_config (oconfig_item_t *ci)
   return (0);
 } /* int csnmp_config */
 
-/* End of the config stuff. Now the interesting part begins */
+/* }}} End of the config stuff. Now the interesting part begins */
 
 static void csnmp_host_close_session (host_definition_t *host)
 {
@@ -960,7 +964,6 @@ static int csnmp_dispatch_table (host_definition_t *host, data_definition_t *dat
   sstrncpy (vl.plugin, "snmp", sizeof (vl.plugin));
 
   vl.interval = host->interval;
-  vl.time = time (NULL);
 
   subid = 0;
   have_more = 1;
@@ -1136,7 +1139,7 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
       break;
     }
 
-    for (i = 0; i < oid_list_len; i++)
+    for (i = 0; (uint32_t) i < oid_list_len; i++)
       snmp_add_null_var (req, oid_list[i].oid, oid_list[i].oid_len);
 
     res = NULL;
@@ -1147,7 +1150,9 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
       char *errstr = NULL;
 
       snmp_sess_error (host->sess_handle, NULL, NULL, &errstr);
-      ERROR ("snmp plugin: host %s: snmp_sess_synch_response failed: %s",
+
+      c_complain (LOG_ERR, &host->complaint,
+         "snmp plugin: host %s: snmp_sess_synch_response failed: %s",
          host->name, (errstr == NULL) ? "Unknown problem" : errstr);
 
       if (res != NULL)
@@ -1162,6 +1167,9 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
     }
     status = 0;
     assert (res != NULL);
+    c_release (LOG_INFO, &host->complaint,
+       "snmp plugin: host %s: snmp_sess_synch_response successful.",
+       host->name);
 
     vb = res->variables;
     if (vb == NULL)
@@ -1377,7 +1385,6 @@ static int csnmp_read_value (host_definition_t *host, data_definition_t *data)
     return (-1);
   }
 
-  vl.time = time (NULL);
 
   for (vb = res->variables; vb != NULL; vb = vb->next_variable)
   {
@@ -1435,7 +1442,7 @@ static int csnmp_read_host (host_definition_t *host)
   time_end = time (NULL);
   DEBUG ("snmp plugin: csnmp_read_host (%s) finished at %u;", host->name,
       (unsigned int) time_end);
-  if ((time_end - time_start) > host->interval)
+  if ((uint32_t) (time_end - time_start) > host->interval)
   {
     WARNING ("snmp plugin: Host `%s' should be queried every %i seconds, "
        "but reading all values takes %u seconds.",
@@ -1445,7 +1452,7 @@ static int csnmp_read_host (host_definition_t *host)
   return (0);
 } /* int csnmp_read_host */
 
-static void *csnmp_read_thread (void *data)
+static void *csnmp_read_thread (void __attribute__((unused)) *data)
 {
   host_definition_t *host;
 
@@ -1498,7 +1505,7 @@ static int csnmp_init (void)
     {
       host->interval = interval_g;
     }
-    else if (host->interval < interval_g)
+    else if (host->interval < (uint32_t) interval_g)
     {
       host->interval = interval_g;
       WARNING ("snmp plugin: Data for host `%s' will be collected every %i seconds.",
@@ -1627,5 +1634,5 @@ void module_register (void)
 } /* void module_register */
 
 /*
- * vim: shiftwidth=2 softtabstop=2 tabstop=8
+ * vim: shiftwidth=2 softtabstop=2 tabstop=8 fdm=marker
  */
index 11c9c57..1779f86 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/swap.c
- * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2005-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
@@ -35,6 +35,9 @@
 #if HAVE_SYS_SYSCTL_H
 #  include <sys/sysctl.h>
 #endif
+#if HAVE_SYS_DKSTAT_H
+#  include <sys/dkstat.h>
+#endif
 #if HAVE_KVM_H
 #  include <kvm.h>
 #endif
@@ -55,6 +58,10 @@ static unsigned long long pagesize;
 static kstat_t *ksp;
 /* #endif HAVE_LIBKSTAT */
 
+#elif HAVE_SWAPCTL
+/* No global variables */
+/* #endif HAVE_SWAPCTL */
+
 #elif defined(VM_SWAPUSAGE)
 /* No global variables */
 /* #endif defined(VM_SWAPUSAGE) */
@@ -85,6 +92,10 @@ static int swap_init (void)
                ksp = NULL;
 /* #endif HAVE_LIBKSTAT */
 
+#elif HAVE_SWAPCTL
+       /* No init stuff */
+/* #endif HAVE_SWAPCTL */
+
 #elif defined(VM_SWAPUSAGE)
        /* No init stuff */
 /* #endif defined(VM_SWAPUSAGE) */
@@ -126,7 +137,6 @@ static void swap_submit (const char *type_instance, double value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "swap", sizeof (vl.plugin));
        sstrncpy (vl.type, "swap", sizeof (vl.type));
@@ -243,6 +253,84 @@ static int swap_read (void)
        swap_submit ("reserved", swap_resv);
 /* #endif HAVE_LIBKSTAT */
 
+#elif HAVE_SWAPCTL
+       struct swapent *swap_entries;
+       int swap_num;
+       int status;
+       int i;
+
+       uint64_t used  = 0;
+       uint64_t total = 0;
+
+       /*
+        * XXX: This is the syntax for the *BSD `swapctl', which has the
+        * following prototype:
+        *   swapctl (int cmd, void *arg, int misc);
+        *
+        * HP-UX and Solaris (and possibly other UNIXes) provide `swapctl',
+        * too, but with the following prototype:
+        *   swapctl (int cmd, void *arg);
+        *
+        * Solaris is usually handled in the KSTAT case above. For other UNIXes
+        * a separate case for the other version of `swapctl' may be necessary.
+        */
+       swap_num = swapctl (SWAP_NSWAP, NULL, 0);
+       if (swap_num < 0)
+       {
+               ERROR ("swap plugin: swapctl (SWAP_NSWAP) failed with status %i.",
+                               swap_num);
+               return (-1);
+       }
+       else if (swap_num == 0)
+               return (0);
+
+       swap_entries = calloc (swap_num, sizeof (*swap_entries));
+       if (swap_entries == NULL)
+       {
+               ERROR ("swap plugin: calloc failed.");
+               return (-1);
+       }
+
+       status = swapctl (SWAP_STATS, swap_entries, swap_num);
+       if (status != swap_num)
+       {
+               ERROR ("swap plugin: swapctl (SWAP_STATS) failed with status %i.",
+                               status);
+               sfree (swap_entries);
+               return (-1);
+       }
+
+#if defined(DEV_BSIZE) && (DEV_BSIZE > 0)
+# define C_SWAP_BLOCK_SIZE ((uint64_t) DEV_BSIZE)
+#else
+# define C_SWAP_BLOCK_SIZE ((uint64_t) 512)
+#endif
+
+       for (i = 0; i < swap_num; i++)
+       {
+               if ((swap_entries[i].se_flags & SWF_ENABLE) == 0)
+                       continue;
+
+               used  += ((uint64_t) swap_entries[i].se_inuse)
+                       * C_SWAP_BLOCK_SIZE;
+               total += ((uint64_t) swap_entries[i].se_nblks)
+                       * C_SWAP_BLOCK_SIZE;
+       }
+
+       if (total < used)
+       {
+               ERROR ("swap plugin: Total swap space (%"PRIu64") "
+                               "is less than used swap space (%"PRIu64").",
+                               total, used);
+               return (-1);
+       }
+
+       swap_submit ("used", (gauge_t) used);
+       swap_submit ("free", (gauge_t) (total - used));
+
+       sfree (swap_entries);
+/* #endif HAVE_SWAPCTL */
+
 #elif defined(VM_SWAPUSAGE)
        int              mib[3];
        size_t           mib_len;
index 01bf629..02afd79 100644 (file)
@@ -305,7 +305,7 @@ static int ctail_init (void)
 static int ctail_read (void)
 {
   int success = 0;
-  int i;
+  size_t i;
 
   for (i = 0; i < tail_match_list_num; i++)
   {
@@ -314,7 +314,7 @@ static int ctail_read (void)
     status = tail_match_read (tail_match_list[i]);
     if (status != 0)
     {
-      ERROR ("tail plugin: tail_match_read[%i] failed.", i);
+      ERROR ("tail plugin: tail_match_read[%zu] failed.", i);
     }
     else
     {
@@ -329,7 +329,7 @@ static int ctail_read (void)
 
 static int ctail_shutdown (void)
 {
-  int i;
+  size_t i;
 
   for (i = 0; i < tail_match_list_num; i++)
   {
index caca537..32b4296 100644 (file)
@@ -68,7 +68,6 @@ static void tape_submit (const char *plugin_instance,
 
        vl.values = values;
        vl.values_len = 2;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "tape", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance,
diff --git a/src/target_notification.c b/src/target_notification.c
new file mode 100644 (file)
index 0000000..96598af
--- /dev/null
@@ -0,0 +1,285 @@
+/**
+ * collectd - src/target_notification.c
+ * Copyright (C) 2008  Florian 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 Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "filter_chain.h"
+#include "utils_cache.h"
+#include "utils_subst.h"
+
+struct tn_data_s
+{
+  int severity;
+  char *message;
+};
+typedef struct tn_data_s tn_data_t;
+
+static int tn_config_add_severity (tn_data_t *data, /* {{{ */
+    const oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Target `notification': The `%s' option requires exactly one string "
+        "argument.", ci->key);
+    return (-1);
+  }
+
+  if ((strcasecmp ("FAILURE", ci->values[0].value.string) == 0)
+      || (strcasecmp ("CRITICAL", ci->values[0].value.string) == 0))
+    data->severity = NOTIF_FAILURE;
+  else if ((strcasecmp ("WARNING", ci->values[0].value.string) == 0)
+      || (strcasecmp ("WARN", ci->values[0].value.string) == 0))
+    data->severity = NOTIF_WARNING;
+  else if (strcasecmp ("OKAY", ci->values[0].value.string) == 0)
+    data->severity = NOTIF_OKAY;
+  else
+  {
+    WARNING ("Target `notification': Unknown severity `%s'. "
+        "Will use `FAILURE' instead.",
+        ci->values[0].value.string);
+    data->severity = NOTIF_FAILURE;
+  }
+
+  return (0);
+} /* }}} int tn_config_add_severity */
+
+static int tn_config_add_string (char **dest, /* {{{ */
+    const oconfig_item_t *ci)
+{
+  char *temp;
+
+  if (dest == NULL)
+    return (-EINVAL);
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Target `notification': The `%s' option requires exactly one string "
+        "argument.", ci->key);
+    return (-1);
+  }
+
+  if (ci->values[0].value.string[0] == 0)
+  {
+    ERROR ("Target `notification': The `%s' option does not accept empty strings.",
+        ci->key);
+    return (-1);
+  }
+
+  temp = sstrdup (ci->values[0].value.string);
+  if (temp == NULL)
+  {
+    ERROR ("tn_config_add_string: sstrdup failed.");
+    return (-1);
+  }
+
+  free (*dest);
+  *dest = temp;
+
+  return (0);
+} /* }}} int tn_config_add_string */
+
+static int tn_destroy (void **user_data) /* {{{ */
+{
+  tn_data_t *data;
+
+  if (user_data == NULL)
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+    return (0);
+
+  sfree (data->message);
+  sfree (data);
+
+  return (0);
+} /* }}} int tn_destroy */
+
+static int tn_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  tn_data_t *data;
+  int status;
+  int i;
+
+  data = (tn_data_t *) malloc (sizeof (*data));
+  if (data == NULL)
+  {
+    ERROR ("tn_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (data, 0, sizeof (*data));
+
+  data->message = NULL;
+  data->severity = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Message", child->key) == 0)
+      status = tn_config_add_string (&data->message, child);
+    else if (strcasecmp ("Severity", child->key) == 0)
+      status = tn_config_add_severity (data, child);
+    else
+    {
+      ERROR ("Target `notification': The `%s' configuration option is not understood "
+          "and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((data->severity != NOTIF_FAILURE)
+        && (data->severity != NOTIF_WARNING)
+        && (data->severity != NOTIF_OKAY))
+    {
+      DEBUG ("Target `notification': Setting "
+          "the default severity `WARNING'.");
+      data->severity = NOTIF_WARNING;
+    }
+
+    if (data->message == NULL)
+    {
+      ERROR ("Target `notification': No `Message' option has been specified. "
+          "Without it, the `Notification' target is useless.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    tn_destroy ((void *) data);
+    return (status);
+  }
+
+  *user_data = data;
+  return (0);
+} /* }}} int tn_create */
+
+static int tn_invoke (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  tn_data_t *data;
+  notification_t n;
+  char temp[NOTIF_MAX_MSG_LEN];
+
+  gauge_t *rates;
+  int rates_failed;
+
+  int i;
+
+  if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+  {
+    ERROR ("Target `notification': Invoke: `data' is NULL.");
+    return (-EINVAL);
+  }
+
+  /* Initialize the structure. */
+  memset (&n, 0, sizeof (n));
+  n.severity = data->severity;
+  n.time = time (NULL);
+  sstrncpy (n.message, data->message, sizeof (n.message));
+  sstrncpy (n.host, vl->host, sizeof (n.host));
+  sstrncpy (n.plugin, vl->plugin, sizeof (n.plugin));
+  sstrncpy (n.plugin_instance, vl->plugin_instance,
+      sizeof (n.plugin_instance));
+  sstrncpy (n.type, vl->type, sizeof (n.type));
+  sstrncpy (n.type_instance, vl->type_instance,
+      sizeof (n.type_instance));
+  n.meta = NULL;
+
+#define REPLACE_FIELD(t,v) \
+  if (subst_string (temp, sizeof (temp), n.message, t, v) != NULL) \
+    sstrncpy (n.message, temp, sizeof (n.message));
+  REPLACE_FIELD ("%{host}", n.host);
+  REPLACE_FIELD ("%{plugin}", n.plugin);
+  REPLACE_FIELD ("%{plugin_instance}", n.plugin_instance);
+  REPLACE_FIELD ("%{type}", n.type);
+  REPLACE_FIELD ("%{type_instance}", n.type_instance);
+
+  rates_failed = 0;
+  rates = NULL;
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    char template[DATA_MAX_NAME_LEN];
+    char value_str[DATA_MAX_NAME_LEN];
+
+    ssnprintf (template, sizeof (template), "%%{ds:%s}", ds->ds[i].name);
+
+    if (ds->ds[i].type != DS_TYPE_GAUGE)
+    {
+      if ((rates == NULL) && (rates_failed == 0))
+      {
+        rates = uc_get_rate (ds, vl);
+        if (rates == NULL)
+          rates_failed = 1;
+      }
+    }
+
+    /* If this is a gauge value, use the current value. */
+    if (ds->ds[i].type == DS_TYPE_GAUGE)
+      ssnprintf (value_str, sizeof (value_str),
+          "%g", (double) vl->values[i].gauge);
+    /* If it's a counter, try to use the current rate. This may fail, if the
+     * value has been renamed. */
+    else if (rates != NULL)
+      ssnprintf (value_str, sizeof (value_str),
+          "%g", (double) rates[i]);
+    /* Since we don't know any better, use the string `unknown'. */
+    else
+      sstrncpy (value_str, "unknown", sizeof (value_str));
+
+    REPLACE_FIELD (template, value_str);
+  }
+  sfree (rates);
+
+  plugin_dispatch_notification (&n);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int tn_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = tn_create;
+       tproc.destroy = tn_destroy;
+       tproc.invoke  = tn_invoke;
+       fc_register_target ("notification", tproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
diff --git a/src/target_replace.c b/src/target_replace.c
new file mode 100644 (file)
index 0000000..9a9affb
--- /dev/null
@@ -0,0 +1,356 @@
+/**
+ * collectd - src/target_replace.c
+ * Copyright (C) 2008  Florian 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 Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "filter_chain.h"
+#include "utils_subst.h"
+
+#include <regex.h>
+
+struct tr_action_s;
+typedef struct tr_action_s tr_action_t;
+struct tr_action_s
+{
+  regex_t re;
+  char *replacement;
+  int may_be_empty;
+
+  tr_action_t *next;
+};
+
+struct tr_data_s
+{
+  tr_action_t *host;
+  tr_action_t *plugin;
+  tr_action_t *plugin_instance;
+  /* tr_action_t *type; */
+  tr_action_t *type_instance;
+};
+typedef struct tr_data_s tr_data_t;
+
+static char *tr_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *tr_strdup */
+
+static void tr_action_destroy (tr_action_t *act) /* {{{ */
+{
+  if (act == NULL)
+    return;
+
+  regfree (&act->re);
+  sfree (act->replacement);
+
+  if (act->next != NULL)
+    tr_action_destroy (act->next);
+
+  sfree (act);
+} /* }}} void tr_action_destroy */
+
+static int tr_config_add_action (tr_action_t **dest, /* {{{ */
+    const oconfig_item_t *ci, int may_be_empty)
+{
+  tr_action_t *act;
+  int status;
+
+  if (dest == NULL)
+    return (-EINVAL);
+
+  if ((ci->values_num != 2)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING)
+      || (ci->values[1].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Target `replace': The `%s' option requires exactly two string "
+        "arguments.", ci->key);
+    return (-1);
+  }
+
+  act = (tr_action_t *) malloc (sizeof (*act));
+  if (act == NULL)
+  {
+    ERROR ("tr_config_add_action: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (act, 0, sizeof (*act));
+
+  act->replacement = NULL;
+  act->may_be_empty = may_be_empty;
+
+  status = regcomp (&act->re, ci->values[0].value.string, REG_EXTENDED);
+  if (status != 0)
+  {
+    char errbuf[1024] = "";
+
+    /* regerror assures null termination. */
+    regerror (status, &act->re, errbuf, sizeof (errbuf));
+    ERROR ("Target `replace': Compiling the regular expression `%s' "
+        "failed: %s.",
+        ci->values[0].value.string, errbuf);
+    sfree (act);
+    return (-EINVAL);
+  }
+
+  act->replacement = tr_strdup (ci->values[1].value.string);
+  if (act->replacement == NULL)
+  {
+    ERROR ("tr_config_add_action: tr_strdup failed.");
+    regfree (&act->re);
+    sfree (act);
+    return (-ENOMEM);
+  }
+
+  /* Insert action at end of list. */
+  if (*dest == NULL)
+    *dest = act;
+  else
+  {
+    tr_action_t *prev;
+
+    prev = *dest;
+    while (prev->next != NULL)
+      prev = prev->next;
+
+    prev->next = act;
+  }
+
+  return (0);
+} /* }}} int tr_config_add_action */
+
+static int tr_action_invoke (tr_action_t *act_head, /* {{{ */
+    char *buffer_in, size_t buffer_in_size, int may_be_empty)
+{
+  tr_action_t *act;
+  int status;
+  char buffer[DATA_MAX_NAME_LEN];
+  regmatch_t matches[8];
+
+  if (act_head == NULL)
+    return (-EINVAL);
+
+  sstrncpy (buffer, buffer_in, sizeof (buffer));
+  memset (matches, 0, sizeof (matches));
+
+  DEBUG ("target_replace plugin: tr_action_invoke: <- buffer = %s;", buffer);
+
+  for (act = act_head; act != NULL; act = act->next)
+  {
+    char temp[DATA_MAX_NAME_LEN];
+    char *subst_status;
+
+    status = regexec (&act->re, buffer,
+        STATIC_ARRAY_SIZE (matches), matches,
+        /* flags = */ 0);
+    if (status == REG_NOMATCH)
+      continue;
+    else if (status != 0)
+    {
+      char errbuf[1024] = "";
+
+      regerror (status, &act->re, errbuf, sizeof (errbuf));
+      ERROR ("Target `replace': Executing a regular expression failed: %s.",
+          errbuf);
+      continue;
+    }
+
+    subst_status = subst (temp, sizeof (temp), buffer,
+        matches[0].rm_so, matches[0].rm_eo, act->replacement);
+    if (subst_status == NULL)
+    {
+      ERROR ("Target `replace': subst (buffer = %s, start = %zu, end = %zu, "
+          "replacement = %s) failed.",
+          buffer, (size_t) matches[0].rm_so, (size_t) matches[0].rm_eo,
+          act->replacement);
+      continue;
+    }
+    sstrncpy (buffer, temp, sizeof (buffer));
+
+    DEBUG ("target_replace plugin: tr_action_invoke: -- buffer = %s;", buffer);
+  } /* for (act = act_head; act != NULL; act = act->next) */
+
+  if ((may_be_empty == 0) && (buffer[0] == 0))
+  {
+    WARNING ("Target `replace': Replacement resulted in an empty string, "
+        "which is not allowed for this buffer (`host' or `plugin').");
+    return (0);
+  }
+
+  DEBUG ("target_replace plugin: tr_action_invoke: -> buffer = %s;", buffer);
+  sstrncpy (buffer_in, buffer, buffer_in_size);
+
+  return (0);
+} /* }}} int tr_action_invoke */
+
+static int tr_destroy (void **user_data) /* {{{ */
+{
+  tr_data_t *data;
+
+  if (user_data == NULL)
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+    return (0);
+
+  tr_action_destroy (data->host);
+  tr_action_destroy (data->plugin);
+  tr_action_destroy (data->plugin_instance);
+  /* tr_action_destroy (data->type); */
+  tr_action_destroy (data->type_instance);
+  sfree (data);
+
+  return (0);
+} /* }}} int tr_destroy */
+
+static int tr_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  tr_data_t *data;
+  int status;
+  int i;
+
+  data = (tr_data_t *) malloc (sizeof (*data));
+  if (data == NULL)
+  {
+    ERROR ("tr_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (data, 0, sizeof (*data));
+
+  data->host = NULL;
+  data->plugin = NULL;
+  data->plugin_instance = NULL;
+  /* data->type = NULL; */
+  data->type_instance = NULL;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if ((strcasecmp ("Host", child->key) == 0)
+        || (strcasecmp ("Hostname", child->key) == 0))
+      status = tr_config_add_action (&data->host, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("Plugin", child->key) == 0)
+      status = tr_config_add_action (&data->plugin, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("PluginInstance", child->key) == 0)
+      status = tr_config_add_action (&data->plugin_instance, child,
+          /* may be empty = */ 1);
+#if 0
+    else if (strcasecmp ("Type", child->key) == 0)
+      status = tr_config_add_action (&data->type, child,
+          /* may be empty = */ 0);
+#endif
+    else if (strcasecmp ("TypeInstance", child->key) == 0)
+      status = tr_config_add_action (&data->type_instance, child,
+          /* may be empty = */ 1);
+    else
+    {
+      ERROR ("Target `replace': The `%s' configuration option is not understood "
+          "and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((data->host == NULL)
+        && (data->plugin == NULL)
+        && (data->plugin_instance == NULL)
+        /* && (data->type == NULL) */
+        && (data->type_instance == NULL))
+    {
+      ERROR ("Target `replace': You need to set at lease one of `Host', "
+          "`Plugin', `PluginInstance', `Type', or `TypeInstance'.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    tr_destroy ((void *) &data);
+    return (status);
+  }
+
+  *user_data = data;
+  return (0);
+} /* }}} int tr_create */
+
+static int tr_invoke (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  tr_data_t *data;
+
+  if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+  {
+    ERROR ("Target `replace': Invoke: `data' is NULL.");
+    return (-EINVAL);
+  }
+
+#define HANDLE_FIELD(f,e) \
+  if (data->f != NULL) \
+    tr_action_invoke (data->f, vl->f, sizeof (vl->f), e)
+  HANDLE_FIELD (host, 0);
+  HANDLE_FIELD (plugin, 0);
+  HANDLE_FIELD (plugin_instance, 1);
+  /* HANDLE_FIELD (type); */
+  HANDLE_FIELD (type_instance, 1);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int tr_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = tr_create;
+       tproc.destroy = tr_destroy;
+       tproc.invoke  = tr_invoke;
+       fc_register_target ("replace", tproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
diff --git a/src/target_set.c b/src/target_set.c
new file mode 100644 (file)
index 0000000..8a014c3
--- /dev/null
@@ -0,0 +1,229 @@
+/**
+ * collectd - src/target_set.c
+ * Copyright (C) 2008  Florian 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 Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "filter_chain.h"
+
+struct ts_data_s
+{
+  char *host;
+  char *plugin;
+  char *plugin_instance;
+  /* char *type; */
+  char *type_instance;
+};
+typedef struct ts_data_s ts_data_t;
+
+static char *ts_strdup (const char *orig) /* {{{ */
+{
+  size_t sz;
+  char *dest;
+
+  if (orig == NULL)
+    return (NULL);
+
+  sz = strlen (orig) + 1;
+  dest = (char *) malloc (sz);
+  if (dest == NULL)
+    return (NULL);
+
+  memcpy (dest, orig, sz);
+
+  return (dest);
+} /* }}} char *ts_strdup */
+
+static int ts_config_add_string (char **dest, /* {{{ */
+    const oconfig_item_t *ci, int may_be_empty)
+{
+  char *temp;
+
+  if (dest == NULL)
+    return (-EINVAL);
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("Target `set': The `%s' option requires exactly one string "
+        "argument.", ci->key);
+    return (-1);
+  }
+
+  if ((!may_be_empty) && (ci->values[0].value.string[0] == 0))
+  {
+    ERROR ("Target `set': The `%s' option does not accept empty strings.",
+        ci->key);
+    return (-1);
+  }
+
+  temp = ts_strdup (ci->values[0].value.string);
+  if (temp == NULL)
+  {
+    ERROR ("ts_config_add_string: ts_strdup failed.");
+    return (-1);
+  }
+
+  free (*dest);
+  *dest = temp;
+
+  return (0);
+} /* }}} int ts_config_add_string */
+
+static int ts_destroy (void **user_data) /* {{{ */
+{
+  ts_data_t *data;
+
+  if (user_data == NULL)
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+    return (0);
+
+  free (data->host);
+  free (data->plugin);
+  free (data->plugin_instance);
+  /* free (data->type); */
+  free (data->type_instance);
+  free (data);
+
+  return (0);
+} /* }}} int ts_destroy */
+
+static int ts_create (const oconfig_item_t *ci, void **user_data) /* {{{ */
+{
+  ts_data_t *data;
+  int status;
+  int i;
+
+  data = (ts_data_t *) malloc (sizeof (*data));
+  if (data == NULL)
+  {
+    ERROR ("ts_create: malloc failed.");
+    return (-ENOMEM);
+  }
+  memset (data, 0, sizeof (*data));
+
+  data->host = NULL;
+  data->plugin = NULL;
+  data->plugin_instance = NULL;
+  /* data->type = NULL; */
+  data->type_instance = NULL;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if ((strcasecmp ("Host", child->key) == 0)
+        || (strcasecmp ("Hostname", child->key) == 0))
+      status = ts_config_add_string (&data->host, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("Plugin", child->key) == 0)
+      status = ts_config_add_string (&data->plugin, child,
+          /* may be empty = */ 0);
+    else if (strcasecmp ("PluginInstance", child->key) == 0)
+      status = ts_config_add_string (&data->plugin_instance, child,
+          /* may be empty = */ 1);
+#if 0
+    else if (strcasecmp ("Type", child->key) == 0)
+      status = ts_config_add_string (&data->type, child,
+          /* may be empty = */ 0);
+#endif
+    else if (strcasecmp ("TypeInstance", child->key) == 0)
+      status = ts_config_add_string (&data->type_instance, child,
+          /* may be empty = */ 1);
+    else
+    {
+      ERROR ("Target `set': The `%s' configuration option is not understood "
+          "and will be ignored.", child->key);
+      status = 0;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Additional sanity-checking */
+  while (status == 0)
+  {
+    if ((data->host == NULL)
+        && (data->plugin == NULL)
+        && (data->plugin_instance == NULL)
+        /* && (data->type == NULL) */
+        && (data->type_instance == NULL))
+    {
+      ERROR ("Target `set': You need to set at lease one of `Host', "
+          "`Plugin', `PluginInstance', `Type', or `TypeInstance'.");
+      status = -1;
+    }
+
+    break;
+  }
+
+  if (status != 0)
+  {
+    ts_destroy ((void *) &data);
+    return (status);
+  }
+
+  *user_data = data;
+  return (0);
+} /* }}} int ts_create */
+
+static int ts_invoke (const data_set_t *ds, value_list_t *vl, /* {{{ */
+    notification_meta_t __attribute__((unused)) **meta, void **user_data)
+{
+  ts_data_t *data;
+
+  if ((ds == NULL) || (vl == NULL) || (user_data == NULL))
+    return (-EINVAL);
+
+  data = *user_data;
+  if (data == NULL)
+  {
+    ERROR ("Target `set': Invoke: `data' is NULL.");
+    return (-EINVAL);
+  }
+
+#define SET_FIELD(f) if (data->f != NULL) { sstrncpy (vl->f, data->f, sizeof (vl->f)); }
+  SET_FIELD (host);
+  SET_FIELD (plugin);
+  SET_FIELD (plugin_instance);
+  /* SET_FIELD (type); */
+  SET_FIELD (type_instance);
+
+  return (FC_TARGET_CONTINUE);
+} /* }}} int ts_invoke */
+
+void module_register (void)
+{
+       target_proc_t tproc;
+
+       memset (&tproc, 0, sizeof (tproc));
+       tproc.create  = ts_create;
+       tproc.destroy = ts_destroy;
+       tproc.invoke  = ts_invoke;
+       fc_register_target ("set", tproc);
+} /* module_register */
+
+/* vim: set sw=2 sts=2 tw=78 et fdm=marker : */
+
index 02241f3..f164b53 100644 (file)
@@ -220,7 +220,6 @@ static void conn_submit_port_entry (port_entry_t *pe)
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "tcpconns", sizeof (vl.plugin));
   sstrncpy (vl.type, "tcp_connections", sizeof (vl.type));
index 5cd427a..d4bf2aa 100644 (file)
@@ -129,7 +129,6 @@ static void tss2_submit_gauge (const char *plugin_instance,
 
        vl.values     = values;
        vl.values_len = 1;
-       vl.time       = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
 
@@ -160,7 +159,6 @@ static void tss2_submit_io (const char *plugin_instance, const char *type,
 
        vl.values     = values;
        vl.values_len = 2;
-       vl.time       = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
 
@@ -408,7 +406,7 @@ static int tss2_select_vserver (FILE *read_fh, FILE *write_fh, vserver_list_t *v
 } /* int tss2_select_vserver */
 
 static int tss2_vserver_gapl (FILE *read_fh, FILE *write_fh,
-               vserver_list_t *vserver, gauge_t *ret_value)
+               gauge_t *ret_value)
 {
        /*
         * Reads the vserver's average packet loss and submits it to collectd.
@@ -679,7 +677,7 @@ static int tss2_read_vserver (vserver_list_t *vserver)
         * with an error. */
        if ((status == 0) && (vserver != NULL))
        {
-               status = tss2_vserver_gapl (read_fh, write_fh, vserver, &packet_loss);
+               status = tss2_vserver_gapl (read_fh, write_fh, &packet_loss);
                if (status == 0)
                {
                        valid |= 0x20;
index b613648..fe54aee 100644 (file)
@@ -51,7 +51,6 @@ static void thermal_submit (const char *plugin_instance, enum dev_type dt,
        vt.gauge = value;
 
        vl.values = &vt;
-       vl.time = time (NULL);
        sstrncpy (vl.plugin, "thermal", sizeof(vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance,
                        sizeof(vl.plugin_instance));
@@ -61,8 +60,8 @@ static void thermal_submit (const char *plugin_instance, enum dev_type dt,
        plugin_dispatch_values (&vl);
 }
 
-static int thermal_sysfs_device_read (const char *dir, const char *name,
-               void *user_data)
+static int thermal_sysfs_device_read (const char __attribute__((unused)) *dir,
+               const char *name, void __attribute__((unused)) *user_data)
 {
        char filename[256];
        char data[1024];
@@ -72,8 +71,9 @@ static int thermal_sysfs_device_read (const char *dir, const char *name,
        if (device_list && ignorelist_match (device_list, name))
                return -1;
 
-       len = snprintf (filename, sizeof (filename), "%s/%s/temp", dirname_sysfs, name);
-       if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
+       len = snprintf (filename, sizeof (filename),
+                       "%s/%s/temp", dirname_sysfs, name);
+       if ((len < 0) || ((size_t) len >= sizeof (filename)))
                return -1;
 
        len = read_file_contents (filename, data, sizeof(data));
@@ -91,8 +91,9 @@ static int thermal_sysfs_device_read (const char *dir, const char *name,
                }
        }
 
-       len = snprintf (filename, sizeof (filename), "%s/%s/cur_state", dirname_sysfs, name);
-       if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
+       len = snprintf (filename, sizeof (filename),
+                       "%s/%s/cur_state", dirname_sysfs, name);
+       if ((len < 0) || ((size_t) len >= sizeof (filename)))
                return -1;
 
        len = read_file_contents (filename, data, sizeof(data));
@@ -113,8 +114,8 @@ static int thermal_sysfs_device_read (const char *dir, const char *name,
        return ok ? 0 : -1;
 }
 
-static int thermal_procfs_device_read (const char *dir, const char *name,
-               void *user_data)
+static int thermal_procfs_device_read (const char __attribute__((unused)) *dir,
+               const char *name, void __attribute__((unused)) *user_data)
 {
        const char str_temp[] = "temperature:";
        char filename[256];
@@ -129,12 +130,15 @@ static int thermal_procfs_device_read (const char *dir, const char *name,
         * temperature:             55 C
         */
        
-       len = snprintf (filename, sizeof (filename), "%s/%s/temperature", dirname_procfs, name);
-       if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
+       len = snprintf (filename, sizeof (filename),
+                       "%s/%s/temperature", dirname_procfs, name);
+       if ((len < 0) || ((size_t) len >= sizeof (filename)))
                return -1;
 
        len = read_file_contents (filename, data, sizeof(data));
-       if (len > sizeof(str_temp) && data[--len] == '\n' && !strncmp(data, str_temp, sizeof(str_temp)-1)) {
+       if ((len > 0) && ((size_t) len > sizeof(str_temp))
+                       && (data[--len] == '\n')
+                       && (! strncmp(data, str_temp, sizeof(str_temp)-1))) {
                char *endptr = NULL;
                double temp;
                double celsius, add;
index a3b51dc..6fc8e11 100644 (file)
@@ -7,6 +7,7 @@ bytes                   value:GAUGE:0:U
 cache_result           value:COUNTER:0:4294967295
 cache_size             value:GAUGE:0:4294967295
 charge                 value:GAUGE:0:U
+compression_ratio      value:GAUGE:0:2
 connections            value:COUNTER:0:U
 counter                        value:COUNTER:U:U
 cpufreq                        value:GAUGE:0:U
@@ -19,11 +20,21 @@ disk_octets         read:COUNTER:0:17179869183, write:COUNTER:0:17179869183
 disk_ops               read:COUNTER:0:4294967295, write:COUNTER:0:4294967295
 disk_time              read:COUNTER:0:1000000, write:COUNTER:0:1000000
 dns_answer             value:COUNTER:0:65535
+dns_notify             value:COUNTER:0:65535
 dns_octets             queries:COUNTER:0:125000000, responses:COUNTER:0:125000000
 dns_opcode             value:COUNTER:0:65535
 dns_qtype              value:COUNTER:0:65535
+dns_qtype_cached       value:GAUGE:0:4294967295
+dns_query              value:COUNTER:0:65535
 dns_question           value:COUNTER:0:65535
 dns_rcode              value:COUNTER:0:65535
+dns_reject             value:COUNTER:0:65535
+dns_request            value:COUNTER:0:65535
+dns_resolver           value:COUNTER:0:65535
+dns_response           value:COUNTER:0:65535
+dns_transfer           value:COUNTER:0:65535
+dns_update             value:COUNTER:0:65535
+dns_zops               value:COUNTER:0:65535
 email_check            value:GAUGE:0:U
 email_count            value:GAUGE:0:U
 email_size             value:GAUGE:0:U
@@ -64,6 +75,7 @@ mysql_threads         running:GAUGE:0:U, connected:GAUGE:0:U, cached:GAUGE:0:U, created
 nfs_procedure          value:COUNTER:0:4294967295
 nginx_connections      value:GAUGE:0:U
 nginx_requests         value:COUNTER:0:134217728
+operations             value:COUNTER:0:4294967295
 percent                        percent:GAUGE:0:100.1
 pg_blks                        value:COUNTER:0:U
 pg_db_size             value:GAUGE:0:U
@@ -79,7 +91,10 @@ ps_count             processes:GAUGE:0:1000000, threads:GAUGE:0:1000000
 ps_cputime             user:COUNTER:0:16000000, syst:COUNTER:0:16000000
 ps_pagefaults          minflt:COUNTER:0:9223372036854775807, majflt:COUNTER:0:9223372036854775807
 ps_rss                 value:GAUGE:0:9223372036854775807
+ps_stacksize           value:GAUGE:0:9223372036854775807
 ps_state               value:GAUGE:0:65535
+ps_vm                  value:GAUGE:0:9223372036854775807
+queue_length           value:GAUGE:0:U
 serial_octets          rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
 signal_noise           value:GAUGE:U:0
 signal_power           value:GAUGE:U:0
index 3b4f1fa..4a1bfa4 100644 (file)
@@ -29,7 +29,8 @@ happen. See L<rrdcreate(1)> for more details.
 =head1 FILES
 
 The location of the types.db file is defined by the B<TypesDB> configuration
-option (see L<collectd.conf(5)>).
+option (see L<collectd.conf(5)>). It defaults to collectd's shared data
+directory, i.E<nbsp>e. F<I<prefix>/share/collectd/>.
 
 =head1 CUSTOM TYPES
 
index 3bdd8c2..57f3450 100644 (file)
@@ -278,7 +278,7 @@ static void *us_handle_client (void *arg)
        return ((void *) 0);
 } /* void *us_handle_client */
 
-static void *us_server_thread (void *arg)
+static void *us_server_thread (void __attribute__((unused)) *arg)
 {
        int  status;
        int *remote_fd;
index 10de92b..1e33754 100644 (file)
@@ -54,7 +54,6 @@ static void users_submit (gauge_t value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "users", sizeof (vl.plugin));
        sstrncpy (vl.type, "users", sizeof (vl.plugin));
index 7c717a1..a63c7d1 100644 (file)
@@ -522,7 +522,7 @@ gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl)
 
   /* This is important - the caller has no other way of knowing how many
    * values are returned. */
-  if (ret_num != ds->ds_num)
+  if (ret_num != (size_t) ds->ds_num)
   {
     ERROR ("utils_cache: uc_get_rate: ds[%s] has %i values, "
        "but uc_get_rate_by_name returned %zu.",
index dccafd1..6b54ace 100644 (file)
@@ -22,6 +22,8 @@
 #ifndef UTILS_CMD_FLUSH_H
 #define UTILS_CMD_FLUSH_H 1
 
+#include <stdio.h>
+
 int handle_flush (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_FLUSH_H */
index 186ef9b..ce3e28e 100644 (file)
@@ -51,7 +51,7 @@ int handle_getval (FILE *fh, char *buffer)
   const data_set_t *ds;
 
   int   status;
-  int   i;
+  size_t i;
 
   if ((fh == NULL) || (buffer == NULL))
     return (-1);
@@ -123,7 +123,7 @@ int handle_getval (FILE *fh, char *buffer)
     return (-1);
   }
 
-  if (ds->ds_num != values_num)
+  if ((size_t) ds->ds_num != values_num)
   {
     ERROR ("ds[%s]->ds_num = %i, "
        "but uc_get_rate_by_name returned %u values.",
index 86134cd..ed9ca9a 100644 (file)
@@ -22,6 +22,8 @@
 #ifndef UTILS_CMD_GETVAL_H
 #define UTILS_CMD_GETVAL_H 1
 
+#include <stdio.h>
+
 int handle_getval (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_GETVAL_H */
index 73146e7..0c72d67 100644 (file)
@@ -22,6 +22,8 @@
 #ifndef UTILS_CMD_LISTVAL_H
 #define UTILS_CMD_LISTVAL_H 1
 
+#include <stdio.h>
+
 int handle_listval (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_LISTVAL_H */
index 8d5475b..7e900b5 100644 (file)
@@ -22,6 +22,8 @@
 #ifndef UTILS_CMD_PUTNOTIF_H
 #define UTILS_CMD_PUTNOTIF_H 1
 
+#include <stdio.h>
+
 int handle_putnotif (FILE *fh, char *buffer);
 
 /* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
index 91b7016..5bd6ec7 100644 (file)
@@ -51,8 +51,6 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
        *value_str = '\0'; value_str++;
 
        vl->time = (time_t) atoi (time_str);
-       if (vl->time == 0)
-               vl->time = time (NULL);
 
        i = 0;
        dummy = value_str;
index 9ba52cc..8460b13 100644 (file)
@@ -22,6 +22,8 @@
 #ifndef UTILS_CMD_PUTVAL_H
 #define UTILS_CMD_PUTVAL_H 1
 
+#include <stdio.h>
+
 int handle_putval (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_PUTVAL_H */
index e93d823..09c4375 100644 (file)
@@ -39,7 +39,8 @@ typedef struct
        int interval;
 } c_complain_t;
 
-#define C_COMPLAIN_INIT { 0, 0 }
+#define C_COMPLAIN_INIT_STATIC { 0, 0 }
+#define C_COMPLAIN_INIT(c) do { (c)->last = 0; (c)->interval = 0; } while (0)
 
 /*
  * NAME
@@ -75,6 +76,15 @@ void c_complain_once (int level, c_complain_t *c, const char *format, ...);
 
 /*
  * NAME
+ *   c_would_release
+ *
+ * DESCRIPTION
+ *   Returns true if the specified complaint would be released, false else.
+ */
+#define c_would_release(c) ((c)->interval != 0)
+
+/*
+ * NAME
  *   c_release
  *
  * DESCRIPTION
@@ -86,7 +96,7 @@ void c_complain_once (int level, c_complain_t *c, const char *format, ...);
 void c_do_release (int level, c_complain_t *c, const char *format, ...);
 #define c_release(level, c, ...) \
        do { \
-               if ((c)->interval != 0) \
+               if (c_would_release (c)) \
                        c_do_release(level, c, __VA_ARGS__); \
        } while (0)
 
diff --git a/src/utils_db_query.c b/src/utils_db_query.c
new file mode 100644 (file)
index 0000000..c2897c7
--- /dev/null
@@ -0,0 +1,1168 @@
+/**
+ * collectd - src/utils_db_query.c
+ * Copyright (C) 2008,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 "configfile.h"
+#include "utils_db_query.h"
+
+/*
+ * Data types
+ */
+struct udb_result_s; /* {{{ */
+typedef struct udb_result_s udb_result_t;
+struct udb_result_s
+{
+  char    *type;
+  char    *instance_prefix;
+  char   **instances;
+  size_t   instances_num;
+  char   **values;
+  size_t   values_num;
+
+  /* Preparation area */
+  const   data_set_t *ds;
+  size_t *instances_pos;
+  size_t *values_pos;
+  char  **instances_buffer;
+  char  **values_buffer;
+
+  /* Legacy data */
+  int legacy_mode;
+  size_t legacy_position;
+  /* When in legacy mode:
+   * - type/ds hold the format of the data
+   * - instance_prefix is used as type-instance if non-NULL
+   * - legacy_position holds the index of the column to use as value.
+   */
+
+  udb_result_t *next;
+}; /* }}} */
+
+struct udb_query_s /* {{{ */
+{
+  char *name;
+  char *statement;
+  void *user_data;
+
+  int legacy_mode;
+
+  unsigned int min_version;
+  unsigned int max_version;
+
+  /* Preparation area */
+  size_t column_num;
+  char *host;
+  char *plugin;
+  char *db_name;
+
+  udb_result_t *results;
+}; /* }}} */
+
+/*
+ * Config Private functions
+ */
+static int udb_config_set_string (char **ret_string, /* {{{ */
+    oconfig_item_t *ci)
+{
+  char *string;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("db query utils: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+
+  string = strdup (ci->values[0].value.string);
+  if (string == NULL)
+  {
+    ERROR ("db query utils: strdup failed.");
+    return (-1);
+  }
+
+  if (*ret_string != NULL)
+    free (*ret_string);
+  *ret_string = string;
+
+  return (0);
+} /* }}} int udb_config_set_string */
+
+static int udb_config_add_string (char ***ret_array, /* {{{ */
+    size_t *ret_array_len, oconfig_item_t *ci)
+{
+  char **array;
+  size_t array_len;
+  int i;
+
+  if (ci->values_num < 1)
+  {
+    WARNING ("db query utils: The `%s' config option "
+        "needs at least one argument.", ci->key);
+    return (-1);
+  }
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    if (ci->values[i].type != OCONFIG_TYPE_STRING)
+    {
+      WARNING ("db query utils: Argument %i to the `%s' option "
+          "is not a string.", i + 1, ci->key);
+      return (-1);
+    }
+  }
+
+  array_len = *ret_array_len;
+  array = (char **) realloc (*ret_array,
+      sizeof (char *) * (array_len + ci->values_num));
+  if (array == NULL)
+  {
+    ERROR ("db query utils: realloc failed.");
+    return (-1);
+  }
+  *ret_array = array;
+
+  for (i = 0; i < ci->values_num; i++)
+  {
+    array[array_len] = strdup (ci->values[i].value.string);
+    if (array[array_len] == NULL)
+    {
+      ERROR ("db query utils: strdup failed.");
+      *ret_array_len = array_len;
+      return (-1);
+    }
+    array_len++;
+  }
+
+  *ret_array_len = array_len;
+  return (0);
+} /* }}} int udb_config_add_string */
+
+static int udb_config_set_uint (unsigned int *ret_value, /* {{{ */
+    oconfig_item_t *ci)
+{
+  double tmp;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("db query utils: The `%s' config option "
+        "needs exactly one numeric argument.", ci->key);
+    return (-1);
+  }
+
+  tmp = ci->values[0].value.number;
+  if ((tmp < 0.0) || (tmp > ((double) UINT_MAX)))
+    return (-ERANGE);
+
+  *ret_value = (unsigned int) (tmp + .5);
+  return (0);
+} /* }}} int udb_config_set_uint */
+
+/*
+ * Legacy result private functions
+ */
+static void udb_legacy_result_finish_result (udb_result_t *r) /* {{{ */
+{
+  if (r == NULL)
+    return;
+
+  assert (r->legacy_mode == 1);
+
+  r->ds = NULL;
+} /* }}} void udb_legacy_result_finish_result */
+
+static int udb_legacy_result_handle_result (udb_result_t *r, /* {{{ */
+    udb_query_t *q, char **column_values)
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  value_t value;
+  char *endptr;
+
+  assert (r->legacy_mode == 1);
+  assert (r->ds != NULL);
+  assert (r->ds->ds_num == 1);
+
+  vl.values = &value;
+  vl.values_len = 1;
+
+  endptr = NULL;
+  errno = 0;
+  if (r->ds->ds[0].type == DS_TYPE_COUNTER)
+    vl.values[0].counter = (counter_t) strtoll (column_values[r->legacy_position],
+        &endptr, /* base = */ 0);
+  else if (r->ds->ds[0].type == DS_TYPE_GAUGE)
+    vl.values[0].gauge = (gauge_t) strtod (column_values[r->legacy_position],
+        &endptr);
+  else
+    errno = EINVAL;
+
+  if ((endptr == column_values[r->legacy_position]) || (errno != 0))
+  {
+    WARNING ("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
+        column_values[r->legacy_position],
+        (r->ds->ds[0].type == DS_TYPE_COUNTER) ? "counter" : "gauge");
+    vl.values[0].gauge = NAN;
+  }
+
+  sstrncpy (vl.host, q->host, sizeof (vl.host));
+  sstrncpy (vl.plugin, q->plugin, sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, q->db_name, sizeof (vl.type_instance));
+  sstrncpy (vl.type, r->type, sizeof (vl.type));
+
+  if (r->instance_prefix != NULL)
+    sstrncpy (vl.type_instance, r->instance_prefix,
+        sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+
+  return (0);
+} /* }}} int udb_legacy_result_handle_result */
+
+static int udb_legacy_result_prepare_result (udb_result_t *r, /* {{{ */
+    char **column_names, size_t column_num)
+{
+  if (r == NULL)
+    return (-EINVAL);
+
+  assert (r->legacy_mode == 1);
+
+  /* Make sure previous preparations are cleaned up. */
+  udb_legacy_result_finish_result (r);
+
+  if (r->legacy_position >= column_num)
+  {
+    ERROR ("db query utils: The legacy configuration specified (at least) "
+        "%zu `Column's, but the query returned only %zu columns!",
+        r->legacy_position + 1, column_num);
+    return (-ENOENT);
+  }
+
+  /* Read `ds' and check number of values {{{ */
+  r->ds = plugin_get_ds (r->type);
+  if (r->ds == NULL)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: Type `%s' is not "
+        "known by the daemon. See types.db(5) for details.",
+        r->type);
+    return (-1);
+  }
+
+  if (r->ds->ds_num != 1)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: The type `%s' "
+        "requires exactly %i values, but the legacy configuration "
+        "requires exactly one!",
+        r->type,
+        r->ds->ds_num);
+    return (-1);
+  }
+  /* }}} */
+
+  return (0);
+} /* }}} int udb_legacy_result_prepare_result */
+
+static int udb_legacy_result_create (const char *query_name, /* {{{ */
+    udb_result_t **r_head, oconfig_item_t *ci, size_t position)
+{
+  udb_result_t *r;
+
+  if ((ci->values_num < 1) || (ci->values_num > 2)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING)
+      || ((ci->values_num == 2)
+        && (ci->values[1].type != OCONFIG_TYPE_STRING)))
+  {
+    WARNING ("db query utils: The `Column' block needs either one or two "
+        "string arguments.");
+    return (-1);
+  }
+
+  r = (udb_result_t *) malloc (sizeof (*r));
+  if (r == NULL)
+  {
+    ERROR ("db query utils: malloc failed.");
+    return (-1);
+  }
+  memset (r, 0, sizeof (*r));
+
+  r->legacy_mode = 1;
+  r->legacy_position = position;
+
+  r->type = strdup (ci->values[0].value.string);
+  if (r->type == NULL)
+  {
+    ERROR ("db query utils: strdup failed.");
+    free (r);
+    return (-1);
+  }
+
+  r->instance_prefix = NULL;
+  if (ci->values_num == 2)
+  {
+    r->instance_prefix = strdup (ci->values[1].value.string);
+    if (r->instance_prefix == NULL)
+    {
+      ERROR ("db query utils: strdup failed.");
+      free (r->type);
+      free (r);
+      return (-1);
+    }
+  }
+
+  /* If all went well, add this result to the list of results. */
+  if (*r_head == NULL)
+  {
+    *r_head = r;
+  }
+  else
+  {
+    udb_result_t *last;
+
+    last = *r_head;
+    while (last->next != NULL)
+      last = last->next;
+
+    last->next = r;
+  }
+
+  return (0);
+} /* }}} int udb_legacy_result_create */
+
+/*
+ * Result private functions
+ */
+static void udb_result_submit (udb_result_t *r, udb_query_t *q) /* {{{ */
+{
+  value_list_t vl = VALUE_LIST_INIT;
+  size_t i;
+
+  assert (r != NULL);
+  assert (r->legacy_mode == 0);
+  assert (r->ds != NULL);
+  assert (((size_t) r->ds->ds_num) == r->values_num);
+
+  vl.values = (value_t *) calloc (r->ds->ds_num, sizeof (value_t));
+  if (vl.values == NULL)
+  {
+    ERROR ("db query utils: malloc failed.");
+    return;
+  }
+  vl.values_len = r->ds->ds_num;
+
+  for (i = 0; i < r->values_num; i++)
+  {
+    char *endptr;
+
+    endptr = NULL;
+    errno = 0;
+    if (r->ds->ds[i].type == DS_TYPE_COUNTER)
+      vl.values[i].counter = (counter_t) strtoll (r->values_buffer[i],
+          &endptr, /* base = */ 0);
+    else if (r->ds->ds[i].type == DS_TYPE_GAUGE)
+      vl.values[i].gauge = (gauge_t) strtod (r->values_buffer[i], &endptr);
+    else
+      errno = EINVAL;
+
+    if ((endptr == r->values_buffer[i]) || (errno != 0))
+    {
+      WARNING ("db query utils: udb_result_submit: Parsing `%s' as %s failed.",
+          r->values_buffer[i],
+          (r->ds->ds[i].type == DS_TYPE_COUNTER) ? "counter" : "gauge");
+      vl.values[i].gauge = NAN;
+    }
+  }
+
+  sstrncpy (vl.host, q->host, sizeof (vl.host));
+  sstrncpy (vl.plugin, q->plugin, sizeof (vl.plugin));
+  sstrncpy (vl.plugin_instance, q->db_name, sizeof (vl.type_instance));
+  sstrncpy (vl.type, r->type, sizeof (vl.type));
+
+  /* Set vl.type_instance {{{ */
+  if (r->instances_num <= 0)
+  {
+    if (r->instance_prefix == NULL)
+      vl.type_instance[0] = 0;
+    else
+      sstrncpy (vl.type_instance, r->instance_prefix,
+          sizeof (vl.type_instance));
+  }
+  else /* if ((r->instances_num > 0) */
+  {
+    if (r->instance_prefix == NULL)
+    {
+      strjoin (vl.type_instance, sizeof (vl.type_instance),
+          r->instances_buffer, r->instances_num, "-");
+    }
+    else
+    {
+      char tmp[DATA_MAX_NAME_LEN];
+
+      strjoin (tmp, sizeof (tmp), r->instances_buffer, r->instances_num, "-");
+      tmp[sizeof (tmp) - 1] = 0;
+
+      snprintf (vl.type_instance, sizeof (vl.type_instance), "%s-%s",
+          r->instance_prefix, tmp);
+    }
+  }
+  vl.type_instance[sizeof (vl.type_instance) - 1] = 0;
+  /* }}} */
+
+  plugin_dispatch_values (&vl);
+
+  sfree (vl.values);
+} /* }}} void udb_result_submit */
+
+static void udb_result_finish_result (udb_result_t *r) /* {{{ */
+{
+  if (r == NULL)
+    return;
+
+  if (r->legacy_mode == 1)
+  {
+    udb_legacy_result_finish_result (r);
+    return;
+  }
+
+  assert (r->legacy_mode == 0);
+
+  r->ds = NULL;
+  sfree (r->instances_pos);
+  sfree (r->values_pos);
+  sfree (r->instances_buffer);
+  sfree (r->values_buffer);
+} /* }}} void udb_result_finish_result */
+
+static int udb_result_handle_result (udb_result_t *r, /* {{{ */
+    udb_query_t *q, char **column_values)
+{
+  size_t i;
+
+  if (r->legacy_mode == 1)
+    return (udb_legacy_result_handle_result (r, q, column_values));
+
+  assert (r->legacy_mode == 0);
+
+  for (i = 0; i < r->instances_num; i++)
+    r->instances_buffer[i] = column_values[r->instances_pos[i]];
+
+  for (i = 0; i < r->values_num; i++)
+    r->values_buffer[i] = column_values[r->values_pos[i]];
+
+  udb_result_submit (r, q);
+
+  return (0);
+} /* }}} int udb_result_handle_result */
+
+static int udb_result_prepare_result (udb_result_t *r, /* {{{ */
+    char **column_names, size_t column_num)
+{
+  size_t i;
+
+  if (r == NULL)
+    return (-EINVAL);
+
+  if (r->legacy_mode == 1)
+    return (udb_legacy_result_prepare_result (r, column_names, column_num));
+
+  assert (r->legacy_mode == 0);
+
+#define BAIL_OUT(status) \
+  r->ds = NULL; \
+  sfree (r->instances_pos); \
+  sfree (r->values_pos); \
+  sfree (r->instances_buffer); \
+  sfree (r->values_buffer); \
+  return (status)
+
+  /* Make sure previous preparations are cleaned up. */
+  udb_result_finish_result (r);
+  r->instances_pos = NULL;
+  r->values_pos = NULL;
+
+  /* Read `ds' and check number of values {{{ */
+  r->ds = plugin_get_ds (r->type);
+  if (r->ds == NULL)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: Type `%s' is not "
+        "known by the daemon. See types.db(5) for details.",
+        r->type);
+    BAIL_OUT (-1);
+  }
+
+  if (((size_t) r->ds->ds_num) != r->values_num)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: The type `%s' "
+        "requires exactly %i value%s, but the configuration specifies %zu.",
+        r->type,
+        r->ds->ds_num, (r->ds->ds_num == 1) ? "" : "s",
+        r->values_num);
+    BAIL_OUT (-1);
+  }
+  /* }}} */
+
+  /* Allocate r->instances_pos, r->values_pos, r->instances_buffer, and
+   * r->values_buffer {{{ */
+  if (r->instances_num > 0)
+  {
+    r->instances_pos = (size_t *) calloc (r->instances_num, sizeof (size_t));
+    if (r->instances_pos == NULL)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+      BAIL_OUT (-ENOMEM);
+    }
+
+    r->instances_buffer = (char **) calloc (r->instances_num, sizeof (char *));
+    if (r->instances_buffer == NULL)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+      BAIL_OUT (-ENOMEM);
+    }
+  } /* if (r->instances_num > 0) */
+
+  r->values_pos = (size_t *) calloc (r->values_num, sizeof (size_t));
+  if (r->values_pos == NULL)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+    BAIL_OUT (-ENOMEM);
+  }
+
+  r->values_buffer = (char **) calloc (r->values_num, sizeof (char *));
+  if (r->values_buffer == NULL)
+  {
+    ERROR ("db query utils: udb_result_prepare_result: malloc failed.");
+    BAIL_OUT (-ENOMEM);
+  }
+  /* }}} */
+
+  /* Determine the position of the instance columns {{{ */
+  for (i = 0; i < r->instances_num; i++)
+  {
+    size_t j;
+
+    for (j = 0; j < column_num; j++)
+    {
+      if (strcasecmp (r->instances[i], column_names[j]) == 0)
+      {
+        r->instances_pos[i] = j;
+        break;
+      }
+    }
+
+    if (j >= column_num)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: "
+          "Column `%s' could not be found.",
+          r->instances[i]);
+      BAIL_OUT (-ENOENT);
+    }
+  } /* }}} for (i = 0; i < r->instances_num; i++) */
+
+  /* Determine the position of the value columns {{{ */
+  for (i = 0; i < r->values_num; i++)
+  {
+    size_t j;
+
+    for (j = 0; j < column_num; j++)
+    {
+      if (strcasecmp (r->values[i], column_names[j]) == 0)
+      {
+        r->values_pos[i] = j;
+        break;
+      }
+    }
+
+    if (j >= column_num)
+    {
+      ERROR ("db query utils: udb_result_prepare_result: "
+          "Column `%s' could not be found.",
+          r->values[i]);
+      BAIL_OUT (-ENOENT);
+    }
+  } /* }}} for (i = 0; i < r->values_num; i++) */
+
+#undef BAIL_OUT
+  return (0);
+} /* }}} int udb_result_prepare_result */
+
+static void udb_result_free (udb_result_t *r) /* {{{ */
+{
+  size_t i;
+
+  if (r == NULL)
+    return;
+
+  sfree (r->type);
+
+  for (i = 0; i < r->instances_num; i++)
+    sfree (r->instances[i]);
+  sfree (r->instances);
+
+  for (i = 0; i < r->values_num; i++)
+    sfree (r->values[i]);
+  sfree (r->values);
+
+  udb_result_free (r->next);
+
+  sfree (r);
+} /* }}} void udb_result_free */
+
+static int udb_result_create (const char *query_name, /* {{{ */
+    udb_result_t **r_head, oconfig_item_t *ci)
+{
+  udb_result_t *r;
+  int status;
+  int i;
+
+  if (ci->values_num != 0)
+  {
+    WARNING ("db query utils: The `Result' block doesn't accept "
+        "any arguments. Ignoring %i argument%s.",
+        ci->values_num, (ci->values_num == 1) ? "" : "s");
+  }
+
+  r = (udb_result_t *) malloc (sizeof (*r));
+  if (r == NULL)
+  {
+    ERROR ("db query utils: malloc failed.");
+    return (-1);
+  }
+  memset (r, 0, sizeof (*r));
+  r->type = NULL;
+  r->instance_prefix = NULL;
+  r->instances = NULL;
+  r->values = NULL;
+  r->next = NULL;
+
+  /* Fill the `udb_result_t' structure.. */
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Type", child->key) == 0)
+      status = udb_config_set_string (&r->type, child);
+    else if (strcasecmp ("InstancePrefix", child->key) == 0)
+      status = udb_config_set_string (&r->instance_prefix, child);
+    else if (strcasecmp ("InstancesFrom", child->key) == 0)
+      status = udb_config_add_string (&r->instances, &r->instances_num, child);
+    else if (strcasecmp ("ValuesFrom", child->key) == 0)
+      status = udb_config_add_string (&r->values, &r->values_num, child);
+    else
+    {
+      WARNING ("db query utils: Query `%s': Option `%s' not allowed here.",
+          query_name, child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  while (status == 0)
+  {
+    if (r->type == NULL)
+    {
+      WARNING ("db query utils: `Type' not given for "
+          "result in query `%s'", query_name);
+      status = -1;
+    }
+    if (r->values == NULL)
+    {
+      WARNING ("db query utils: `ValuesFrom' not given for "
+          "result in query `%s'", query_name);
+      status = -1;
+    }
+
+    break;
+  } /* while (status == 0) */
+
+  if (status != 0)
+  {
+    udb_result_free (r);
+    return (-1);
+  }
+
+  /* If all went well, add this result to the list of results. */
+  if (*r_head == NULL)
+  {
+    *r_head = r;
+  }
+  else
+  {
+    udb_result_t *last;
+
+    last = *r_head;
+    while (last->next != NULL)
+      last = last->next;
+
+    last->next = r;
+  }
+
+  return (0);
+} /* }}} int udb_result_create */
+
+/*
+ * Query private functions
+ */
+void udb_query_free_one (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return;
+
+  sfree (q->name);
+  sfree (q->statement);
+
+  udb_result_free (q->results);
+
+  sfree (q);
+} /* }}} void udb_query_free_one */
+
+/*
+ * Query public functions
+ */
+int udb_query_create (udb_query_t ***ret_query_list, /* {{{ */
+    size_t *ret_query_list_len, oconfig_item_t *ci,
+    udb_query_create_callback_t cb, int legacy_mode)
+{
+  udb_query_t **query_list;
+  size_t        query_list_len;
+
+  udb_query_t *q;
+  int status;
+  int i;
+
+  size_t legacy_position;
+
+  if ((ret_query_list == NULL) || (ret_query_list_len == NULL))
+    return (-EINVAL);
+  query_list     = *ret_query_list;
+  query_list_len = *ret_query_list_len;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("db query utils: The `Query' block "
+        "needs exactly one string argument.");
+    return (-1);
+  }
+
+  q = (udb_query_t *) malloc (sizeof (*q));
+  if (q == NULL)
+  {
+    ERROR ("db query utils: malloc failed.");
+    return (-1);
+  }
+  memset (q, 0, sizeof (*q));
+  q->legacy_mode = legacy_mode;
+  q->min_version = 0;
+  q->max_version = UINT_MAX;
+
+  legacy_position = 0;
+
+  status = udb_config_set_string (&q->name, ci);
+  if (status != 0)
+  {
+    sfree (q);
+    return (status);
+  }
+
+  /* Fill the `udb_query_t' structure.. */
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+
+    if (strcasecmp ("Statement", child->key) == 0)
+      status = udb_config_set_string (&q->statement, child);
+    else if (strcasecmp ("Result", child->key) == 0)
+      status = udb_result_create (q->name, &q->results, child);
+    else if (strcasecmp ("MinVersion", child->key) == 0)
+      status = udb_config_set_uint (&q->min_version, child);
+    else if (strcasecmp ("MaxVersion", child->key) == 0)
+      status = udb_config_set_uint (&q->max_version, child);
+
+    /* PostgreSQL compatibility code */
+    else if ((strcasecmp ("Query", child->key) == 0)
+        && (q->legacy_mode == 1))
+    {
+      WARNING ("db query utils: Query `%s': The `Query' option is "
+          "deprecated. Please use `Statement' instead.",
+          q->name);
+      status = udb_config_set_string (&q->statement, child);
+    }
+    else if ((strcasecmp ("Column", child->key) == 0)
+        && (q->legacy_mode == 1))
+    {
+      WARNING ("db query utils: Query `%s': The `Column' option is "
+          "deprecated. Please use the new syntax instead.",
+          q->name);
+      status = udb_legacy_result_create (q->name, &q->results, child,
+          legacy_position);
+      legacy_position++;
+    }
+    else if ((strcasecmp ("MinPGVersion", child->key) == 0)
+        && (q->legacy_mode == 1))
+    {
+      WARNING ("db query utils: Query `%s': The `MinPGVersion' option is "
+          "deprecated. Please use `MinVersion' instead.",
+          q->name);
+      status = udb_config_set_uint (&q->min_version, child);
+    }
+    else if ((strcasecmp ("MaxPGVersion", child->key) == 0)
+        && (q->legacy_mode == 1))
+    {
+      WARNING ("db query utils: Query `%s': The `MaxPGVersion' option is "
+          "deprecated. Please use `MaxVersion' instead.",
+          q->name);
+      status = udb_config_set_uint (&q->max_version, child);
+    }
+
+    /* Call custom callbacks */
+    else if (cb != NULL)
+    {
+      status = (*cb) (q, child);
+      if (status != 0)
+      {
+        WARNING ("db query utils: The configuration callback failed "
+            "to handle `%s'.", child->key);
+      }
+    }
+    else
+    {
+      WARNING ("db query utils: Query `%s': Option `%s' not allowed here.",
+          q->name, child->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  /* Check that all necessary options have been given. */
+  if (status == 0)
+  {
+    if (q->statement == NULL)
+    {
+      WARNING ("db query utils: Query `%s': No `Statement' given.", q->name);
+      status = -1;
+    }
+    if (q->results == NULL)
+    {
+      WARNING ("db query utils: Query `%s': No (valid) `Result' block given.",
+          q->name);
+      status = -1;
+    }
+  } /* if (status == 0) */
+
+  /* If all went well, add this query to the list of queries within the
+   * database structure. */
+  if (status == 0)
+  {
+    udb_query_t **temp;
+
+    temp = (udb_query_t **) realloc (query_list,
+        sizeof (*query_list) * (query_list_len + 1));
+    if (temp == NULL)
+    {
+      ERROR ("db query utils: realloc failed");
+      status = -1;
+    }
+    else
+    {
+      query_list = temp;
+      query_list[query_list_len] = q;
+      query_list_len++;
+    }
+  }
+
+  if (status != 0)
+  {
+    udb_query_free_one (q);
+    return (-1);
+  }
+
+  *ret_query_list     = query_list;
+  *ret_query_list_len = query_list_len;
+
+  return (0);
+} /* }}} int udb_query_create */
+
+void udb_query_free (udb_query_t **query_list, size_t query_list_len) /* {{{ */
+{
+  size_t i;
+
+  if (query_list == NULL)
+    return;
+
+  for (i = 0; i < query_list_len; i++)
+    udb_query_free_one (query_list[i]);
+
+  sfree (query_list);
+} /* }}} void udb_query_free */
+
+int udb_query_pick_from_list_by_name (const char *name, /* {{{ */
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len)
+{
+  size_t i;
+  int num_added;
+
+  if ((name == NULL) || (src_list == NULL) || (dst_list == NULL)
+      || (dst_list_len == NULL))
+  {
+    ERROR ("db query utils: udb_query_pick_from_list_by_name: "
+        "Invalid argument.");
+    return (-EINVAL);
+  }
+
+  num_added = 0;
+  for (i = 0; i < src_list_len; i++)
+  {
+    udb_query_t **tmp_list;
+    size_t tmp_list_len;
+
+    if (strcasecmp (name, src_list[i]->name) != 0)
+      continue;
+
+    tmp_list_len = *dst_list_len;
+    tmp_list = (udb_query_t **) realloc (*dst_list, (tmp_list_len + 1)
+        * sizeof (udb_query_t *));
+    if (tmp_list == NULL)
+    {
+      ERROR ("db query utils: realloc failed.");
+      return (-ENOMEM);
+    }
+
+    tmp_list[tmp_list_len] = src_list[i];
+    tmp_list_len++;
+
+    *dst_list = tmp_list;
+    *dst_list_len = tmp_list_len;
+
+    num_added++;
+  } /* for (i = 0; i < src_list_len; i++) */
+
+  if (num_added <= 0)
+  {
+    ERROR ("db query utils: Cannot find query `%s'. Make sure the <Query> "
+        "block is above the database definition!",
+        name);
+    return (-ENOENT);
+  }
+  else
+  {
+    DEBUG ("db query utils: Added %i versions of query `%s'.",
+        num_added, name);
+  }
+
+  return (0);
+} /* }}} int udb_query_pick_from_list_by_name */
+
+int udb_query_pick_from_list (oconfig_item_t *ci, /* {{{ */
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len)
+{
+  const char *name;
+
+  if ((ci == NULL) || (src_list == NULL) || (dst_list == NULL)
+      || (dst_list_len == NULL))
+  {
+    ERROR ("db query utils: udb_query_pick_from_list: "
+        "Invalid argument.");
+    return (-EINVAL);
+  }
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    ERROR ("db query utils: The `%s' config option "
+        "needs exactly one string argument.", ci->key);
+    return (-1);
+  }
+  name = ci->values[0].value.string;
+
+  return (udb_query_pick_from_list_by_name (name,
+        src_list, src_list_len,
+        dst_list, dst_list_len));
+} /* }}} int udb_query_pick_from_list */
+
+const char *udb_query_get_name (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return (NULL);
+
+  return (q->name);
+} /* }}} const char *udb_query_get_name */
+
+const char *udb_query_get_statement (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return (NULL);
+
+  return (q->statement);
+} /* }}} const char *udb_query_get_statement */
+
+void udb_query_set_user_data (udb_query_t *q, void *user_data) /* {{{ */
+{
+  if (q == NULL)
+    return;
+
+  q->user_data = user_data;
+} /* }}} void udb_query_set_user_data */
+
+void *udb_query_get_user_data (udb_query_t *q) /* {{{ */
+{
+  if (q == NULL)
+    return (NULL);
+
+  return (q->user_data);
+} /* }}} void *udb_query_get_user_data */
+
+int udb_query_check_version (udb_query_t *q, unsigned int version) /* {{{ */
+{
+  if (q == NULL)
+    return (-EINVAL);
+
+  if ((version < q->min_version) || (version > q->max_version))
+    return (0);
+
+  return (1);
+} /* }}} int udb_query_check_version */
+
+void udb_query_finish_result (udb_query_t *q) /* {{{ */
+{
+  udb_result_t *r;
+
+  if (q == NULL)
+    return;
+
+  q->column_num = 0;
+  sfree (q->host);
+  sfree (q->plugin);
+  sfree (q->db_name);
+
+  for (r = q->results; r != NULL; r = r->next)
+    udb_result_finish_result (r);
+} /* }}} void udb_query_finish_result */
+
+int udb_query_handle_result (udb_query_t *q, char **column_values) /* {{{ */
+{
+  udb_result_t *r;
+  int success;
+  int status;
+
+  if (q == NULL)
+    return (-EINVAL);
+
+  if ((q->column_num < 1) || (q->host == NULL) || (q->plugin == NULL)
+      || (q->db_name == NULL))
+  {
+    ERROR ("db query utils: Query `%s': Query is not prepared; "
+        "can't handle result.", q->name);
+    return (-EINVAL);
+  }
+
+#if defined(COLLECT_DEBUG) && COLLECT_DEBUG /* {{{ */
+  do
+  {
+    size_t i;
+
+    for (i = 0; i < q->column_num; i++)
+    {
+      DEBUG ("db query utils: udb_query_handle_result (%s, %s): "
+          "column[%zu] = %s;",
+          q->db_name, q->name, i, column_values[i]);
+    }
+  } while (0);
+#endif /* }}} */
+
+  success = 0;
+  for (r = q->results; r != NULL; r = r->next)
+  {
+    status = udb_result_handle_result (r, q, column_values);
+    if (status == 0)
+      success++;
+  }
+
+  if (success == 0)
+  {
+    ERROR ("db query utils: udb_query_handle_result (%s, %s): "
+        "All results failed.", q->db_name, q->name);
+    return (-1);
+  }
+
+  return (0);
+} /* }}} int udb_query_handle_result */
+
+int udb_query_prepare_result (udb_query_t *q, /* {{{ */
+    const char *host, const char *plugin, const char *db_name,
+    char **column_names, size_t column_num)
+{
+  udb_result_t *r;
+  int status;
+
+  if (q == NULL)
+    return (-EINVAL);
+
+  udb_query_finish_result (q);
+
+  q->column_num = column_num;
+  q->host = strdup (host);
+  q->plugin = strdup (plugin);
+  q->db_name = strdup (db_name);
+
+  if ((q->host == NULL) || (q->plugin == NULL) || (q->db_name == NULL))
+  {
+    ERROR ("db query utils: Query `%s': Prepare failed: Out of memory.", q->name);
+    udb_query_finish_result (q);
+    return (-ENOMEM);
+  }
+
+#if defined(COLLECT_DEBUG) && COLLECT_DEBUG
+  do
+  {
+    size_t i;
+
+    for (i = 0; i < column_num; i++)
+    {
+      DEBUG ("db query utils: udb_query_prepare_result: "
+          "query = %s; column[%zu] = %s;",
+          q->name, i, column_names[i]);
+    }
+  } while (0);
+#endif
+
+  for (r = q->results; r != NULL; r = r->next)
+  {
+    status = udb_result_prepare_result (r, column_names, column_num);
+    if (status != 0)
+    {
+      udb_query_finish_result (q);
+      return (status);
+    }
+  }
+
+  return (0);
+} /* }}} int udb_query_prepare_result */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_db_query.h b/src/utils_db_query.h
new file mode 100644 (file)
index 0000000..6703e92
--- /dev/null
@@ -0,0 +1,72 @@
+/**
+ * collectd - src/utils_db_query.h
+ * Copyright (C) 2008,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>
+ **/
+
+#ifndef UTILS_DB_QUERY_H
+#define UTILS_DB_QUERY_H 1
+
+#include "configfile.h"
+
+/*
+ * Data types
+ */
+struct udb_query_s;
+typedef struct udb_query_s udb_query_t;
+
+typedef int (*udb_query_create_callback_t) (udb_query_t *q,
+    oconfig_item_t *ci);
+
+/* 
+ * Public functions
+ */
+int udb_query_create (udb_query_t ***ret_query_list,
+    size_t *ret_query_list_len, oconfig_item_t *ci,
+    udb_query_create_callback_t cb, int legacy_mode);
+void udb_query_free (udb_query_t **query_list, size_t query_list_len);
+
+int udb_query_pick_from_list_by_name (const char *name,
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len);
+int udb_query_pick_from_list (oconfig_item_t *ci,
+    udb_query_t **src_list, size_t src_list_len,
+    udb_query_t ***dst_list, size_t *dst_list_len);
+
+const char *udb_query_get_name (udb_query_t *q);
+const char *udb_query_get_statement (udb_query_t *q);
+
+void  udb_query_set_user_data (udb_query_t *q, void *user_data);
+void *udb_query_get_user_data (udb_query_t *q);
+
+/* 
+ * udb_query_check_version
+ *
+ * Returns 0 if the query is NOT suitable for `version' and >0 if the
+ * query IS suitable.
+ */
+int udb_query_check_version (udb_query_t *q, unsigned int version);
+
+int udb_query_prepare_result (udb_query_t *q,
+    const char *host, const char *plugin, const char *db_name,
+    char **column_names, size_t column_num);
+int udb_query_handle_result (udb_query_t *q, char **column_values);
+void udb_query_finish_result (udb_query_t *q);
+
+#endif /* UTILS_DB_QUERY_H */
+/* vim: set sw=2 sts=2 et : */
index c16b3c1..b7dc791 100644 (file)
@@ -337,9 +337,7 @@ rfc1035NameUnpack(const char *buf, size_t sz, off_t * off, char *name, size_t ns
 }
 
 static int
-handle_dns(const char *buf, int len,
-       const struct in6_addr *s_addr,
-       const struct in6_addr *d_addr)
+handle_dns(const char *buf, int len)
 {
     rfc1035_header_t qh;
     uint16_t us;
@@ -412,16 +410,14 @@ handle_dns(const char *buf, int len,
 }
 
 static int
-handle_udp(const struct udphdr *udp, int len,
-       const struct in6_addr *s_addr,
-       const struct in6_addr *d_addr)
+handle_udp(const struct udphdr *udp, int len)
 {
     char buf[PCAP_SNAPLEN];
     if ((ntohs (udp->UDP_DEST) != 53)
                    && (ntohs (udp->UDP_SRC) != 53))
        return 0;
     memcpy(buf, udp + 1, len - sizeof(*udp));
-    if (0 == handle_dns(buf, len - sizeof(*udp), s_addr, d_addr))
+    if (0 == handle_dns(buf, len - sizeof(*udp)))
        return 0;
     return 1;
 }
@@ -492,7 +488,7 @@ handle_ipv6 (struct ip6_hdr *ipv6, int len)
        return (0);
 
     memcpy (buf, (char *) ipv6 + offset, payload_len);
-    if (handle_udp ((struct udphdr *) buf, payload_len, &s_addr, &d_addr) == 0)
+    if (handle_udp ((struct udphdr *) buf, payload_len) == 0)
        return (0);
 
     return (1); /* Success */
@@ -516,7 +512,7 @@ handle_ip(const struct ip *ip, int len)
     if (IPPROTO_UDP != ip->ip_p)
        return 0;
     memcpy(buf, (void *) ip + offset, len - offset);
-    if (0 == handle_udp((struct udphdr *) buf, len - offset, &s_addr, &d_addr))
+    if (0 == handle_udp((struct udphdr *) buf, len - offset))
        return 0;
     return 1;
 }
index b3f08ac..efc7903 100644 (file)
@@ -36,6 +36,7 @@
 #include "config.h"
 
 #include <arpa/nameser.h>
+#include <stdint.h>
 
 #if HAVE_PCAP_H
 # include <pcap.h>
index 9e75e4e..c19c5ff 100644 (file)
@@ -50,7 +50,7 @@ static char *match_substr (const char *str, int begin, int end)
 
   if ((begin < 0) || (end < 0) || (begin >= end))
     return (NULL);
-  if (end > (strlen (str) + 1))
+  if ((size_t) end > (strlen (str) + 1))
   {
     ERROR ("utils_match: match_substr: `end' points after end of string.");
     return (NULL);
@@ -68,7 +68,7 @@ static char *match_substr (const char *str, int begin, int end)
   return (ret);
 } /* char *match_substr */
 
-static int default_callback (const char *str,
+static int default_callback (const char __attribute__((unused)) *str,
     char * const *matches, size_t matches_num, void *user_data)
 {
   cu_match_value_t *data = (cu_match_value_t *) user_data;
@@ -228,7 +228,7 @@ int match_apply (cu_match_t *obj, const char *str)
   regmatch_t re_match[32];
   char *matches[32];
   size_t matches_num;
-  int i;
+  size_t i;
 
   if ((obj == NULL) || (str == NULL))
     return (-1);
index d94c140..2168cd1 100644 (file)
@@ -150,4 +150,55 @@ int parse_option (char **ret_buffer, char **ret_key, char **ret_value)
   return (0);
 } /* int parse_option */
 
+int escape_string (char *buffer, size_t buffer_size)
+{
+  char *temp;
+  size_t i;
+  size_t j;
+
+  /* Check if we need to escape at all first */
+  temp = strpbrk (buffer, " \t\"\\");
+  if (temp == NULL)
+    return (0);
+
+  temp = (char *) malloc (buffer_size);
+  if (temp == NULL)
+    return (-1);
+  memset (temp, 0, buffer_size);
+
+  temp[0] = '"';
+  j = 1;
+
+  for (i = 0; i < buffer_size; i++)
+  {
+    if (buffer[i] == 0)
+    {
+      break;
+    }
+    else if ((buffer[i] == '"') || (buffer[i] == '\\'))
+    {
+      if (j > (buffer_size - 4))
+        break;
+      temp[j] = '\\';
+      temp[j + 1] = buffer[i];
+      j += 2;
+    }
+    else
+    {
+      if (j > (buffer_size - 3))
+        break;
+      temp[j] = buffer[i];
+      j++;
+    }
+  }
+
+  assert ((j + 1) < buffer_size);
+  temp[j] = '"';
+  temp[j + 1] = 0;
+
+  sstrncpy (buffer, temp, buffer_size);
+  sfree (temp);
+  return (0);
+} /* int escape_string */
+
 /* vim: set sw=2 ts=8 tw=78 et : */
index cb7f6d3..1dfb3ae 100644 (file)
@@ -25,6 +25,8 @@
 int parse_string (char **ret_buffer, char **ret_string);
 int parse_option (char **ret_buffer, char **ret_key, char **ret_value);
 
+int escape_string (char *buffer, size_t buffer_size);
+
 #endif /* UTILS_PARSE_OPTION */
 
 /* vim: set sw=2 ts=8 tw=78 et : */
diff --git a/src/utils_rrdcreate.c b/src/utils_rrdcreate.c
new file mode 100644 (file)
index 0000000..c4e9d8b
--- /dev/null
@@ -0,0 +1,421 @@
+/**
+ * collectd - src/utils_rrdcreate.c
+ * Copyright (C) 2006-2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_rrdcreate.h"
+
+#include <pthread.h>
+#include <rrd.h>
+
+/*
+ * Private variables
+ */
+static int rra_timespans[] =
+{
+  3600,
+  86400,
+  604800,
+  2678400,
+  31622400
+};
+static int rra_timespans_num = STATIC_ARRAY_SIZE (rra_timespans);
+
+static char *rra_types[] =
+{
+  "AVERAGE",
+  "MIN",
+  "MAX"
+};
+static int rra_types_num = STATIC_ARRAY_SIZE (rra_types);
+
+#if !defined(HAVE_THREADSAFE_LIBRRD) || !HAVE_THREADSAFE_LIBRRD
+static pthread_mutex_t librrd_lock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+/*
+ * Private functions
+ */
+static void rra_free (int rra_num, char **rra_def) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < rra_num; i++)
+  {
+    sfree (rra_def[i]);
+  }
+  sfree (rra_def);
+} /* }}} void rra_free */
+
+/* * * * * * * * * *
+ * WARNING:  Magic *
+ * * * * * * * * * */
+static int rra_get (char ***ret, const value_list_t *vl, /* {{{ */
+    const rrdcreate_config_t *cfg)
+{
+  char **rra_def;
+  int rra_num;
+
+  int *rts;
+  int  rts_num;
+
+  int rra_max;
+
+  int span;
+
+  int cdp_num;
+  int cdp_len;
+  int i, j;
+
+  char buffer[128];
+
+  /* The stepsize we use here: If it is user-set, use it. If not, use the
+   * interval of the value-list. */
+  int ss;
+
+  if (cfg->rrarows <= 0)
+  {
+    *ret = NULL;
+    return (-1);
+  }
+
+  if ((cfg->xff < 0) || (cfg->xff >= 1.0))
+  {
+    *ret = NULL;
+    return (-1);
+  }
+
+  ss = (cfg->stepsize > 0) ? cfg->stepsize : vl->interval;
+  if (ss <= 0)
+  {
+    *ret = NULL;
+    return (-1);
+  }
+
+  /* Use the configured timespans or fall back to the built-in defaults */
+  if (cfg->timespans_num != 0)
+  {
+    rts = cfg->timespans;
+    rts_num = cfg->timespans_num;
+  }
+  else
+  {
+    rts = rra_timespans;
+    rts_num = rra_timespans_num;
+  }
+
+  rra_max = rts_num * rra_types_num;
+
+  if ((rra_def = (char **) malloc ((rra_max + 1) * sizeof (char *))) == NULL)
+    return (-1);
+  memset (rra_def, '\0', (rra_max + 1) * sizeof (char *));
+  rra_num = 0;
+
+  cdp_len = 0;
+  for (i = 0; i < rts_num; i++)
+  {
+    span = rts[i];
+
+    if ((span / ss) < cfg->rrarows)
+      span = ss * cfg->rrarows;
+
+    if (cdp_len == 0)
+      cdp_len = 1;
+    else
+      cdp_len = (int) floor (((double) span)
+          / ((double) (cfg->rrarows * ss)));
+
+    cdp_num = (int) ceil (((double) span)
+        / ((double) (cdp_len * ss)));
+
+    for (j = 0; j < rra_types_num; j++)
+    {
+      int status;
+
+      if (rra_num >= rra_max)
+        break;
+
+      status = ssnprintf (buffer, sizeof (buffer), "RRA:%s:%3.1f:%u:%u",
+          rra_types[j], cfg->xff, cdp_len, cdp_num);
+
+      if ((status < 0) || ((size_t) status >= sizeof (buffer)))
+      {
+        ERROR ("rra_get: Buffer would have been truncated.");
+        continue;
+      }
+
+      rra_def[rra_num++] = sstrdup (buffer);
+    }
+  }
+
+  *ret = rra_def;
+  return (rra_num);
+} /* }}} int rra_get */
+
+static void ds_free (int ds_num, char **ds_def) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ds_num; i++)
+    if (ds_def[i] != NULL)
+      free (ds_def[i]);
+  free (ds_def);
+} /* }}} void ds_free */
+
+static int ds_get (char ***ret, /* {{{ */
+    const data_set_t *ds, const value_list_t *vl,
+    const rrdcreate_config_t *cfg)
+{
+  char **ds_def;
+  int ds_num;
+
+  char min[32];
+  char max[32];
+  char buffer[128];
+
+  ds_def = (char **) malloc (ds->ds_num * sizeof (char *));
+  if (ds_def == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("rrdtool plugin: malloc failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+  memset (ds_def, '\0', ds->ds_num * sizeof (char *));
+
+  for (ds_num = 0; ds_num < ds->ds_num; ds_num++)
+  {
+    data_source_t *d = ds->ds + ds_num;
+    char *type;
+    int status;
+
+    ds_def[ds_num] = NULL;
+
+    if (d->type == DS_TYPE_COUNTER)
+      type = "COUNTER";
+    else if (d->type == DS_TYPE_GAUGE)
+      type = "GAUGE";
+    else
+    {
+      ERROR ("rrdtool plugin: Unknown DS type: %i",
+          d->type);
+      break;
+    }
+
+    if (isnan (d->min))
+    {
+      sstrncpy (min, "U", sizeof (min));
+    }
+    else
+      ssnprintf (min, sizeof (min), "%lf", d->min);
+
+    if (isnan (d->max))
+    {
+      sstrncpy (max, "U", sizeof (max));
+    }
+    else
+      ssnprintf (max, sizeof (max), "%lf", d->max);
+
+    status = ssnprintf (buffer, sizeof (buffer),
+        "DS:%s:%s:%i:%s:%s",
+        d->name, type,
+        (cfg->heartbeat > 0) ? cfg->heartbeat : (2 * vl->interval),
+        min, max);
+    if ((status < 1) || ((size_t) status >= sizeof (buffer)))
+      break;
+
+    ds_def[ds_num] = sstrdup (buffer);
+  } /* for ds_num = 0 .. ds->ds_num */
+
+  if (ds_num != ds->ds_num)
+  {
+    ds_free (ds_num, ds_def);
+    return (-1);
+  }
+
+  *ret = ds_def;
+  return (ds_num);
+} /* }}} int ds_get */
+
+#if HAVE_THREADSAFE_LIBRRD
+static int srrd_create (const char *filename, /* {{{ */
+    unsigned long pdp_step, time_t last_up,
+    int argc, const char **argv)
+{
+  int status;
+  char *filename_copy;
+
+  if ((filename == NULL) || (argv == NULL))
+    return (-EINVAL);
+
+  /* Some versions of librrd don't have the `const' qualifier for the first
+   * argument, so we have to copy the pointer here to avoid warnings. It sucks,
+   * but what else can we do? :(  -octo */
+  filename_copy = strdup (filename);
+  if (filename_copy == NULL)
+  {
+    ERROR ("srrd_create: strdup failed.");
+    return (-ENOMEM);
+  }
+
+  optind = 0; /* bug in librrd? */
+  rrd_clear_error ();
+
+  status = rrd_create_r (filename_copy, pdp_step, last_up,
+      argc, (void *) argv);
+
+  if (status != 0)
+  {
+    WARNING ("rrdtool plugin: rrd_create_r (%s) failed: %s",
+        filename, rrd_get_error ());
+  }
+
+  sfree (filename_copy);
+
+  return (status);
+} /* }}} int srrd_create */
+/* #endif HAVE_THREADSAFE_LIBRRD */
+
+#else /* !HAVE_THREADSAFE_LIBRRD */
+static int srrd_create (const char *filename, /* {{{ */
+    unsigned long pdp_step, time_t last_up,
+    int argc, const char **argv)
+{
+  int status;
+
+  int new_argc;
+  char **new_argv;
+
+  char pdp_step_str[16];
+  char last_up_str[16];
+
+  new_argc = 6 + argc;
+  new_argv = (char **) malloc ((new_argc + 1) * sizeof (char *));
+  if (new_argv == NULL)
+  {
+    ERROR ("rrdtool plugin: malloc failed.");
+    return (-1);
+  }
+
+  if (last_up == 0)
+    last_up = time (NULL) - 10;
+
+  ssnprintf (pdp_step_str, sizeof (pdp_step_str), "%lu", pdp_step);
+  ssnprintf (last_up_str, sizeof (last_up_str), "%u", (unsigned int) last_up);
+
+  new_argv[0] = "create";
+  new_argv[1] = (void *) filename;
+  new_argv[2] = "-s";
+  new_argv[3] = pdp_step_str;
+  new_argv[4] = "-b";
+  new_argv[5] = last_up_str;
+
+  memcpy (new_argv + 6, argv, argc * sizeof (char *));
+  new_argv[new_argc] = NULL;
+
+  pthread_mutex_lock (&librrd_lock);
+  optind = 0; /* bug in librrd? */
+  rrd_clear_error ();
+
+  status = rrd_create (new_argc, new_argv);
+  pthread_mutex_unlock (&librrd_lock);
+
+  if (status != 0)
+  {
+    WARNING ("rrdtool plugin: rrd_create (%s) failed: %s",
+        filename, rrd_get_error ());
+  }
+
+  sfree (new_argv);
+
+  return (status);
+} /* }}} int srrd_create */
+#endif /* !HAVE_THREADSAFE_LIBRRD */
+
+/*
+ * Public functions
+ */
+int cu_rrd_create_file (const char *filename, /* {{{ */
+    const data_set_t *ds, const value_list_t *vl,
+    const rrdcreate_config_t *cfg)
+{
+  char **argv;
+  int argc;
+  char **rra_def;
+  int rra_num;
+  char **ds_def;
+  int ds_num;
+  int status = 0;
+
+  if (check_create_dir (filename))
+    return (-1);
+
+  if ((rra_num = rra_get (&rra_def, vl, cfg)) < 1)
+  {
+    ERROR ("cu_rrd_create_file failed: Could not calculate RRAs");
+    return (-1);
+  }
+
+  if ((ds_num = ds_get (&ds_def, ds, vl, cfg)) < 1)
+  {
+    ERROR ("cu_rrd_create_file failed: Could not calculate DSes");
+    return (-1);
+  }
+
+  argc = ds_num + rra_num;
+
+  if ((argv = (char **) malloc (sizeof (char *) * (argc + 1))) == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("cu_rrd_create_file failed: %s",
+        sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  memcpy (argv, ds_def, ds_num * sizeof (char *));
+  memcpy (argv + ds_num, rra_def, rra_num * sizeof (char *));
+  argv[ds_num + rra_num] = NULL;
+
+  assert (vl->time > 10);
+  status = srrd_create (filename,
+      (cfg->stepsize > 0) ? cfg->stepsize : vl->interval,
+      vl->time - 10,
+      argc, (const char **) argv);
+
+  free (argv);
+  ds_free (ds_num, ds_def);
+  rra_free (rra_num, rra_def);
+
+  if (status != 0)
+  {
+    WARNING ("cu_rrd_create_file: srrd_create (%s) returned status %i.",
+        filename, status);
+  }
+  else
+  {
+    DEBUG ("cu_rrd_create_file: Successfully created RRD file \"%s\".",
+        filename);
+  }
+
+  return (status);
+} /* }}} int cu_rrd_create_file */
+
+/* vim: set sw=2 sts=2 et fdm=marker : */
diff --git a/src/utils_rrdcreate.h b/src/utils_rrdcreate.h
new file mode 100644 (file)
index 0000000..935e4e0
--- /dev/null
@@ -0,0 +1,50 @@
+/**
+ * collectd - src/utils_rrdcreate.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_RRDCREATE_H
+#define UTILS_RRDCREATE_H 1
+
+#include "plugin.h"
+
+#include <stddef.h>
+
+struct rrdcreate_config_s
+{
+  int    stepsize;
+  int    heartbeat;
+  int    rrarows;
+  double xff;
+
+  int *timespans;
+  size_t timespans_num;
+
+  char **consolidation_functions;
+  size_t consolidation_functions_num;
+};
+typedef struct rrdcreate_config_s rrdcreate_config_t;
+
+int cu_rrd_create_file (const char *filename,
+    const data_set_t *ds, const value_list_t *vl,
+    const rrdcreate_config_t *cfg);
+
+#endif /* UTILS_RRDCREATE_H */
+
+/* vim: set sw=2 sts=2 et : */
diff --git a/src/utils_subst.c b/src/utils_subst.c
new file mode 100644 (file)
index 0000000..a49f6db
--- /dev/null
@@ -0,0 +1,144 @@
+/**
+ * collectd - src/utils_subst.c
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module provides functions for string substitution.
+ */
+
+#include "collectd.h"
+#include "common.h"
+
+char *subst (char *buf, size_t buflen, const char *string, int off1, int off2,
+               const char *replacement)
+{
+       char  *buf_ptr = buf;
+       size_t len     = buflen;
+
+       if ((NULL == buf) || (0 >= buflen) || (NULL == string)
+                       || (0 > off1) || (0 > off2) || (off1 > off2)
+                       || (NULL == replacement))
+               return NULL;
+
+       sstrncpy (buf_ptr, string,
+                       ((size_t)off1 + 1 > buflen) ? buflen : (size_t)off1 + 1);
+       buf_ptr += off1;
+       len     -= off1;
+
+       if (0 >= len)
+               return buf;
+
+       sstrncpy (buf_ptr, replacement, len);
+       buf_ptr += strlen (replacement);
+       len     -= strlen (replacement);
+
+       if (0 >= len)
+               return buf;
+
+       sstrncpy (buf_ptr, string + off2, len);
+       return buf;
+} /* subst */
+
+char *asubst (const char *string, int off1, int off2, const char *replacement)
+{
+       char *buf;
+       int   len;
+
+       char *ret;
+
+       if ((NULL == string) || (0 > off1) || (0 > off2) || (off1 > off2)
+                       || (NULL ==replacement))
+               return NULL;
+
+       len = off1 + strlen (replacement) + strlen (string) - off2 + 1;
+
+       buf = (char *)malloc (len);
+       if (NULL == buf)
+               return NULL;
+
+       ret = subst (buf, len, string, off1, off2, replacement);
+       if (NULL == ret)
+               free (buf);
+       return ret;
+} /* asubst */
+
+char *subst_string (char *buf, size_t buflen, const char *string,
+               const char *needle, const char *replacement)
+{
+       char *temp;
+       size_t needle_len;
+       size_t i;
+
+       if ((buf == NULL) || (string == NULL)
+                       || (needle == NULL) || (replacement == NULL))
+               return (NULL);
+
+       temp = (char *) malloc (buflen);
+       if (temp == NULL)
+       {
+               ERROR ("subst_string: malloc failed.");
+               return (NULL);
+       }
+
+       needle_len = strlen (needle);
+       strncpy (buf, string, buflen);
+
+       /* Limit the loop to prevent endless loops. */
+       for (i = 0; i < buflen; i++)
+       {
+               char *begin_ptr;
+               size_t begin;
+
+               /* Find `needle' in `buf'. */
+               begin_ptr = strstr (buf, needle);
+               if (begin_ptr == NULL)
+                       break;
+
+               /* Calculate the start offset. */
+               begin = begin_ptr - buf;
+
+               /* Substitute the region using `subst'. The result is stored in
+                * `temp'. */
+               begin_ptr = subst (temp, buflen, buf,
+                               begin, begin + needle_len,
+                               replacement);
+               if (begin_ptr == NULL)
+               {
+                       WARNING ("subst_string: subst failed.");
+                       break;
+               }
+
+               /* Copy the new string in `temp' to `buf' for the next round. */
+               strncpy (buf, temp, buflen);
+       }
+
+       if (i >= buflen)
+       {
+               WARNING ("subst_string: Loop exited after %zu iterations: "
+                               "string = %s; needle = %s; replacement = %s;",
+                               i, string, needle, replacement);
+       }
+
+       sfree (temp);
+       return (buf);
+} /* char *subst_string */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_subst.h b/src/utils_subst.h
new file mode 100644 (file)
index 0000000..4387b85
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * collectd - src/utils_subst.h
+ * Copyright (C) 2008  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Authors:
+ *   Sebastian "tokkee" Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This module provides functions for string substitution.
+ */
+
+#ifndef UTILS_SUBST_H
+#define UTILS_SUBST_H 1
+
+#include <stddef.h>
+
+/*
+ * subst:
+ *
+ * Replace a substring of a string with the specified replacement text. The
+ * resulting string is stored in the buffer pointed to by 'buf' of length
+ * 'buflen'. Upon success, the buffer will always be null-terminated. The
+ * result may be truncated if the buffer is too small.
+ *
+ * The substring to be replaces is identified by the two offsets 'off1' and
+ * 'off2' where 'off1' specifies the offset to the beginning of the substring
+ * and 'off2' specifies the offset to the first byte after the substring.
+ *
+ * The minimum buffer size to store the complete return value (including the
+ * terminating '\0' character) thus has to be:
+ * off1 + strlen(replacement) + strlen(string) - off2 + 1
+ *
+ * Example:
+ *
+ *             01234567890
+ *   string = "foo_____bar"
+ *                ^    ^
+ *                |    |
+ *              off1  off2
+ *
+ *   off1 = 3
+ *   off2 = 8
+ *
+ *   replacement = " - "
+ *
+ *   -> "foo - bar"
+ *
+ * The function returns 'buf' on success, NULL else.
+ */
+char *subst (char *buf, size_t buflen, const char *string, int off1, int off2,
+               const char *replacement);
+
+/*
+ * asubst:
+ *
+ * This function is very similar to subst(). It differs in that it
+ * automatically allocates the memory required for the return value which the
+ * user then has to free himself.
+ *
+ * Returns the newly allocated result string on success, NULL else.
+ */
+char *asubst (const char *string, int off1, int off2, const char *replacement);
+
+/*
+ * subst_string:
+ *
+ * Works like `subst', but instead of specifying start and end offsets you
+ * specify `needle', the string that is to be replaced. If `needle' is found
+ * in `string' (using strstr(3)), the offset is calculated and `subst' is
+ * called with the determined parameters.
+ *
+ * If the substring is not found, no error will be indicated and
+ * `subst_string' works mostly like `strncpy'.
+ *
+ * If the substring appears multiple times, all appearances will be replaced.
+ * If the substring has been found `buflen' times, an endless loop is assumed
+ * and the loop is broken. A warning is printed and the function returns
+ * success.
+ */
+char *subst_string (char *buf, size_t buflen, const char *string,
+               const char *needle, const char *replacement);
+
+#endif /* UTILS_SUBST_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
index f518b1c..22c7917 100644 (file)
@@ -80,7 +80,6 @@ static int simple_submit_match (cu_match_t *match, void *user_data)
 
   vl.values = values;
   vl.values_len = 1;
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, data->plugin, sizeof (vl.plugin));
   sstrncpy (vl.plugin_instance, data->plugin_instance,
@@ -100,10 +99,11 @@ static int simple_submit_match (cu_match_t *match, void *user_data)
   return (0);
 } /* int simple_submit_match */
 
-static int tail_callback (void *data, char *buf, int buflen)
+static int tail_callback (void *data, char *buf,
+    int __attribute__((unused)) buflen)
 {
   cu_tail_match_t *obj = (cu_tail_match_t *) data;
-  int i;
+  size_t i;
 
   for (i = 0; i < obj->matches_num; i++)
     match_apply (obj->matches[i].match, buf);
@@ -135,7 +135,7 @@ cu_tail_match_t *tail_match_create (const char *filename)
 
 void tail_match_destroy (cu_tail_match_t *obj)
 {
-  int i;
+  size_t i;
 
   if (obj == NULL)
     return;
@@ -237,7 +237,7 @@ int tail_match_read (cu_tail_match_t *obj)
 {
   char buffer[4096];
   int status;
-  int i;
+  size_t i;
 
   status = cu_tail_read (obj->tail, buffer, sizeof (buffer), tail_callback,
       (void *) obj);
index 68b85a3..03a3f2d 100644 (file)
@@ -674,7 +674,7 @@ static int ut_report_state (const data_set_t *ds,
 
   plugin_dispatch_notification (&n);
 
-  plugin_notification_meta_free (&n);
+  plugin_notification_meta_free (n.meta);
   return (0);
 } /* }}} int ut_report_state */
 
@@ -689,7 +689,7 @@ static int ut_report_state (const data_set_t *ds,
  * Does not fail.
  */
 static int ut_check_one_data_source (const data_set_t *ds,
-    const value_list_t *vl,
+    const value_list_t __attribute__((unused)) *vl,
     const threshold_t *th,
     const gauge_t *values,
     int ds_index)
@@ -833,8 +833,18 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
   return (0);
 } /* }}} int ut_check_threshold */
 
+/*
+ * int ut_check_interesting (PUBLIC)
+ *
+ * Given an identification returns
+ * 0: No threshold is defined.
+ * 1: A threshold has been found. The flag `persist' is off.
+ * 2: A threshold has been found. The flag `persist' is on.
+ *    (That is, it is expected that many notifications are sent until the
+ *    problem disappears.)
+ */
 int ut_check_interesting (const char *name)
-{
+{ /* {{{ */
   char *name_copy = NULL;
   char *host = NULL;
   char *plugin = NULL;
@@ -887,6 +897,6 @@ int ut_check_interesting (const char *name)
   if ((th->flags & UT_FLAG_PERSIST) == 0)
     return (1);
   return (2);
-} /* int ut_check_interesting */
+} /* }}} int ut_check_interesting */
 
 /* vim: set sw=2 ts=8 sts=2 tw=78 fdm=marker : */
index 25f7c70..6775d20 100644 (file)
@@ -45,7 +45,6 @@ static void submit (const char *plugin_instance, const char *type,
   vl.values = values;
   vl.values_len = values_len;
 
-  vl.time = time (NULL);
   sstrncpy (vl.host, hostname_g, sizeof (vl.host));
   sstrncpy (vl.plugin, "vmem", sizeof (vl.plugin));
   if (plugin_instance != NULL)
index f295e49..1615ca0 100644 (file)
@@ -58,7 +58,6 @@ static void traffic_submit (const char *plugin_instance,
 
        vl.values = values;
        vl.values_len = STATIC_ARRAY_SIZE (values);
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "vserver", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
@@ -80,7 +79,6 @@ static void load_submit (const char *plugin_instance,
 
        vl.values = values;
        vl.values_len = STATIC_ARRAY_SIZE (values);
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "vserver", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
@@ -100,7 +98,6 @@ static void submit_gauge (const char *plugin_instance, const char *type,
 
        vl.values = values;
        vl.values_len = STATIC_ARRAY_SIZE (values);
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "vserver", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
@@ -144,7 +141,7 @@ static int vserver_read (void)
 
        while (42)
        {
-               size_t len;
+               int len;
                char file[BUFSIZE];
 
                FILE *fh;
@@ -173,7 +170,7 @@ static int vserver_read (void)
                if (dent->d_name[0] == '.')
                        continue;
 
-               len = snprintf (file, sizeof (file), PROCDIR "/%s", dent->d_name);
+               len = ssnprintf (file, sizeof (file), PROCDIR "/%s", dent->d_name);
                if ((len < 0) || (len >= BUFSIZE))
                        continue;
                
@@ -192,7 +189,7 @@ static int vserver_read (void)
                /* socket message accounting */
                len = ssnprintf (file, sizeof (file),
                                PROCDIR "/%s/cacct", dent->d_name);
-               if ((len < 0) || (len >= sizeof (file)))
+               if ((len < 0) || ((size_t) len >= sizeof (file)))
                        continue;
 
                if (NULL == (fh = fopen (file, "r")))
@@ -240,7 +237,7 @@ static int vserver_read (void)
                /* thread information and load */
                len = ssnprintf (file, sizeof (file),
                                PROCDIR "/%s/cvirt", dent->d_name);
-               if ((len < 0) || (len >= sizeof (file)))
+               if ((len < 0) || ((size_t) len >= sizeof (file)))
                        continue;
 
                if (NULL == (fh = fopen (file, "r")))
@@ -293,7 +290,7 @@ static int vserver_read (void)
                /* processes and memory usage */
                len = ssnprintf (file, sizeof (file),
                                PROCDIR "/%s/limit", dent->d_name);
-               if ((len < 0) || (len >= sizeof (file)))
+               if ((len < 0) || ((size_t) len >= sizeof (file)))
                        continue;
 
                if (NULL == (fh = fopen (file, "r")))
index 21bbcb4..f7ba735 100644 (file)
@@ -55,7 +55,6 @@ static void wireless_submit (const char *plugin_instance, const char *type,
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "wireless", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, plugin_instance,
index 09786fc..52beb65 100644 (file)
@@ -36,7 +36,6 @@ static void cxmms_submit (const char *type, gauge_t value)
 
        vl.values = values;
        vl.values_len = 1;
-       vl.time = time (NULL);
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "xmms", sizeof (vl.plugin));
        sstrncpy (vl.type, type, sizeof (vl.type));
index 0501df9..a1dcaa7 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEFAULT_VERSION="4.5.3.git"
+DEFAULT_VERSION="4.6.2.git"
 
 VERSION="$( git describe 2> /dev/null | sed -e 's/^collectd-//' )"