Merge branch 'master' into ff/rrdcached
authorFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sun, 14 Sep 2008 14:25:08 +0000 (16:25 +0200)
committerFlorian Forster <octo@leeloo.lan.home.verplant.org>
Sun, 14 Sep 2008 14:25:08 +0000 (16:25 +0200)
Conflicts:

src/rrdtool.c

72 files changed:
.gitignore
AUTHORS
ChangeLog
README
TODO
bindings/perl/Collectd.pm
bindings/perl/Collectd/Unixsock.pm
configure.in
contrib/collection.cgi
contrib/collection3/bin/graph.cgi
contrib/collection3/bin/index.cgi
contrib/collection3/bin/json.cgi
contrib/collection3/etc/collection.conf [new file with mode: 0644]
contrib/collection3/etc/collection3.conf [deleted file]
contrib/collection3/lib/Collectd/Config.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Common.pm
contrib/collection3/lib/Collectd/Graph/Config.pm [new file with mode: 0644]
contrib/collection3/lib/Collectd/Graph/Type.pm
contrib/collection3/lib/Collectd/Graph/Type/GenericStacked.pm
contrib/collection3/lib/Collectd/Graph/Type/Load.pm
contrib/collection3/lib/Collectd/Graph/TypeLoader.pm
contrib/collection3/share/navigate.js [new file with mode: 0644]
contrib/collection3/share/style.css
contrib/examples/myplugin.c
contrib/snmp-probe-host.px [new file with mode: 0755]
src/Makefile.am
src/apple_sensors.c
src/battery.c
src/collectd-exec.pod
src/collectd-perl.pod
src/collectd-unixsock.pod
src/collectd.c
src/collectd.conf.in
src/collectd.conf.pod
src/collectd.h
src/collectd.pod
src/common.c
src/common.h
src/exec.c
src/filecount.c [new file with mode: 0644]
src/ipmi.c
src/memcached.c
src/memory.c
src/onewire.c
src/perl.c
src/plugin.c
src/plugin.h
src/postgresql.c
src/postgresql_default.conf [new file with mode: 0644]
src/processes.c
src/qmail.c [deleted file]
src/rrdtool.c
src/snmp.c
src/swap.c
src/tcpconns.c
src/thermal.c
src/types.db
src/unixsock.c
src/utils_cmd_flush.c
src/utils_cmd_flush.h
src/utils_cmd_getval.c
src/utils_cmd_getval.h
src/utils_cmd_listval.c
src/utils_cmd_listval.h
src/utils_cmd_putnotif.c
src/utils_cmd_putnotif.h
src/utils_cmd_putval.c
src/utils_cmd_putval.h
src/utils_parse_option.c [new file with mode: 0644]
src/utils_parse_option.h [new file with mode: 0644]
src/utils_threshold.c
version-gen.sh

index 5d4fbca..03bd0c4 100644 (file)
@@ -44,6 +44,8 @@ src/*.la
 src/*.lo
 src/*.o
 src/collectd
+src/collectd-nagios
+src/collectdmon
 src/collectd*.1
 src/collectd*.5
 src/types.db.5
@@ -64,6 +66,12 @@ src/liboconfig/parser.c
 src/liboconfig/parser.h
 src/liboconfig/scanner.c
 
+# perl stuff:
+bindings/.perl-directory-stamp
+bindings/perl/Collectd/pm_to_blib
+bindings/perl/blib/
+bindings/perl/pm_to_blib
+
 # make dist stuff:
 collectd-*.tar.gz
 collectd-*.tar.bz2
diff --git a/AUTHORS b/AUTHORS
index 7f92d55..888015e 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,82 +1,98 @@
-This package was written by:
-  Florian octo Forster <octo at verplant.org>
+Permanent project members
+=========================
 
-apcups plugin by:
-  Anthony Gialluca <tonyabg at charter.net>
+Florian "octo" Forster <octo at verplant.org>
+ - Initial author.
 
-cpufreq, multimeter and irq plugin, as well as some bugfixes in the exec plugin
-by:
-  Peter Holik <peter at holik.at>
+Sebastian "tokkee" Harl <sh at tokkee.org>
+ - Bugfixed and enhancments in many places all around the project.
+ - perl plugin.
+ - users plugin.
+ - vserver plugin.
+ - Debian package.
 
-hddtemp plugin by:
-  Vincent Stehlé <vincent.stehle at free.fr>
 
-iptables plugin by:
-  Sjoerd van der Berg <harekiet at gmail.com>
+Contributors (sorted alphabetically)
+====================================
 
-libvirt plugin by:
-  Richard W. M. Jones <rjones at redhat.com>
+Alessandro Iurlano <alessandro.iurlano at gmail.com>
+ - Initial filecount plugin.
 
-mbmon plugin by:
-  Flavio Stanchina <flavio at stanchina.net>
+Alvaro Barcellos <alvaro.barcellos at gmail.com>
+ - Don't-fork patch.
 
-memcached plugin by:
-  Antony Dovgal <tony at daylessday.org>
+Anthony Gialluca <tonyabg at charter.net>
+ - apcups plugin.
 
-nfs plugin by:
-  Jason Pepas <cell at ices.utexas.edu>
+Antony Dovgal <tony at daylessday.org>
+ - memcached plugin.
 
-perl plugin by:
-  Sebastian Harl <sh at tokkee.org>
+Christophe Kalt <collectd at klb.taranis.org>
+ - The version 3 `log' mode.
+ - Many Solaris related hints and fixes.
 
-powerdns plugin and initial `tail' subsystem by:
-  Luke Herberling <collectd at c-ware.com>
+Dan Berrange <berrange at redhat.com>
+ - uuid plugin.
 
-processes plugin by:
-  Lyonel Vincent <lyonel at ezix.org>
+David Bacher <drbacher at gmail.com>
+ - serial plugin.
 
-sensors plugin has been improved by:
-  Luboš Staněk <kolektor at atlas.cz>
+Flavio Stanchina <flavio at stanchina.net>
+ - mbmon plugin.
 
-serial plugin by:
-  David Bacher <drbacher at gmail.com>
+Jason Pepas <cell at ices.utexas.edu>
+ - nfs plugin.
 
-tape plugin by:
-  Scott Garrett <sgarrett at technomancer.com>
+Luboš Staněk <kolektor at atlas.cz>
+ - sensors plugin improvements.
+ - Time and effort to find a nasty bug in the ntpd-plugin.
 
-teamspeak2 plugin by:
-  Stefan Hacker <stefan.hacker at web.de>
+Luke Herberling <collectd at c-ware.com>
+ - powerdns plugin.
+ - Initial `tail' subsystem by:
 
-users plugin by:
-  Sebastian Harl <sh at tokkee.org>
+Lyonel Vincent <lyonel at ezix.org>
+ - processes plugin.
 
-uuid plugin by:
-  Dan Berrange <berrange at redhat.com>
-  Richard W.M. Jones <rjones at redhat.com>
+Michael Stapelberg <michael+git@stapelberg.de>
+ - OpenBSD port of the tcpconns plugin.
 
-vserver plugin by:
-  Sebastian Harl <sh at tokkee.org>
+Niki W. Waibel <niki.waibel at newlogic.com>
+ - Initial autotools fixes.
+ - libltdl code.
+ - getmnt-wizardry.
 
-PID-file patch by:
-  Tommie Gannert <d00-tga at d.kth.se>
+Oleg King <king2 at kaluga.ru>
+ - Added support for the statgrab library to
+   + the cpu plugin,
+   + the disk plugin, and
+   + the users plugin.
 
-Don't-fork-patch by:
-  Alvaro Barcellos <alvaro.barcellos at gmail.com>
+Peter Holik <peter at holik.at>
+ - cpufreq plugin.
+ - multimeter plugin.
+ - irq plugin.
+ - Some bugfixes in the exec plugin.
+ - Notifications in the ipmi plugin.
 
-Many autotools related fixes, libltdl code, getmnt-wizardry and much help has
-contributed:
-  Niki W. Waibel <niki.waibel at newlogic.com>
+Richard W. M. Jones <rjones at redhat.com>
+ - libvirt plugin.
+ - uuid plugin.
 
-The `log' mode has been written by:
-  Christophe Kalt <collectd at klb.taranis.org>
+Scott Garrett <sgarrett at technomancer.com>
+ - tape plugin.
 
-Much time and effort to find a nasty bug in the ntpd-plugin has been
-contributed by:
-  Luboš Staněk <lubek at users.sourceforge.net>
+Sjoerd van der Berg <harekiet at gmail.com>
+ - iptables plugin.
 
-Support for the statgrab library has been added to the cpu, disk and users
-plugins by
-  Oleg King <king2 at kaluga.ru>
+Stefan Hacker <stefan.hacker at web.de>
+ - teamspeak2 plugin.
+
+Tommie Gannert <d00-tga at d.kth.se>
+ - PID-file patch.
+
+Vincent Stehlé <vincent.stehle at free.fr>
+ - hddtemp plugin.
 
 collectd is available at:
   <http://collectd.org/>
index 35e8b36..00a04ad 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,41 @@
+2008-09-04, Version 4.5.0
+       * collectd: Added the ability to flush certain identifiers.
+       * collectd: The concept of `notification meta data' has been
+         introduced.
+       * filecount plugin: The new filecount plugin counts the number of
+         files in a directory and its subdirectories.
+       * ipmi plugin: Sensor names have been changed to ensure unique names.
+         Notifications upon added and removed sensors can now be generated.
+       * notify_desktop plugin: This new plugin sends notifications to the
+         X desktop using the structure defined in the `Desktop Notification
+         Specification'.
+       * notify_email plugin: This new plugin sends out notifications via
+         email, using the `esmtp' library.
+       * onewire plugin: The new experimental(!) onewire plugin reads values,
+         such as temperatures, from sensors connected to the computer via the
+         onewire bus.
+       * perl plugin: Improved synchronized access to internal data structures
+         and fixed a possible dead-lock.
+       * perl plugin: Added the ability to flush certain identifiers and marked
+         plugin_flush_all() and plugin_flush_one() as deprecated in favor of
+         plugin_flush().
+       * perl plugin: Added the ability to configure Perl plugins.
+       * postgresql plugin: The new postgresql plugin collects statistics
+         about or from a PostgreSQL database.
+       * processes plugin: The `ProcessMatch' option has been added.
+       * rrdtool plugin: Implement throttling of the `update queue' to lessen
+         IO load.
+       * tcpconns plugin: This plugin has been ported to OpenBSD.
+       * thermal plugin: The new thermal plugin collects system temperatures
+         using Linux ACPI thermal zone data.
+
+2008-09-01, Version 4.4.3
+       * collectd: Fix a memory leak in the threshold checking code.
+       * memcached plugin: Fix a too short timeout and a related file
+         descriptor leak.
+       * memory plugin: A typo in the libstatgrab code has been fixed.
+       * snmp plugin: Fix a possible memory leak.
+
 2008-07-15, Version 4.4.2
        * build system: Use pkg-config to detect the upsclient library.
        * collectd: Try even harder to determine the endianess of the
        * vmem plugin: The new vmem plugin collects very detailed statistics
          about the virtual memory subsystem of Linux.
 
+2008-08-30, Version 4.3.4
+       * Build system: Improved detection of and linking with the statgrab
+         library.
+       * collectd: Portability fixes, especially to determine endianess more
+         reliable.
+       * Various plugins: Fix format strings.
+       * disk plugin: A fix for giving disks under Linux 2.4 the right names
+         again has been applied.
+       * memcached plugin: Fix a too short timeout and a related file
+         descriptor leak.
+       * memory plugin: A typo in the libstatgrab code has been fixed.
+       * network plugin: A fix in the initialization function solves problems
+         under Solaris.
+       * nginx plugin: A thread-unsafe function has been replaced.
+       * vserver plugin: A thread-unsafe function has been replaced.
+       * wireless plugin: A work-around for broken wireless drivers has been
+         added.
+
 2008-04-22, Version 4.3.3
        * build system: Improved detection of several libraries, especially if
          they are in non-standard paths.
diff --git a/README b/README
index f41e720..58ce45d 100644 (file)
--- a/README
+++ b/README
@@ -62,6 +62,9 @@ Features
       Values gathered by a custom program or script.
       See collectd-exec(5).
 
+    - filecount
+      Count the number of files in directories.
+
     - hddtemp
       Harddisk temperatures using hddtempd.
 
@@ -132,8 +135,9 @@ Features
       Network UPS tools: UPS current, voltage, power, charge, utilisation,
       temperature, etc. See upsd(8).
 
-    - onewire
+    - onewire (EXPERIMENTAL!)
       Read onewire sensors using the owcapu library of the owfs project.
+      Please read in collectd.conf(5) why this plugin is experimental.
 
     - perl
       The perl plugin implements a Perl-interpreter into collectd. You can
diff --git a/TODO b/TODO
index 08fcec1..b5cd6b5 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,8 +1,6 @@
-For version 4.4:
-* PowerDNS plugin
-
-For version 4.3:
-* unixsock plugin: Remove the custom cache if possible.
+* Parse options/identifiers with spaces in them correctly.
+* Figure out what to do with the onewire plugin.
+* Custom notification messages?
 
 src/battery.c: commend not working code.
 
index cca349e..738206b 100644 (file)
@@ -56,6 +56,7 @@ our %EXPORT_TAGS = (
                        TYPE_LOG
                        TYPE_NOTIF
                        TYPE_FLUSH
+                       TYPE_CONFIG
                        TYPE_DATASET
        ) ],
        'ds_types' => [ qw(
@@ -98,6 +99,7 @@ our $interval_g;
 Exporter::export_ok_tags ('all');
 
 my @plugins : shared = ();
+my %cf_callbacks : shared = ();
 
 my %types = (
        TYPE_INIT,     "init",
@@ -133,6 +135,8 @@ sub DEBUG   { _log (scalar caller, LOG_DEBUG,   shift); }
 sub plugin_call_all {
        my $type = shift;
 
+       my %plugins;
+
        our $cb_name = undef;
 
        if (! defined $type) {
@@ -148,9 +152,13 @@ sub plugin_call_all {
                return;
        }
 
-       lock @plugins;
-       foreach my $plugin (keys %{$plugins[$type]}) {
-               my $p = $plugins[$type]->{$plugin};
+       {
+               lock %{$plugins[$type]};
+               %plugins = %{$plugins[$type]};
+       }
+
+       foreach my $plugin (keys %plugins) {
+               my $p = $plugins{$plugin};
 
                my $status = 0;
 
@@ -238,7 +246,8 @@ sub plugin_register {
                return;
        }
 
-       if ((! defined $plugins[$type]) && (TYPE_DATASET != $type)) {
+       if ((! defined $plugins[$type]) && (TYPE_DATASET != $type)
+                       && (TYPE_CONFIG != $type)) {
                ERROR ("Collectd::plugin_register: Invalid type \"$type\"");
                return;
        }
@@ -246,6 +255,16 @@ sub plugin_register {
        if ((TYPE_DATASET == $type) && ("ARRAY" eq ref $data)) {
                return plugin_register_data_set ($name, $data);
        }
+       elsif ((TYPE_CONFIG == $type) && (! ref $data)) {
+               my $pkg = scalar caller;
+
+               if ($data !~ m/^$pkg\:\:/) {
+                       $data = $pkg . "::" . $data;
+               }
+
+               lock %cf_callbacks;
+               $cf_callbacks{$name} = $data;
+       }
        elsif ((TYPE_DATASET != $type) && (! ref $data)) {
                my $pkg = scalar caller;
 
@@ -261,7 +280,7 @@ sub plugin_register {
                        cb_name   => $data,
                );
 
-               lock @plugins;
+               lock %{$plugins[$type]};
                $plugins[$type]->{$name} = \%p;
        }
        else {
@@ -285,8 +304,12 @@ sub plugin_unregister {
        if (TYPE_DATASET == $type) {
                return plugin_unregister_data_set ($name);
        }
+       elsif (TYPE_CONFIG == $type) {
+               lock %cf_callbacks;
+               delete $cf_callbacks{$name};
+       }
        elsif (defined $plugins[$type]) {
-               lock @plugins;
+               lock %{$plugins[$type]};
                delete $plugins[$type]->{$name};
        }
        else {
@@ -299,28 +322,101 @@ sub plugin_flush {
        my %args = @_;
 
        my $timeout = -1;
+       my @plugins = ();
+       my @ids     = ();
 
        DEBUG ("Collectd::plugin_flush:"
                . (defined ($args{'timeout'}) ? " timeout = $args{'timeout'}" : "")
-               . (defined ($args{'plugins'}) ? " plugins = $args{'plugins'}" : ""));
+               . (defined ($args{'plugins'}) ? " plugins = $args{'plugins'}" : "")
+               . (defined ($args{'identifiers'})
+                       ? " identifiers = $args{'identifiers'}" : ""));
 
        if (defined ($args{'timeout'}) && ($args{'timeout'} > 0)) {
                $timeout = $args{'timeout'};
        }
 
-       if (! defined $args{'plugins'}) {
-               plugin_flush_all ($timeout);
+       if (defined ($args{'plugins'})) {
+               if ("ARRAY" eq ref ($args{'plugins'})) {
+                       @plugins = @{$args{'plugins'}};
+               }
+               else {
+                       @plugins = ($args{'plugins'});
+               }
        }
        else {
-               if ("ARRAY" eq ref ($args{'plugins'})) {
-                       foreach my $plugin (@{$args{'plugins'}}) {
-                               plugin_flush_one ($timeout, $plugin);
-                       }
+               @plugins = (undef);
+       }
+
+       if (defined ($args{'identifiers'})) {
+               if ("ARRAY" eq ref ($args{'identifiers'})) {
+                       @ids = @{$args{'identifiers'}};
                }
                else {
-                       plugin_flush_one ($timeout, $args{'plugins'});
+                       @ids = ($args{'identifiers'});
                }
        }
+       else {
+               @ids = (undef);
+       }
+
+       foreach my $plugin (@plugins) {
+               foreach my $id (@ids) {
+                       _plugin_flush($plugin, $timeout, $id);
+               }
+       }
+}
+
+sub plugin_flush_one {
+       my $timeout = shift;
+       my $name    = shift;
+
+       WARNING ("Collectd::plugin_flush_one is deprecated - "
+               . "use Collectd::plugin_flush instead.");
+
+       if (! (defined ($timeout) && defined ($name))) {
+               ERROR ("Usage: Collectd::plugin_flush_one(timeout, name)");
+               return;
+       }
+
+       plugin_flush (plugins => $name, timeout => $timeout);
+}
+
+sub plugin_flush_all {
+       my $timeout = shift;
+
+       WARNING ("Collectd::plugin_flush_all is deprecated - "
+               . "use Collectd::plugin_flush instead.");
+
+       if (! defined ($timeout)) {
+               ERROR ("Usage: Collectd::plugin_flush_all(timeout)");
+               return;
+       }
+
+       plugin_flush (timeout => $timeout);
+}
+
+sub _plugin_dispatch_config {
+       my $plugin = shift;
+       my $config = shift;
+
+       our $cb_name = undef;
+
+       if (! (defined ($plugin) && defined ($config))) {
+               return;
+       }
+
+       if (! defined $cf_callbacks{$plugin}) {
+               WARNING ("Found a configuration for the \"$plugin\" plugin, but "
+                       . "the plugin isn't loaded or didn't register "
+                       . "a configuration callback.");
+               return;
+       }
+
+       {
+               lock %cf_callbacks;
+               $cb_name = $cf_callbacks{$plugin};
+       }
+       call_by_name ($config);
 }
 
 1;
index af274a5..eb6e389 100644 (file)
@@ -57,8 +57,19 @@ use Carp (qw(cluck confess));
 use IO::Socket::UNIX;
 use Regexp::Common (qw(number));
 
+our $Debug = 0;
+
 return (1);
 
+sub _debug
+{
+       if (!$Debug)
+       {
+               return;
+       }
+       print @_;
+}
+
 sub _create_socket
 {
        my $path = shift;
@@ -134,6 +145,22 @@ sub _parse_identifier
        return ($ident);
 } # _parse_identifier
 
+sub _escape_argument
+{
+       my $string = shift;
+
+       if ($string =~ m/^\w+$/)
+       {
+               return ("$string");
+       }
+
+       $string =~ s#\\#\\\\#g;
+       $string =~ s#"#\\"#g;
+       $string = "\"$string\"";
+
+       return ($string);
+}
+
 =head1 PUBLIC METHODS
 
 =over 4
@@ -175,7 +202,7 @@ sub getval
        my %args = @_;
 
        my $status;
-       my $fh = $obj->{'sock'} or confess;
+       my $fh = $obj->{'sock'} or confess ('object has no filehandle');
        my $msg;
        my $identifier;
 
@@ -183,13 +210,13 @@ sub getval
 
        $identifier = _create_identifier (\%args) or return;
 
-       $msg = "GETVAL $identifier\n";
-       #print "-> $msg";
-       send ($fh, $msg, 0) or confess ("send: $!");
+       $msg = 'GETVAL ' . _escape_argument ($identifier) . "\n";
+       _debug "-> $msg";
+       print $fh $msg;
 
-       $msg = undef;
-       recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
-       #print "<- $msg";
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
 
        ($status, $msg) = split (' ', $msg, 2);
        if ($status <= 0)
@@ -198,9 +225,12 @@ sub getval
                return;
        }
 
-       for (split (' ', $msg))
+       for (my $i = 0; $i < $status; $i++)
        {
-               my $entry = $_;
+               my $entry = <$fh>;
+               chomp ($entry);
+               _debug "<- $entry\n";
+
                if ($entry =~ m/^(\w+)=NaN$/)
                {
                        $ret->{$1} = undef;
@@ -236,6 +266,13 @@ sub putval
        my $msg;
        my $identifier;
        my $values;
+       my $interval = "";
+
+       if (defined $args{'interval'})
+       {
+               $interval = ' interval='
+               . _escape_argument ($args{'interval'});
+       }
 
        $identifier = _create_identifier (\%args) or return;
        if (!$args{'values'})
@@ -254,12 +291,16 @@ sub putval
                $values = join (':', $time, map { defined ($_) ? $_ : 'U' } (@{$args{'values'}}));
        }
 
-       $msg = "PUTVAL $identifier $values\n";
-       #print "-> $msg";
-       send ($fh, $msg, 0) or confess ("send: $!");
-       $msg = undef;
-       recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
-       #print "<- $msg";
+       $msg = 'PUTVAL '
+       . _escape_argument ($identifier)
+       . $interval
+       . ' ' . _escape_argument ($values) . "\n";
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
 
        ($status, $msg) = split (' ', $msg, 2);
        return (1) if ($status == 0);
@@ -284,10 +325,12 @@ sub listval
        my $status;
        my $fh = $obj->{'sock'} or confess;
 
-       $msg = "LISTVAL\n";
-       send ($fh, $msg, 0) or confess ("send: $!");
+       _debug "LISTVAL\n";
+       print $fh "LISTVAL\n";
 
        $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
        ($status, $msg) = split (' ', $msg, 2);
        if ($status < 0)
        {
@@ -302,6 +345,7 @@ sub listval
 
                $msg = <$fh>;
                chomp ($msg);
+               _debug "<- $msg\n";
 
                ($time, $ident) = split (' ', $msg, 2);
 
@@ -356,7 +400,6 @@ sub putnotif
        my $fh = $obj->{'sock'} or confess;
 
        my $msg; # message sent to the socket
-       my $opt_msg; # message of the notification
        
        if (!$args{'message'})
        {
@@ -382,16 +425,16 @@ sub putnotif
                $args{'time'} = time ();
        }
        
-       $opt_msg = $args{'message'};
-       delete ($args{'message'});
-
        $msg = 'PUTNOTIF '
-       . join (' ', map { $_ . '=' . $args{$_} } (keys %args))
-       . " message=$opt_msg\n";
+       . join (' ', map { $_ . '=' . _escape_argument ($args{$_}) } (keys %args))
+       . "\n";
 
-       send ($fh, $msg, 0) or confess ("send: $!");
-       $msg = undef;
-       recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
 
        ($status, $msg) = split (' ', $msg, 2);
        return (1) if ($status == 0);
@@ -472,15 +515,18 @@ sub flush
                                return;
                        }
 
-                       $msg .= " identifier=$ident_str";
+                       $msg .= ' identifier=' . _escape_argument ($ident_str);
                }
        }
 
        $msg .= "\n";
 
-       send ($fh, $msg, 0) or confess ("send: $!");
-       $msg = undef;
-       recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
+       _debug "-> $msg";
+       print $fh $msg;
+
+       $msg = <$fh>;
+       chomp ($msg);
+       _debug "<- $msg\n";
 
        ($status, $msg) = split (' ', $msg, 2);
        return (1) if ($status == 0);
@@ -489,6 +535,16 @@ sub flush
        return;
 }
 
+sub error
+{
+       my $obj = shift;
+       if ($obj->{'error'})
+       {
+               return ($obj->{'error'});
+       }
+       return;
+}
+
 =item I<$obj>-E<gt>destroy ();
 
 Closes the socket before the object is destroyed. This function is also
index e1d2e57..9c54aba 100644 (file)
@@ -45,11 +45,26 @@ case $host_os in
        *darwin*)
        ac_system="Darwin"
        ;;
+       *openbsd*)
+       ac_system="OpenBSD"
+       ;;
        *)
        ac_system="unknown"
 esac
 AC_MSG_RESULT([$ac_system])
 
+if test "x$ac_system" = "xLinux"
+then
+       AC_ARG_VAR([KERNEL_DIR], [path to Linux kernel sources])
+       if test -z "$KERNEL_DIR"
+       then
+               KERNEL_DIR="/lib/modules/`uname -r`/source"
+       fi
+
+       KERNEL_CFLAGS="-I$KERNEL_DIR/include"
+       AC_SUBST(KERNEL_CFLAGS)
+fi
+
 #
 # Checks for header files.
 #
@@ -278,8 +293,13 @@ have_net_ip_vs_h="no"
 have_ip_vs_h="no"
 if test "x$ac_system" = "xLinux"
 then
+       SAVE_CFLAGS=$CFLAGS
+       CFLAGS="$CFLAGS $KERNEL_CFLAGS"
+
        AC_CHECK_HEADERS(net/ip_vs.h, [have_net_ip_vs_h="yes"])
        AC_CHECK_HEADERS(ip_vs.h, [have_ip_vs_h="yes"])
+
+       CFLAGS=$SAVE_CFLAGS
 fi
 
 # For quota module
@@ -1223,12 +1243,33 @@ then
   AC_SUBST(BUILD_WITH_LIBSTATGRAB_LDFLAGS)
 fi
 
-AC_CHECK_LIB(kvm, kvm_getswapinfo, [with_libkvm="yes"], [with_libkvm="no"])
-if test "x$with_libkvm" = "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, 1, [Define to 1 if you have the 'kvm' library (-lkvm)])
+       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, test "x$with_libkvm" = "xyes")
+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")
 
 with_sensors_cflags=""
 with_sensors_ldflags=""
@@ -1583,7 +1624,11 @@ AM_CONDITIONAL(BUILD_WITH_LIBESMTP, test "x$with_libesmtp" = "xyes")
 perl_interpreter="perl"
 AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to libperl.])],
 [
-       if test "x$withval" != "xno" && test "x$withval" != "xyes"
+       if test -x "$withval"
+       then
+               perl_interpreter="$withval"
+               with_libperl="yes"
+       else if test "x$withval" != "xno" && test "x$withval" != "xyes"
        then
                LDFLAGS="$LDFLAGS -L$withval/lib"
                CPPFLAGS="$CPPFLAGS -I$withval/include"
@@ -1591,7 +1636,7 @@ AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to l
                with_libperl="yes"
        else
                with_libperl="$withval"
-       fi
+       fi; fi
 ],
 [
        with_libperl="yes"
@@ -1601,7 +1646,7 @@ AC_MSG_CHECKING([for perl])
 perl_interpreter=`which "$perl_interpreter" 2> /dev/null`
 if test -x "$perl_interpreter"
 then
-       AC_MSG_RESULT([yes])
+       AC_MSG_RESULT([yes ($perl_interpreter)])
 else
        perl_interpreter=""
        AC_MSG_RESULT([no])
@@ -1624,13 +1669,14 @@ then
     AC_LINK_IFELSE(
       AC_LANG_PROGRAM(
       [[
+#define PERL_NO_GET_CONTEXT
 #include <EXTERN.h>
 #include <perl.h>
 #include <XSUB.h>
       ]],
       [[
-       PerlInterpreter *perl = NULL;
-       Perl_load_module (perl, PERL_LOADMOD_NOIMPORT,
+       dTHX;
+       load_module (PERL_LOADMOD_NOIMPORT,
                         newSVpv ("Collectd::Plugin::FooBar", 24),
                         Nullsv);
       ]]),
@@ -1691,15 +1737,6 @@ then
        LDFLAGS=$SAVE_LDFLAGS
 fi
 
-if test "x$ac_system" = "xLinux"
-then
-       AC_ARG_VAR([KERNEL_DIR], [path to Linux kernel sources])
-       if test -z "$KERNEL_DIR"
-       then
-               KERNEL_DIR="/lib/modules/`uname -r`/source"
-       fi
-fi
-
 with_own_libiptc="no"
 AC_ARG_WITH(libiptc, [AS_HELP_STRING([--with-libiptc@<:@=PREFIX@:>@], [Path to libiptc.])],
 [
@@ -1745,7 +1782,7 @@ fi
 if test "x$with_libiptc" = "xyes"
 then
        SAVE_CFLAGS=$CFLAGS
-       CFLAGS="$CFLAGS -I$KERNEL_DIR/include"
+       CFLAGS="$CFLAGS $KERNEL_CFLAGS"
 
        AC_CHECK_HEADERS(linux/netfilter_ipv4/ip_tables.h linux/netfilter_ipv6/ip6_tables.h, [],
        [
@@ -2520,6 +2557,11 @@ then
        fi
 fi
 
+if test "x$ac_system" = "xOpenBSD"
+then
+       plugin_tcpconns="yes"
+fi
+
 # Mac OS X devices
 if test "x$with_libiokit" = "xyes"
 then
@@ -2616,12 +2658,21 @@ then
        plugin_processes="yes"
 fi
 
-if test "x$with_libkvm" = "xyes"
+if test "x$with_kvm_getprocs" = "xyes"
 then
        plugin_processes="yes"
+fi
+
+if test "x$with_kvm_getswapinfo" = "xyes"
+then
        plugin_swap="yes"
 fi
 
+if test "x$with_kvm_nlist" = "xyes"
+then
+       plugin_tcpconns="yes"
+fi
+
 if test "x$have_getutent" = "xyes"
 then
        plugin_users="yes"
@@ -2651,6 +2702,7 @@ AC_PLUGIN([dns],         [$with_libpcap],      [DNS traffic analysis])
 AC_PLUGIN([email],       [yes],                [EMail statistics])
 AC_PLUGIN([entropy],     [$plugin_entropy],    [Entropy statistics])
 AC_PLUGIN([exec],        [yes],                [Execution of external programs])
+AC_PLUGIN([filecount],   [yes],                [Count files in directories])
 AC_PLUGIN([hddtemp],     [yes],                [Query hddtempd])
 AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
 AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
@@ -2678,7 +2730,6 @@ 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([qmail],       [yes],                [QMail queue statistics])
 AC_PLUGIN([rrdtool],     [$with_rrdtool],      [RRDTool output plugin])
 AC_PLUGIN([rrdcached],   [$librrd_rrdc_update], [RRDTool output plugin])
 AC_PLUGIN([sensors],     [$with_lm_sensors],   [lm_sensors statistics])
@@ -2819,6 +2870,7 @@ Configuration:
     email . . . . . . . . $enable_email
     entropy . . . . . . . $enable_entropy
     exec  . . . . . . . . $enable_exec
+    filecount . . . . . . $enable_filecount
     hddtemp . . . . . . . $enable_hddtemp
     interface . . . . . . $enable_interface
     iptables  . . . . . . $enable_iptables
@@ -2847,7 +2899,6 @@ Configuration:
     postgresql  . . . . . $enable_postgresql
     powerdns  . . . . . . $enable_powerdns
     processes . . . . . . $enable_processes
-    qmail . . . . . . . . $enable_qmail
     rrdtool . . . . . . . $enable_rrdtool
     rrdcached . . . . . . $enable_rrdcached
     sensors . . . . . . . $enable_sensors
index cab7543..d3e5ccf 100755 (executable)
@@ -1963,6 +1963,16 @@ sub load_graph_definitions
     'GPRINT:pg_blks_avg:AVERAGE:%4.1lf%s Avg,',
     'GPRINT:pg_blks_max:MAX:%4.1lf%s Max,',
     'GPRINT:pg_blks_avg:LAST:%4.1lf%s Last'],
+    pg_db_size => ['DEF:pg_db_size_avg={file}:value:AVERAGE',
+    'DEF:pg_db_size_min={file}:value:MIN',
+    'DEF:pg_db_size_max={file}:value:MAX',
+    "AREA:pg_db_size_max#$HalfBlue",
+    "AREA:pg_db_size_min#$Canvas",
+    "LINE1:pg_db_size_avg#$FullBlue:Bytes",
+    'GPRINT:pg_db_size_min:MIN:%4.1lf%s Min,',
+    'GPRINT:pg_db_size_avg:AVERAGE:%4.1lf%s Avg,',
+    'GPRINT:pg_db_size_max:MAX:%4.1lf%s Max,',
+    'GPRINT:pg_db_size_avg:LAST:%4.1lf%s Last'],
     pg_n_tup_c => ['DEF:pg_n_tup_avg={file}:value:AVERAGE',
     'DEF:pg_n_tup_min={file}:value:MIN',
     'DEF:pg_n_tup_max={file}:value:MAX',
index c84199d..1524a66 100755 (executable)
@@ -9,15 +9,17 @@ use Carp (qw(confess cluck));
 use CGI (':cgi');
 use RRDs ();
 
-use Collectd::Graph::TypeLoader (qw(tl_read_config tl_load_type));
+use Collectd::Graph::Config (qw(gc_read_config gc_get_scalar));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
 
 use Collectd::Graph::Common (qw(sanitize_type get_selected_files
-      epoch_to_rfc1123));
+      epoch_to_rfc1123 flush_files));
 use Collectd::Graph::Type ();
 
 our $Debug = param ('debug');
 our $Begin = param ('begin');
 our $End = param ('end');
+our $GraphWidth = param ('width');
 
 if ($Debug)
 {
@@ -27,7 +29,17 @@ Content-Type: text/plain
 HTTP
 }
 
-tl_read_config ("$RealBin/../etc/collection3.conf");
+gc_read_config ("$RealBin/../etc/collection.conf");
+
+if ($GraphWidth)
+{
+  $GraphWidth =~ s/\D//g;
+}
+
+if (!$GraphWidth)
+{
+  $GraphWidth = gc_get_scalar ('GraphWidth', 400);
+}
 
 { # Sanitize begin and end times
   $End ||= 0;
@@ -93,25 +105,51 @@ for (@$files)
 }
 
 my $expires = time ();
+# IF (End is `now')
+#    OR (Begin is before `now' AND End is after `now')
 if (($End == 0) || (($Begin <= $expires) && ($End >= $expires)))
 {
   # 400 == width in pixels
-  my $timespan = $expires - $Begin;
-  $expires += int ($timespan / 400);
+  my $timespan;
+
+  if ($End == 0)
+  {
+    $timespan = $expires - $Begin;
+  }
+  else
+  {
+    $timespan = $End - $Begin;
+  }
+  $expires += int ($timespan / 400.0);
 }
+# IF (End is not `now')
+#    AND (End is before `now')
+# ==> Graph will never change again!
 elsif (($End > 0) && ($End < $expires))
 {
-  $expires += 366 * 86400;
+  $expires += (366 * 86400);
 }
 elsif ($Begin > $expires)
 {
   $expires = $Begin;
 }
 
+# Send FLUSH command to the daemon if necessary and possible.
+flush_files ($files,
+    begin => $Begin,
+    end => $End,
+    addr => gc_get_scalar ('UnixSockAddr', undef),
+    interval => gc_get_scalar ('Interval', 10));
+
 print STDOUT header (-Content_type => 'image/png',
   -Last_Modified => epoch_to_rfc1123 ($obj->getLastModified ()),
   -Expires => epoch_to_rfc1123 ($expires));
 
+if ($Debug)
+{
+  print "\$expires = $expires;\n";
+}
+
 my $args = $obj->getRRDArgs (0);
 
 if ($Debug)
@@ -135,7 +173,7 @@ else
   }
 
   $| = 1;
-  RRDs::graph ('-', '-a', 'PNG', @timesel, @$args);
+  RRDs::graph ('-', '-a', 'PNG', '--width', $GraphWidth, @timesel, @$args);
   if (my $err = RRDs::error ())
   {
     print STDERR "RRDs::graph failed: $err\n";
index b0001d2..be20928 100755 (executable)
@@ -27,10 +27,11 @@ use HTML::Entities ('encode_entities');
 
 use Data::Dumper;
 
-use Collectd::Graph::TypeLoader (qw(tl_read_config tl_load_type));
+use Collectd::Graph::Config (qw(gc_read_config gc_get_scalar));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
 use Collectd::Graph::Common (qw(get_files_from_directory get_all_hosts
       get_timespan_selection get_selected_files get_host_selection
-      get_plugin_selection));
+      get_plugin_selection flush_files));
 use Collectd::Graph::Type ();
 
 our $Debug = param ('debug') ? 1 : 0;
@@ -57,7 +58,7 @@ if (!exists ($Actions{$action}))
   exit 1;
 }
 
-tl_read_config ("$RealBin/../etc/collection3.conf");
+gc_read_config ("$RealBin/../etc/collection.conf");
 
 $Actions{$action}->();
 exit (0);
@@ -107,6 +108,14 @@ sub start_html
 {
   return if ($html_started);
 
+  my $end;
+  my $begin;
+  my $timespan;
+
+  $end = time ();
+  $timespan = get_timespan_selection ();
+  $begin = $end - $timespan;
+
   if (can_handle_xhtml ())
   {
     print <<HTML;
@@ -136,8 +145,9 @@ HTML
     <title>collection.cgi, Version 3</title>
     <link rel="icon" href="../share/shortcut-icon.png" type="image/png" />
     <link rel="stylesheet" href="../share/style.css" type="text/css" />
+    <script type="text/javascript" src="../share/navigate.js" />
   </head>
-  <body>
+  <body onload="nav_init ($begin, $end);">
 HTML
   $html_started = 1;
 }}
@@ -261,18 +271,28 @@ sub action_show_selection
   start_html ();
   show_selector ();
 
-  my $ident = {};
-
   my $all_files;
+  my $timespan;
+
   my $types = {};
 
+  my $id_counter = 0;
+
   $all_files = get_selected_files ();
+  $timespan = get_timespan_selection ();
 
   if ($Debug)
   {
     print "<pre>", Data::Dumper->Dump ([$all_files], ['all_files']), "</pre>\n";
   }
 
+  # Send FLUSH command to the daemon if necessary and possible.
+  flush_files ($all_files,
+      begin => time () - $timespan,
+      end => time (),
+      addr => gc_get_scalar ('UnixSockAddr', undef),
+      interval => gc_get_scalar ('Interval', 10));
+
   for (@$all_files)
   {
     my $file = $_;
@@ -301,17 +321,50 @@ sub action_show_selection
     my $type = $_;
     my $graphs_num = $types->{$type}->getGraphsNum ();
 
-    my $timespan = get_timespan_selection ();
-
     for (my $i = 0; $i < $graphs_num; $i++)
     {
       my $args = $types->{$type}->getGraphArgs ($i);
       my $url = encode_entities ("graph.cgi?$args;begin=-$timespan");
+      my $id = sprintf ("graph%04i", $id_counter++);
 
       print "      <tr>\n";
       print "        <td rowspan=\"$graphs_num\">$type</td>\n" if ($i == 0);
-
-      print qq#        <td><img src="$url" /></td>\n#;
+      print <<EOF;
+        <td>
+          <div class="graph_canvas">
+            <div class="graph_float">
+              <img id="${id}" class="graph_image"
+                alt="A graph"
+                src="$url" />
+              <div class="controls zoom">
+                <div title="Earlier"
+                  onclick="nav_move_earlier ('${id}');">&#x2190;</div>
+                <div title="Zoom out"
+                  onclick="nav_zoom_out ('${id}');">-</div>
+                <div title="Zoom in"
+                  onclick="nav_zoom_in ('${id}');">+</div>
+                <div title="Later"
+                  onclick="nav_move_later ('${id}');">&#x2192;</div>
+              </div>
+              <div class="controls preset">
+                <div title="Show current hour"
+                  onclick="nav_time_reset ('${id}', 3600);">H</div>
+                <div title="Show current day"
+                  onclick="nav_time_reset ('${id}', 86400);">D</div>
+                <div title="Show current week"
+                  onclick="nav_time_reset ('${id}', 7 * 86400);">W</div>
+                <div title="Show current month"
+                  onclick="nav_time_reset ('${id}', 31 * 86400);">M</div>
+                <div title="Show current year"
+                  onclick="nav_time_reset ('${id}', 366 * 86400);">Y</div>
+                <div title="Set all images to this timespan"
+                  onclick="nav_set_reference ('${id}');">!</div>
+              </div>
+            </div>
+          </div>
+       </td>
+EOF
+      # print qq#        <td><img src="$url" /></td>\n#;
       print "      </tr>\n";
     }
   }
index 0dceb62..c2af61c 100755 (executable)
@@ -27,13 +27,14 @@ use URI::Escape ('uri_escape');
 
 use Data::Dumper;
 
-use Collectd::Graph::TypeLoader (qw(tl_read_config tl_load_type));
+use Collectd::Graph::Config (qw(gc_read_config));
+use Collectd::Graph::TypeLoader (qw(tl_load_type));
 use Collectd::Graph::Common (qw(get_all_hosts get_files_for_host type_to_module_name));
 use Collectd::Graph::Type ();
 
 our $Debug = param ('debug') ? 1 : 0;
 
-tl_read_config ("$RealBin/../etc/collection3.conf");
+gc_read_config ("$RealBin/../etc/collection.conf");
 
 if ($Debug)
 {
diff --git a/contrib/collection3/etc/collection.conf b/contrib/collection3/etc/collection.conf
new file mode 100644 (file)
index 0000000..a836cf2
--- /dev/null
@@ -0,0 +1,350 @@
+GraphWidth 400
+UnixSockAddr "/var/run/collectd-unixsock"
+<Type apache_scoreboard>
+  Module GenericStacked
+  DataSources count
+  RRDTitle "Apache scoreboard on {hostname}"
+  RRDVerticalLabel "Slots"
+  RRDFormat "%6.2lf"
+  DSName closing Closing
+  DSName dnslookup DNS lookup
+  DSName finishing Finishing
+  DSName idle_cleanup Idle cleanup
+  DSName keepalive Keep alive
+  DSName logging Logging
+  DSName open Open (empty)
+  DSName reading Reading
+  DSName sending Sending
+  DSName starting Starting
+  DSName waiting Waiting
+  Order open closing dnslookup finishing idle_cleanup keepalive logging open reading sending starting waiting
+  Color closing      000080
+  Color dnslookup    ff0000
+  Color finishing    008080
+  Color idle_cleanup ffff00
+  Color keepalive    0080ff
+  Color logging      a000a0
+  Color open         e0e0e0
+  Color reading      0000ff
+  Color sending      00e000
+  Color starting     ff00ff
+  Color waiting      ffb000
+</Type>
+<Type cpu>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "CPU {plugin_instance} usage"
+  RRDVerticalLabel "Jiffies"
+  RRDFormat "%5.2lf"
+  DSName idle Idle
+  DSName nice Nice
+  DSName user User
+  DSName wait Wait-IO
+  DSName system System
+  DSName softirq SoftIRQ
+  DSName interrupt IRQ
+  DSName steal Steal
+  Order idle nice user wait system softirq interrupt steal
+  Color idle      e8e8e8
+  Color nice      00e000
+  Color user      0000ff
+  Color wait      ffb000
+  Color system    ff0000
+  Color softirq   ff00ff
+  Color interrupt a000a0
+  Color steal     000000
+</Type>
+<Type current>
+  DataSources value
+  DSName value Current
+  RRDTitle "Current ({type_instance})"
+  RRDVerticalLabel "Ampere"
+  RRDFormat "%4.1lfA"
+  Color value ffb000
+</Type>
+<Type df>
+  Module Df
+  DataSources free used
+</Type>
+<Type disk_octets>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Traffic ({plugin_instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type disk_ops>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Operations ({plugin_instance})"
+  RRDVerticalLabel "Operations per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type disk_merged>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk Merged Operations ({plugin_instance})"
+  RRDVerticalLabel "Merged operations/s"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type disk_time>
+  Module GenericIO
+  DataSources read write
+  DSName "read Read   "
+  DSName write Written
+  RRDTitle "Disk time per operation ({plugin_instance})"
+  RRDVerticalLabel "Avg. Time/Op"
+# RRDOptions ...
+  RRDFormat "%5.1lf%ss"
+  Scale 0.001
+</Type>
+<Type entropy>
+  DataSources entropy
+  DSName entropy Entropy bits
+  RRDTitle "Available entropy on {hostname}"
+  RRDVerticalLabel "Bits"
+  RRDFormat "%4.0lf"
+</Type>
+<Type fanspeed>
+  DataSources value
+  DSName value RPM
+  RRDTitle "Fanspeed ({instance})"
+  RRDVerticalLabel "RPM"
+  RRDFormat "%6.1lf"
+  Color value 00b000
+</Type>
+<Type frequency>
+  DataSources frequency
+  DSName frequency Frequency
+  RRDTitle "Frequency ({type_instance})"
+  RRDVerticalLabel "Hertz"
+  RRDFormat "%4.1lfHz"
+  Color frequency a000a0
+</Type>
+<Type humidity>
+  DataSources value
+  DSName value Humitidy
+  RRDTitle "Humitidy ({instance})"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%4.1lf%%"
+  Color value 00e000
+</Type>
+<Type if_errors>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Errors ({type_instance})"
+  RRDVerticalLabel "Errors per second"
+# RRDOptions ...
+  RRDFormat "%.3lf"
+</Type>
+<Type if_rx_errors>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interface receive errors ({plugin_instance})"
+  RRDVerticalLabel "Erorrs/s"
+  RRDFormat "%.1lf"
+  Color length  f00000
+  Color over    00e0ff
+  Color crc     00e000
+  Color frame   ffb000
+  Color fifo    f000c0
+  Color missed  0000f0
+</Type>
+<Type if_tx_errors>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interface transmit errors ({plugin_instance})"
+  RRDVerticalLabel "Erorrs/s"
+  RRDFormat "%.1lf"
+  Color aborted   f00000
+  Color carrier   00e0ff
+  Color fifo      00e000
+  Color heartbeat ffb000
+  Color window    f000c0
+</Type>
+<Type if_octets>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Traffic ({type_instance})"
+  RRDVerticalLabel "Bits per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+  Scale 8
+</Type>
+<Type if_packets>
+  Module GenericIO
+  DataSources rx tx
+  DSName rx RX
+  DSName tx TX
+  RRDTitle "Interface Packets ({type_instance})"
+  RRDVerticalLabel "Packets per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type ipt_bytes>
+  DataSources value
+  DSName value Bytes/s
+  RRDTitle "Traffic ({type_instance})"
+  RRDVerticalLabel "Bytes per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf%s"
+</Type>
+<Type ipt_packets>
+  DataSources value
+  DSName value Packets/s
+  RRDTitle "Packets ({type_instance})"
+  RRDVerticalLabel "Packets per second"
+# RRDOptions ...
+  RRDFormat "%5.1lf"
+</Type>
+<Type irq>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Interrupts on {hostname}"
+  RRDVerticalLabel "IRQs/s"
+  RRDFormat "%5.1lf"
+</Type>
+<Type load>
+  Module Load
+</Type>
+<Type memory>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Physical memory utilization on {hostname}"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%5.1lf%s"
+  RRDOptions -b 1024 -l 0
+  DSName     "free Free    "
+  DSName   "cached Cached  "
+  DSName "buffered Buffered"
+  DSName     "used Used    "
+  #Order used buffered cached free
+  Order free cached buffered used
+  Color free      00e000
+  Color cached    0000ff
+  Color buffered  ffb000
+  Color used      ff0000
+</Type>
+<Type percent>
+  DataSources percent
+  DSName percent Percent
+  RRDTitle "Percent ({type_instance})"
+  RRDVerticalLabel "Percent"
+  RRDFormat "%4.1lf%%"
+  Color percent 0000ff
+</Type>
+<Type ping>
+  DataSources ping
+  DSName "ping Latency"
+  RRDTitle "Network latency ({type_instance})"
+  RRDVerticalLabel "Milliseconds"
+  RRDFormat "%5.2lfms"
+</Type>
+<Type power>
+  DataSources value
+  DSName value Watts
+  RRDTitle "Power ({type_instance})"
+  RRDVerticalLabel "Watts"
+  RRDFormat "%6.2lf%sW"
+  Color value 008080
+</Type>
+<Type ps_rss>
+  DataSources value
+  DSName value RSS
+  RRDTitle "Resident Segment Size ({instance})"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%6.2lf%s"
+</Type>
+<Type ps_state>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Processes on {hostname}"
+  RRDVerticalLabel "Processes"
+  RRDFormat "%5.1lf%s"
+  DSName running  Running
+  DSName sleeping Sleeping
+  DSName paging   Paging
+  DSName zombies  Zombies
+  DSName blocked  Blocked
+  DSName stopped  Stopped
+  Order paging blocked zombies stopped running sleeping
+  Color running  00e000
+  Color sleeping 0000ff
+  Color paging   ffb000
+  Color zombies  ff0000
+  Color blocked  ff00ff
+  Color stopped  a000a0
+</Type>
+<Type swap>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "Swap utilization on {hostname}"
+  RRDVerticalLabel "Bytes"
+  RRDFormat "%5.1lf%s"
+  RRDOptions -b 1024 -l 0
+  DSName     "free Free    "
+  DSName   "cached Cached  "
+  DSName     "used Used    "
+  #Order used cached free
+  Order free cached used
+  Color free      00e000
+  Color cached    0000ff
+  Color used      ff0000
+</Type>
+<Type tcp_connections>
+  Module GenericStacked
+  DataSources value
+  RRDTitle "TCP connections ({plugin_instance})"
+  RRDVerticalLabel "Connections"
+  RRDFormat "%5.1lf"
+  Order LISTEN CLOSING LAST_ACK CLOSE_WAIT CLOSE TIME_WAIT FIN_WAIT2 FIN_WAIT1 SYN_RECV SYN_SENT ESTABLISHED CLOSED
+  Color ESTABLISHED 00e000
+  Color SYN_SENT   00e0ff
+  Color SYN_RECV   00e0a0
+  Color FIN_WAIT1  f000f0
+  Color FIN_WAIT2  f000a0
+  Color TIME_WAIT  ffb000
+  Color CLOSE      0000f0
+  Color CLOSE_WAIT 0000a0
+  Color LAST_ACK   000080
+  Color LISTEN     ff0000
+  Color CLOSING    000000
+  Color CLOSED     0000f0
+</Type>
+<Type temperature>
+  DataSources value
+  DSName value Temp
+  RRDTitle "Temperature ({instance})"
+  RRDVerticalLabel "°Celsius"
+  RRDFormat "%4.1lf°C"
+</Type>
+<Type users>
+  DataSources users
+  DSName users Users
+  RRDTitle "Users ({type_instance}) on {hostname}"
+  RRDVerticalLabel "Users"
+  RRDFormat "%.1lf"
+  Color users 0000f0
+</Type>
+<Type voltage>
+  DataSources value
+  DSName value Volts
+  RRDTitle "Voltage ({type_instance})"
+  RRDVerticalLabel "Volts"
+  RRDFormat "%4.1lfV"
+  Color value f00000
+</Type>
+# vim: set sw=2 sts=2 et syntax=apache fileencoding=latin-1 :
diff --git a/contrib/collection3/etc/collection3.conf b/contrib/collection3/etc/collection3.conf
deleted file mode 100644 (file)
index 583eeac..0000000
+++ /dev/null
@@ -1,255 +0,0 @@
-<Type cpu>
-  Module GenericStacked
-  DataSources value
-  RRDTitle "CPU {plugin_instance} usage"
-  RRDVerticalLabel "Jiffies"
-  RRDFormat "%5.2lf"
-  DSName idle Idle
-  DSName nice Nice
-  DSName user User
-  DSName wait Wait-IO
-  DSName system System
-  DSName softirq SoftIRQ
-  DSName interrupt IRQ
-  DSName steal Steal
-  Order idle nice user wait system softirq interrupt steal
-  Color idle      e8e8e8
-  Color nice      00e000
-  Color user      0000ff
-  Color wait      ffb000
-  Color system    ff0000
-  Color softirq   ff00ff
-  Color interrupt a000a0
-  Color steal     000000
-</Type>
-<Type df>
-  Module Df
-  DataSources free used
-</Type>
-<Type disk_octets>
-  Module GenericIO
-  DataSources read write
-  DSName "read Read   "
-  DSName write Written
-  RRDTitle "Disk Traffic ({plugin_instance})"
-  RRDVerticalLabel "Bytes per second"
-# RRDOptions ...
-  RRDFormat "%5.1lf%s"
-</Type>
-<Type disk_ops>
-  Module GenericIO
-  DataSources read write
-  DSName "read Read   "
-  DSName write Written
-  RRDTitle "Disk Operations ({plugin_instance})"
-  RRDVerticalLabel "Operations per second"
-# RRDOptions ...
-  RRDFormat "%5.1lf"
-</Type>
-<Type disk_merged>
-  Module GenericIO
-  DataSources read write
-  DSName "read Read   "
-  DSName write Written
-  RRDTitle "Disk Merged Operations ({plugin_instance})"
-  RRDVerticalLabel "Merged operations/s"
-# RRDOptions ...
-  RRDFormat "%5.1lf"
-</Type>
-<Type disk_time>
-  Module GenericIO
-  DataSources read write
-  DSName "read Read   "
-  DSName write Written
-  RRDTitle "Disk time per operation ({plugin_instance})"
-  RRDVerticalLabel "Avg. Time/Op"
-# RRDOptions ...
-  RRDFormat "%5.1lf%ss"
-  Scale 0.001
-</Type>
-<Type entropy>
-  DataSources entropy
-  DSName entropy Entropy bits
-  RRDTitle "Available entropy on {hostname}"
-  RRDVerticalLabel "Bits"
-  RRDFormat "%4.0lf"
-</Type>
-<Type fanspeed>
-  DataSources value
-  DSName value RPM
-  RRDTitle "Fanspeed ({type_instance})"
-  RRDVerticalLabel "RPM"
-  RRDFormat "%6.1lf"
-  Color value 00b000
-</Type>
-<Type if_errors>
-  Module GenericIO
-  DataSources rx tx
-  DSName rx RX
-  DSName tx TX
-  RRDTitle "Interface Errors ({type_instance})"
-  RRDVerticalLabel "Errors per second"
-# RRDOptions ...
-  RRDFormat "%.3lf"
-</Type>
-<Type if_rx_errors>
-  Module GenericStacked
-  DataSources value
-  RRDTitle "Interface receive errors ({plugin_instance})"
-  RRDVerticalLabel "Erorrs/s"
-  RRDFormat "%.1lf"
-  Color length  f00000
-  Color over    00e0ff
-  Color crc     00e000
-  Color frame   ffb000
-  Color fifo    f000c0
-  Color missed  0000f0
-</Type>
-<Type if_tx_errors>
-  Module GenericStacked
-  DataSources value
-  RRDTitle "Interface transmit errors ({plugin_instance})"
-  RRDVerticalLabel "Erorrs/s"
-  RRDFormat "%.1lf"
-  Color aborted   f00000
-  Color carrier   00e0ff
-  Color fifo      00e000
-  Color heartbeat ffb000
-  Color window    f000c0
-</Type>
-<Type if_octets>
-  Module GenericIO
-  DataSources rx tx
-  DSName rx RX
-  DSName tx TX
-  RRDTitle "Interface Traffic ({type_instance})"
-  RRDVerticalLabel "Bits per second"
-# RRDOptions ...
-  RRDFormat "%5.1lf%s"
-  Scale 8
-</Type>
-<Type if_packets>
-  Module GenericIO
-  DataSources rx tx
-  DSName rx RX
-  DSName tx TX
-  RRDTitle "Interface Packets ({type_instance})"
-  RRDVerticalLabel "Packets per second"
-# RRDOptions ...
-  RRDFormat "%5.1lf%s"
-</Type>
-<Type ipt_bytes>
-  DataSources value
-  DSName value Bytes/s
-  RRDTitle "Traffic ({type_instance})"
-  RRDVerticalLabel "Bytes per second"
-# RRDOptions ...
-  RRDFormat "%5.1lf%s"
-</Type>
-<Type ipt_packets>
-  DataSources value
-  DSName value Packets/s
-  RRDTitle "Packets ({type_instance})"
-  RRDVerticalLabel "Packets per second"
-# RRDOptions ...
-  RRDFormat "%5.1lf"
-</Type>
-<Type irq>
-  Module GenericStacked
-  DataSources value
-  RRDTitle "Interrupts on {hostname}"
-  RRDVerticalLabel "IRQs/s"
-  RRDFormat "%5.1lf"
-</Type>
-<Type load>
-  Module Load
-</Type>
-<Type memory>
-  Module GenericStacked
-  DataSources value
-  RRDTitle "Physical memory utilization on {hostname}"
-  RRDVerticalLabel "Bytes"
-  RRDFormat "%5.1lf%s"
-  RRDOptions -b 1024 -l 0
-  DSName     "free Free    "
-  DSName   "cached Cached  "
-  DSName "buffered Buffered"
-  DSName     "used Used    "
-  #Order used buffered cached free
-  Order free cached buffered used
-  Color free      00e000
-  Color cached    0000ff
-  Color buffered  ffb000
-  Color used      ff0000
-</Type>
-<Type ping>
-  DataSources ping
-  DSName "ping Latency"
-  RRDTitle "Network latency ({type_instance})"
-  RRDVerticalLabel "Milliseconds"
-  RRDFormat "%5.2lfms"
-</Type>
-<Type ps_state>
-  Module GenericStacked
-  DataSources value
-  RRDTitle "Processes on {hostname}"
-  RRDVerticalLabel "Processes"
-  RRDFormat "%5.1lf%s"
-  DSName running  Running
-  DSName sleeping Sleeping
-  DSName paging   Paging
-  DSName zombies  Zombies
-  DSName blocked  Blocked
-  DSName stopped  Stopped
-  Order paging blocked zombies stopped running sleeping
-  Color running  00e000
-  Color sleeping 0000ff
-  Color paging   ffb000
-  Color zombies  ff0000
-  Color blocked  ff00ff
-  Color stopped  a000a0
-</Type>
-<Type tcp_connections>
-  Module GenericStacked
-  DataSources value
-  RRDTitle "TCP connections ({plugin_instance})"
-  RRDVerticalLabel "Connections"
-  RRDFormat "%5.1lf"
-  Order LISTEN CLOSING LAST_ACK CLOSE_WAIT CLOSE TIME_WAIT FIN_WAIT2 FIN_WAIT1 SYN_RECV SYN_SENT ESTABLISHED CLOSED
-  Color ESTABLISHED 00e000
-  Color SYN_SENT   00e0ff
-  Color SYN_RECV   00e0a0
-  Color FIN_WAIT1  f000f0
-  Color FIN_WAIT2  f000a0
-  Color TIME_WAIT  ffb000
-  Color CLOSE      0000f0
-  Color CLOSE_WAIT 0000a0
-  Color LAST_ACK   000080
-  Color LISTEN     ff0000
-  Color CLOSING    000000
-  Color CLOSED     0000f0
-</Type>
-<Type temperature>
-  DataSources value
-  DSName value Temp
-  RRDTitle "Temperature ({type_instance})"
-  RRDVerticalLabel "°Celsius"
-  RRDFormat "%4.1lf°C"
-</Type>
-<Type users>
-  DataSources users
-  DSName users Users
-  RRDTitle "Users ({type_instance}) on {hostname}"
-  RRDVerticalLabel "Users"
-  RRDFormat "%.1lf"
-  Color users 0000f0
-</Type>
-<Type voltage>
-  DataSources value
-  DSName value Volts
-  RRDTitle "Voltage ({type_instance})"
-  RRDVerticalLabel "Volts"
-  RRDFormat "%4.1lfV"
-  Color value f00000
-</Type>
-# vim: set sw=2 sts=2 et syntax=apache :
diff --git a/contrib/collection3/lib/Collectd/Config.pm b/contrib/collection3/lib/Collectd/Config.pm
new file mode 100644 (file)
index 0000000..d20be35
--- /dev/null
@@ -0,0 +1,144 @@
+package Collectd::Graph::Config;
+
+=head1 NAME
+
+Collectd::Graph::Config - Parse the collection3 config file.
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+use Collectd::Graph::Type ();
+
+@Collectd::Graph::Config::ISA = ('Exporter');
+@Collectd::Graph::Config::EXPORT_OK = (qw(gc_read_config gc_get_config
+  gc_get_scalar));
+
+our $Configuration = undef;
+
+return (1);
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<gc_read_config> (I<$file>)
+
+Reads the configuration from the file located at I<$file>. Returns B<true> when
+successfull and B<false> otherwise.
+
+=cut
+
+sub gc_read_config
+{
+  my $file = shift;
+  my %conf;
+
+  if ($Configuration)
+  {
+    return (1);
+  }
+
+  $file ||= "etc/collection.conf";
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+
+  $Configuration = \%conf;
+  return (1);
+} # gc_read_config
+
+=item B<gc_get_config> ()
+
+Returns the hash as provided by L<Config::General>. The hash is returned as a
+hash reference. Don't change it!
+
+=cut
+
+sub gc_get_config
+{
+  return ($Configuration);
+} # gc_get_config
+
+=item B<gc_get_config> (I<$key>, [I<$default>])
+
+Returns the scalar value I<$key> from the config file. If the key does not
+exist, I<$default> will be returned. If no default is given, B<undef> will be
+used in this case.
+
+=cut
+
+sub gc_get_scalar
+{
+  my $key = shift;
+  my $default = (@_ != 0) ? shift : undef;
+  my $value;
+
+  if (!$Configuration)
+  {
+    return ($default);
+  }
+
+  $value = $Configuration->{lc ($key)};
+  if (!defined ($value))
+  {
+    return ($default);
+  }
+
+  if (ref ($value) ne '')
+  {
+    cluck ("Value for `$key' should be scalar, but actually is "
+      . ref ($value));
+    return ($default);
+  }
+
+  return ($value);
+} # gc_get_config
+
+=back
+
+=head1 DEPENDS ON
+
+L<Config::General>
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
index ec171da..6f26fd8 100644 (file)
@@ -38,6 +38,7 @@ $ColorHalfBlue = 'B7B7F7';
   sort_idents_by_type_instance
   type_to_module_name
   epoch_to_rfc1123
+  flush_files
 ));
 
 our $DataDir = '/var/lib/collectd/rrd';
@@ -582,4 +583,116 @@ sub epoch_to_rfc1123
   return ($string);
 }
 
+sub flush_files
+{
+  my $all_files = shift;
+  my %opts = @_;
+
+  my $begin;
+  my $end;
+  my $addr;
+  my $interval;
+  my $sock;
+  my $now;
+  my $files_to_flush = [];
+  my $status;
+
+  if (!defined $opts{'begin'})
+  {
+    cluck ("begin is not defined");
+    return;
+  }
+  $begin = $opts{'begin'};
+
+  if (!defined $opts{'end'})
+  {
+    cluck ("end is not defined");
+    return;
+  }
+  $end = $opts{'end'};
+
+  if (!$opts{'addr'})
+  {
+    return (1);
+  }
+
+  $interval = $opts{'interval'} || 10;
+
+  if (ref ($all_files) eq 'HASH')
+  {
+    my @tmp = ($all_files);
+    $all_files = \@tmp;
+  }
+
+  $now = time ();
+  # Don't flush anything if the timespan is in the future.
+  if (($end > $now) && ($begin > $now))
+  {
+    return (1);
+  }
+
+  for (@$all_files)
+  {
+    my $file_orig = $_;
+    my $file_name = ident_to_filename ($file_orig);
+    my $file_copy = {};
+    my @statbuf;
+    my $mtime;
+
+    @statbuf = stat ($file_name);
+    if (!@statbuf)
+    {
+      next;
+    }
+    $mtime = $statbuf[9];
+
+    # Skip if file is fresh
+    if (($now - $mtime) <= $interval)
+    {
+      next;
+    }
+    # or $end is before $mtime
+    elsif (($end != 0) && (($end - $mtime) <= 0))
+    {
+      next;
+    }
+
+    $file_copy->{'host'} = $file_orig->{'hostname'};
+    $file_copy->{'plugin'} = $file_orig->{'plugin'};
+    if (exists $file_orig->{'plugin_instance'})
+    {
+      $file_copy->{'plugin_instance'} = $file_orig->{'plugin_instance'}
+    }
+    $file_copy->{'type'} = $file_orig->{'type'};
+    if (exists $file_orig->{'type_instance'})
+    {
+      $file_copy->{'type_instance'} = $file_orig->{'type_instance'}
+    }
+
+    push (@$files_to_flush, $file_copy);
+  } # for (@$all_files)
+
+  if (!@$files_to_flush)
+  {
+    return (1);
+  }
+
+  $sock = Collectd::Unixsock->new ($opts{'addr'});
+  if (!$sock)
+  {
+    return;
+  }
+
+  $status = $sock->flush (plugins => ['rrdtool'], identifier => $files_to_flush);
+  if (!$status)
+  {
+    cluck ("FLUSH failed: " . $sock->{'error'});
+    $sock->destroy ();
+    return;
+  }
+
+  $sock->destroy ();
+  return (1);
+} # flush_files
+
 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
diff --git a/contrib/collection3/lib/Collectd/Graph/Config.pm b/contrib/collection3/lib/Collectd/Graph/Config.pm
new file mode 100644 (file)
index 0000000..d20be35
--- /dev/null
@@ -0,0 +1,144 @@
+package Collectd::Graph::Config;
+
+=head1 NAME
+
+Collectd::Graph::Config - Parse the collection3 config file.
+
+=cut
+
+# Copyright (C) 2008  Florian octo Forster <octo at verplant.org>
+#
+# 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use Exporter ();
+use Config::General ('ParseConfig');
+use Collectd::Graph::Type ();
+
+@Collectd::Graph::Config::ISA = ('Exporter');
+@Collectd::Graph::Config::EXPORT_OK = (qw(gc_read_config gc_get_config
+  gc_get_scalar));
+
+our $Configuration = undef;
+
+return (1);
+
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
+=item B<gc_read_config> (I<$file>)
+
+Reads the configuration from the file located at I<$file>. Returns B<true> when
+successfull and B<false> otherwise.
+
+=cut
+
+sub gc_read_config
+{
+  my $file = shift;
+  my %conf;
+
+  if ($Configuration)
+  {
+    return (1);
+  }
+
+  $file ||= "etc/collection.conf";
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+
+  $Configuration = \%conf;
+  return (1);
+} # gc_read_config
+
+=item B<gc_get_config> ()
+
+Returns the hash as provided by L<Config::General>. The hash is returned as a
+hash reference. Don't change it!
+
+=cut
+
+sub gc_get_config
+{
+  return ($Configuration);
+} # gc_get_config
+
+=item B<gc_get_config> (I<$key>, [I<$default>])
+
+Returns the scalar value I<$key> from the config file. If the key does not
+exist, I<$default> will be returned. If no default is given, B<undef> will be
+used in this case.
+
+=cut
+
+sub gc_get_scalar
+{
+  my $key = shift;
+  my $default = (@_ != 0) ? shift : undef;
+  my $value;
+
+  if (!$Configuration)
+  {
+    return ($default);
+  }
+
+  $value = $Configuration->{lc ($key)};
+  if (!defined ($value))
+  {
+    return ($default);
+  }
+
+  if (ref ($value) ne '')
+  {
+    cluck ("Value for `$key' should be scalar, but actually is "
+      . ref ($value));
+    return ($default);
+  }
+
+  return ($value);
+} # gc_get_config
+
+=back
+
+=head1 DEPENDS ON
+
+L<Config::General>
+
+=head1 SEE ALSO
+
+L<Collectd::Graph::Type>
+
+=head1 AUTHOR AND LICENSE
+
+Copyright (c) 2008 by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
+General Public License, VersionE<nbsp>2 (GPLv2).
+
+=cut
+
+# vim: set shiftwidth=2 softtabstop=2 tabstop=8 et fdm=marker :
index 60097e5..1fb60af 100644 (file)
@@ -248,6 +248,20 @@ sub getTitle
   my $plugin_instance = $ident->{'plugin_instance'};
   my $type = $ident->{'type'};
   my $type_instance = $ident->{'type_instance'};
+  my $instance;
+
+  if (defined $type_instance)
+  {
+    $instance = $type_instance;
+  }
+  elsif (defined $plugin_instance)
+  {
+    $instance = $plugin_instance;
+  }
+  else
+  {
+    $instance = 'no instance';
+  }
 
   if (!defined $plugin_instance)
   {
@@ -264,6 +278,7 @@ sub getTitle
   $title =~ s#{plugin_instance}#$plugin_instance#g;
   $title =~ s#{type}#$type#g;
   $title =~ s#{type_instance}#$type_instance#g;
+  $title =~ s#{instance}#$instance#g;
 
   return ($title);
 }
index 273d89e..a1a3e1c 100644 (file)
@@ -32,6 +32,18 @@ sub getRRDArgs
   my $idents = $group->{$group[$index]};
   my $ds_name_len = 0;
 
+  my $ds = $obj->getDataSources ();
+  if (!$ds)
+  {
+    confess ("obj->getDataSources failed.");
+  }
+  if (@$ds != 1)
+  {
+    confess ("I can only work with RRD files that have "
+      . "exactly one data source!");
+  }
+  my $data_source = $ds->[0];
+
   my $rrd_title = $obj->getTitle ($idents->[0]);
 
   my $colors = $obj->{'rrd_colors'} || {};
@@ -64,9 +76,9 @@ sub getRRDArgs
     $names[$i] =~ s/:/\\:/g;
 
     push (@ret,
-      "DEF:min${i}=${filename}:value:MIN",
-      "DEF:avg${i}=${filename}:value:AVERAGE",
-      "DEF:max${i}=${filename}:value:MAX");
+      "DEF:min${i}=${filename}:${data_source}:MIN",
+      "DEF:avg${i}=${filename}:${data_source}:AVERAGE",
+      "DEF:max${i}=${filename}:${data_source}:MAX");
   }
 
   for (my $i = @$idents - 1; $i >= 0; $i--)
index f13665e..b6ca4a6 100644 (file)
@@ -18,8 +18,6 @@ sub new
   $obj->{'rrd_format'} = '%.2lf';
   $obj->{'colors'} = [qw(00ff00 0000ff ff0000)];
 
-  print STDERR "Hi, this is Collectd::Graph::Type::Load::new\n";
-
   return (bless ($obj, $pkg));
 } # new
 
index 6223eaa..c5fe613 100644 (file)
@@ -27,12 +27,11 @@ use warnings;
 use Carp (qw(cluck confess));
 use Exporter ();
 use Config::General ('ParseConfig');
+use Collectd::Graph::Config ('gc_get_config');
 use Collectd::Graph::Type ();
 
 @Collectd::Graph::TypeLoader::ISA = ('Exporter');
-@Collectd::Graph::TypeLoader::EXPORT_OK = ('tl_read_config', 'tl_load_type');
-
-our $Configuration = undef;
+@Collectd::Graph::TypeLoader::EXPORT_OK = ('tl_load_type');
 
 our @ArrayMembers = (qw(data_sources rrd_opts custom_order));
 our @ScalarMembers = (qw(rrd_title rrd_format rrd_vertical scale));
@@ -53,42 +52,6 @@ our %MemberToConfigMap =
 
 return (1);
 
-=head1 EXPORTED FUNCTIONS
-
-=over 4
-
-=item B<tl_read_config> (I<$file>)
-
-Reads the configuration from the file located at I<$file>.
-
-=cut
-
-sub tl_read_config
-{
-  my $file = shift;
-  my %conf;
-
-  if ($Configuration)
-  {
-    return (1);
-  }
-
-  %conf = ParseConfig (-ConfigFile => $file,
-    -LowerCaseNames => 1,
-    -UseApacheInclude => 1,
-    -IncludeDirectories => 1,
-    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
-    -MergeDuplicateBlocks => 1,
-    -CComments => 0);
-  if (!%conf)
-  {
-    return;
-  }
-
-  $Configuration = \%conf;
-  return (1);
-} # tl_read_config
-
 sub _create_object
 {
   my $module = shift;
@@ -123,7 +86,6 @@ sub _load_module_from_config
 
   if ($module)
   {
-    print STDERR "\$module = $module;\n";
     $obj = _create_object ($module);
     if (!$obj)
     {
@@ -232,8 +194,6 @@ sub _load_module_from_config
 
       $obj->{$member} ||= {};
       $obj->{$member}{$ds} = $val;
-
-      print STDERR "\$obj->{$member}{$ds} = $val;\n";
     } # for (@val_list)
   } # }}} for (@DSMappedMembers)
 
@@ -263,6 +223,10 @@ sub _load_module_generic
   return ($obj);
 } # _load_module_generic
 
+=head1 EXPORTED FUNCTIONS
+
+=over 4
+
 =item B<tl_load_type> (I<$type>)
 
 Does whatever is necessary to get an object with which to graph RRD files of
@@ -273,10 +237,11 @@ type I<$type>.
 sub tl_load_type
 {
   my $type = shift;
+  my $conf = gc_get_config ();
 
-  if (defined $Configuration->{'type'}{$type})
+  if (defined ($conf) && defined ($conf->{'type'}{$type}))
   {
-    return (_load_module_from_config ($Configuration->{'type'}{$type}));
+    return (_load_module_from_config ($conf->{'type'}{$type}));
   }
   else
   {
diff --git a/contrib/collection3/share/navigate.js b/contrib/collection3/share/navigate.js
new file mode 100644 (file)
index 0000000..3bfe56e
--- /dev/null
@@ -0,0 +1,300 @@
+function nav_init (time_begin, time_end)
+{
+  var all_images;
+  var i;
+
+  all_images = document.getElementsByTagName ("img");
+  for (i = 0; i < all_images.length; i++)
+  {
+    if (all_images[i].className != "graph_image")
+      continue;
+
+    all_images[i].navTimeBegin = new Number (time_begin);
+    all_images[i].navTimeEnd   = new Number (time_end);
+
+    all_images[i].navBaseURL = all_images[i].src.replace (/;(begin|end)=[^;]*/g, '');
+
+    if (all_images[i].addEventListener) /* Mozilla */
+    {
+      all_images[i].addEventListener ('dblclick', nav_handle_dblclick,
+          false /* == bubbling */);
+      all_images[i].addEventListener ('DOMMouseScroll', nav_handle_wheel,
+          false /* == bubbling */);
+    }
+    else
+    {
+      all_images[i].ondblclick = nav_handle_dblclick;
+      all_images[i].onmousewheel = nav_handle_wheel;
+    }
+  }
+
+  return (true);
+} /* nav_init */
+
+function nav_image_repaint (img)
+{
+  if (!img || !img.navBaseURL
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  img.src = img.navBaseURL + ";"
+    + "begin=" + img.navTimeBegin.toFixed (0) + ";"
+    + "end=" + img.navTimeEnd.toFixed (0);
+} /* nav_image_repaint */
+
+function nav_time_reset (img_id ,diff)
+{
+  var img;
+
+  img = document.getElementById (img_id);
+  if (!img)
+    return (false);
+
+  img.navTimeEnd = new Number ((new Date ()).getTime () / 1000);
+  img.navTimeBegin = new Number (img.navTimeEnd - diff);
+
+  nav_image_repaint (img);
+
+  return (true);
+}
+
+function nav_time_change_obj (img, factor_begin, factor_end)
+{
+  var diff;
+
+  if (!img || !img.navBaseURL
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return (false);
+
+  diff = img.navTimeEnd - img.navTimeBegin;
+
+  /* Prevent zooming in if diff is less than five minutes */
+  if ((diff <= 300) && (factor_begin > 0.0) && (factor_end < 0.0))
+    return (true);
+
+  img.navTimeBegin += (diff * factor_begin);
+  img.navTimeEnd   += (diff * factor_end);
+
+  nav_image_repaint (img);
+
+  return (true);
+} /* nav_time_change */
+
+function nav_time_change (img_id, factor_begin, factor_end)
+{
+  var diff;
+
+  if (img_id == '*')
+  {
+    var all_images;
+    var i;
+
+    all_images = document.getElementsByTagName ("img");
+    for (i = 0; i < all_images.length; i++)
+    {
+      if (all_images[i].className != "graph_image")
+        continue;
+    
+      nav_time_change_obj (all_images[i], factor_begin, factor_end);
+    }
+  }
+  else
+  {
+    var img;
+
+    img = document.getElementById (img_id);
+    if (!img)
+      return (false);
+
+    nav_time_change_obj (img, factor_begin, factor_end);
+  }
+
+  return (true);
+} /* nav_time_change */
+
+function nav_move_earlier (img_id)
+{
+  return (nav_time_change (img_id, -0.2, -0.2));
+} /* nav_move_earlier */
+
+function nav_move_later (img_id)
+{
+  return (nav_time_change (img_id, +0.2, +0.2));
+} /* nav_move_later */
+
+function nav_zoom_in (img_id)
+{
+  return (nav_time_change (img_id, +0.2, -0.2));
+} /* nav_zoom_in */
+
+function nav_zoom_out (img_id)
+{
+  return (nav_time_change (img_id, (-1.0 / 3.0), (1.0 / 3.0)));
+} /* nav_zoom_in */
+
+function nav_set_reference (img_id)
+{
+  var img;
+  var all_images;
+  var tmp;
+  var i;
+
+  img = document.getElementById (img_id);
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  all_images = document.getElementsByTagName ("img");
+  for (i = 0; i < all_images.length; i++)
+  {
+    tmp = all_images[i];
+    if (!tmp || (tmp.className != "graph_image")
+        || !tmp.navTimeBegin || !tmp.navTimeEnd)
+      continue;
+
+    if (tmp.id == img_id)
+      continue;
+
+    tmp.navTimeBegin = img.navTimeBegin;
+    tmp.navTimeEnd = img.navTimeEnd;
+
+    nav_image_repaint (tmp);
+  }
+} /* nav_set_reference */
+
+/* 
+ * TODO: calculate the mouse position relative to the image in a cross-browser
+ * manner.
+ */
+function nav_calculate_offset_x (obj)
+{
+  var offset = 0;
+
+  if (!obj)
+    return (offset);
+
+  offset = obj.offsetLeft;
+  if (obj.offsetParent)
+    offset += nav_calculate_offset_x (obj.offsetParent);
+
+  return (offset);
+} /* nav_calculate_offset_x */
+
+function nav_calculate_event_x (e)
+{
+  var pos = 0;
+  var off = 0;
+
+  if (!e || !e.target)
+    return;
+  
+  off = nav_calculate_offset_x (e.target);
+
+  if (e.pageX || e.pageY)
+  {
+    pos = e.pageX;
+  }
+  else if (e.clientX || e.clientY)
+  {
+    pos = e.clientX + document.body.scrollLeft
+      + document.documentElement.scrollLeft;
+  }
+
+  return (pos);
+} /* nav_calculate_event_x */
+
+function nav_recenter (e)
+{
+  var x;
+  var y;
+  var img;
+  var diff;
+  var time_old_center;
+  var time_new_center;
+  var width;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  width = img.width - 97;
+
+  x = e.layerX - 70;
+  if (!x || (x < 0) || (x > width))
+    return;
+
+  y = e.layerY;
+  if (!y || (y < 35) || (y > 135))
+    return;
+
+  diff = img.navTimeEnd - img.navTimeBegin;
+
+  time_old_center = img.navTimeBegin + (diff / 2.0);
+  time_new_center = img.navTimeBegin + (x * diff / width);
+
+  img.navTimeBegin += (time_new_center - time_old_center);
+  img.navTimeEnd   += (time_new_center - time_old_center);
+} /* nav_recenter */
+
+function nav_handle_dblclick (e)
+{
+  var img;
+
+  /* M$IE */
+  if (!e)
+    e = window.event;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  nav_recenter (e);
+  nav_image_repaint (img);
+
+  // e.returnValue = false;
+} /* nav_handle_dblclick */
+
+/* Taken from <http://adomas.org/javascript-mouse-wheel/> */
+function nav_handle_wheel (e)
+{
+  var delta = 0;
+  var img;
+  
+  /* M$IE */
+  if (!e)
+    e = window.event;
+
+  img = e.target;
+  if (!img || (img.className != "graph_image")
+      || !img.navTimeBegin || !img.navTimeEnd)
+    return;
+
+  /* Opera and M$IE */
+  if (e.wheelDelta)
+  {
+    delta = e.wheelDelta; 
+    if (window.opera)
+      delta = delta * (-1);
+  }
+  else if (e.detail)
+  {
+    delta = e.detail * (-1);
+  }
+
+  if (!delta)
+    return;
+
+  nav_recenter (e);
+  if (delta > 0)
+    nav_zoom_in (img.id);
+  else
+    nav_zoom_out (img.id);
+
+  if (e.preventDefault)
+    e.preventDefault ();
+  e.returnValue = false;
+} /* function nav_handle_wheel */
+
+/* vim: set sw=2 sts=2 et : */
index a6648aa..8c12951 100644 (file)
@@ -1,3 +1,73 @@
+div.graph
+{
+}
+
+div.graph_canvas div.graph_float
+{
+  float: left;
+  position: relative;
+}
+
+div.graph_float div.controls
+{
+  display: none;
+  position: absolute;
+}
+
+div.graph_float:hover div.controls
+{
+  display: block;
+}
+
+div.graph_float div.controls.zoom
+{
+  right: 5px;
+  bottom: 10px;
+}
+
+div.graph_float div.controls.preset
+{
+  right: 5px;
+  top: 5px;
+}
+
+div.graph_float div.controls div
+{
+  display: block;
+
+  color: gray;
+  background: white;
+
+  text-decoration: none;
+  text-align: center;
+  font-size: small;
+
+  cursor: pointer;
+
+  border: 1px solid gray;
+  width: 1em;
+  height: 1em;
+  padding: 1px;
+  margin: 0px;
+}
+
+div.graph_float div.controls div:hover
+{
+  color: black;
+  border-color: black;
+}
+
+div.graph_float div.controls.preset div
+{
+  margin: 1px 0px 1px 0px;
+}
+
+div.graph_float div.controls.zoom div
+{
+  float: left;
+  margin: 0px 1px 0px 1px;
+}
+
 table
 {
   border-collapse: collapse;
index 240c6c3..f68cc1a 100644 (file)
@@ -98,8 +98,8 @@ static int my_read (void)
        vl.values     = values;
        vl.values_len = 1;
        vl.time       = time (NULL);
-       strcpy (vl.host, hostname_g);
-       strcpy (vl.plugin, "myplugin");
+       sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+       sstrncpy (vl.plugin, "myplugin", sizeof (vl.plugin));
        /* optionally set vl.plugin_instance and vl.type_instance to reasonable
         * values (default: "") */
 
diff --git a/contrib/snmp-probe-host.px b/contrib/snmp-probe-host.px
new file mode 100755 (executable)
index 0000000..1d8f975
--- /dev/null
@@ -0,0 +1,372 @@
+#!/usr/bin/perl
+#
+# collectd - snmp-probe-host.px
+# 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
+#
+# Author:
+#   Florian octo Forster <octo at noris.net>
+#
+
+use strict;
+use warnings;
+use SNMP;
+use Config::General ('ParseConfig');
+use Getopt::Long ('GetOptions');
+use Socket6;
+
+sub get_config
+{
+  my %conf;
+  my $file = shift;
+
+  %conf = ParseConfig (-ConfigFile => $file,
+    -LowerCaseNames => 1,
+    -UseApacheInclude => 1,
+    -IncludeDirectories => 1,
+    ($Config::General::VERSION >= 2.38) ? (-IncludeAgain => 0) : (),
+    -MergeDuplicateBlocks => 1,
+    -CComments => 0);
+  if (!%conf)
+  {
+    return;
+  }
+  return (\%conf);
+} # get_config
+
+sub probe_one
+{
+  my $sess = shift;
+  my $conf = shift;
+  my @oids;
+  my $cmd = 'GET';
+  my $vl;
+
+  if (!$conf->{'table'} || !$conf->{'values'})
+  {
+    warn "No 'table' or 'values' setting";
+    return;
+  }
+
+  @oids = split (/"\s*"/, $conf->{'values'});
+  if ($conf->{'table'} =~ m/^(true|yes|on)$/i)
+  {
+    $cmd = 'GETNEXT';
+    if (defined ($conf->{'instance'}))
+    {
+      push (@oids, $conf->{'instance'});
+    }
+  }
+
+  require Data::Dumper;
+
+  #print "probe_one: \@oids = (" . join (', ', @oids) . ");\n";
+  for (@oids)
+  {
+    my $oid_orig = $_;
+    my $vb;
+    my $status;
+
+    if ($oid_orig =~ m/[^0-9\.]/)
+    {
+      my $tmp = SNMP::translateObj ($oid_orig);
+      if (!defined ($tmp))
+      {
+        warn ("Cannot translate OID $oid_orig");
+        return;
+      }
+      $oid_orig = $tmp;
+    }
+
+    $vb = SNMP::Varbind->new ([$oid_orig]);
+
+    if ($cmd eq 'GET')
+    {
+      $status = $sess->get ($vb);
+      if ($sess->{'ErrorNum'} != 0)
+      {
+        return;
+      }
+      if (!defined ($status))
+      {
+        return;
+      }
+      if ("$status" eq 'NOSUCHOBJECT')
+      {
+        return;
+      }
+    }
+    else
+    {
+      my $oid_copy;
+
+      $status = $sess->getnext ($vb);
+      if ($sess->{'ErrorNum'} != 0)
+      {
+        return;
+      }
+
+      $oid_copy = $vb->[0];
+      if ($oid_copy =~ m/[^0-9\.]/)
+      {
+        my $tmp = SNMP::translateObj ($oid_copy);
+        if (!defined ($tmp))
+        {
+          warn ("Cannot translate OID $oid_copy");
+          return;
+        }
+        $oid_copy = $tmp;
+      }
+
+      #print "$oid_orig > $oid_copy ?\n";
+      if (substr ($oid_copy, 0, length ($oid_orig)) ne $oid_orig)
+      {
+        return;
+      }
+    }
+
+    #print STDOUT Data::Dumper->Dump ([$oid_orig, $status], [qw(oid_orig status)]);
+  } # for (@oids)
+
+  return (1);
+} # probe_one
+
+sub probe_all
+{
+  my $host = shift;
+  my $community = shift;
+  my $data = shift;
+  my $version = 2;
+  my @valid_data = ();
+  my $begin;
+  my $address;
+
+  {
+    my @status;
+
+    @status = getaddrinfo ($host, 'snmp');
+    while (@status >= 5)
+    {
+      my $family    = shift (@status);
+      my $socktype  = shift (@status);
+      my $proto     = shift (@status);
+      my $saddr     = shift (@status);
+      my $canonname = shift (@status);
+      my $host;
+      my $port;
+
+      ($host, $port) = getnameinfo ($saddr, NI_NUMERICHOST);
+      if (defined ($port))
+      {
+        $address = $host;
+      }
+      else
+      {
+        warn ("getnameinfo failed: $host");
+      }
+    }
+  }
+  if (!$address)
+  {
+    return;
+  }
+
+  while ($version > 0)
+  {
+    my $sess;
+
+    $sess = new SNMP::Session (DestHost => $host,
+      Community => $community,
+      Version => $version,
+      Timeout => 1000000,
+      UseNumeric => 1);
+    if (!$sess)
+    {
+      $version--;
+      next;
+    }
+
+    $begin = time ();
+
+    for (keys %$data)
+    {
+      my $name = $_;
+      if (probe_one ($sess, $data->{$name}))
+      {
+        push (@valid_data, $name);
+      }
+
+      if ((@valid_data == 0) && ((time () - $begin) > 10))
+      {
+        # break for loop
+        last;
+      }
+    }
+
+    if (@valid_data)
+    {
+      # break while loop
+      last;
+    }
+
+    $version--;
+  } # while ($version > 0)
+
+  print <<EOF;
+  <Host "$host">
+    Address "$address"
+    Version $version
+    Community "$community"
+EOF
+  for (sort (@valid_data))
+  {
+    print "    Collect \"$_\"\n";
+  }
+  if (!@valid_data)
+  {
+    print <<EOF;
+# WARNING: Autoconfiguration failed.
+# TODO: Add one or more `Collect' statements here:
+#   Collect "foo"
+EOF
+  }
+  print <<EOF;
+    Interval 60
+  </Host>
+EOF
+} # probe_all
+
+sub exit_usage
+{
+  print <<USAGE;
+Usage: snmp-probe-host.px --host <host> [options]
+
+Options are:
+  -H | --host          Hostname of the device to probe.
+  -C | --config        Path to config file holding the SNMP data blocks.
+  -c | --community     SNMP community to use. Default: `public'.
+  -h | --help          Print this information and exit.
+
+USAGE
+  exit (1);
+}
+
+=head1 NAME
+
+snmp-probe-host.px - Find out what information an SNMP device provides.
+
+=head1 SYNOPSIS
+
+  ./snmp-probe-host.px --host switch01.mycompany.com --community ei2Acoum
+
+=head1 DESCRIPTION
+
+The C<snmp-probe-host.px> script can be used to automatically generate SNMP
+configuration snippets for collectd's snmp plugin (see L<collectd-snmp(5)>).
+
+This script parses the collectd configuration and detecs all "data" blocks that
+are defined for the SNMP plugin. It then queries the device specified on the
+command line for all OIDs and registeres which OIDs could be answered correctly
+and which resulted in an error. With that information the script figures out
+which "data" blocks can be used with this hosts and prints an appropriate
+"host" block to standard output.
+
+The script first tries to contact the device via SNMPv2. If after ten seconds
+no working "data" block has been found, it will try to downgrade to SNMPv1.
+This is a bit a hack, but works for now.
+
+=cut
+
+my $host;
+my $file = '/etc/collectd/collectd.conf';
+my $community = 'public';
+my $conf;
+my $working_data;
+
+=head1 OPTIONS
+
+The following command line options are accepted:
+
+=over 4
+
+=item B<--host> I<hostname>
+
+Hostname of the device. This B<should> be a fully qualified domain name (FQDN),
+but anything the system can resolve to an IP address will word. B<Required
+argument>.
+
+=item B<--config> I<config file>
+
+Sets the name of the collectd config file which defined the SNMP "data" blocks.
+Due to limitations of the config parser used in this script
+(C<Config::General>), C<Include> statements cannot be parsed correctly.
+Defaults to F</etc/collectd/collectd.conf>.
+
+=item B<--community> I<community>
+
+SNMP community to use. Should be pretty straight forward.
+
+=back
+
+=cut
+
+GetOptions ('H|host|hostname=s' => \$host,
+  'C|conf|config=s' => \$file,
+  'c|community=s' => \$community,
+  'h|help' => \&exit_usage) or die;
+
+if (!$host)
+{
+  print STDERR "No hostname given. Please use `--host'.\n";
+  exit (1);
+}
+
+$conf = get_config ($file) or die ("Cannot read config");
+
+if (!defined ($conf->{'plugin'})
+  || !defined ($conf->{'plugin'}{'snmp'})
+  || !defined ($conf->{'plugin'}{'snmp'}{'data'}))
+{
+  print STDERR "Error: No <plugin>, <snmp>, or <data> block found.\n";
+  exit (1);
+}
+
+probe_all ($host, $community, $conf->{'plugin'}{'snmp'}{'data'});
+
+exit (0);
+
+=head1 BUGS
+
+=over 4
+
+=item
+
+C<Include> statements in the config file are not handled correctly.
+
+=item
+
+SNMPv2 / SNMPv1 detection is a hack.
+
+=back
+
+=head1 AUTHOR
+
+Copyright (c) 2008 by Florian octo Forster
+E<lt>octoE<nbsp>atE<nbsp>noris.netE<gt>. Licensed under the terms of the GPLv2.
+Written for the norisE<nbsp>networkE<nbsp>AG L<http://noris.net/>.
+
+=cut
+
+# vim: set sw=2 sts=2 ts=8 et :
index 4c0d5c8..4412b3b 100644 (file)
@@ -21,6 +21,7 @@ if BUILD_FEATURE_DAEMON
 AM_CPPFLAGS += -DPIDFILE='"${localstatedir}/run/${PACKAGE_NAME}.pid"'
 endif
 AM_CPPFLAGS += -DPLUGINDIR='"${pkglibdir}"'
+AM_CPPFLAGS += -DPKGDATADIR='"${pkgdatadir}"'
 
 sbin_PROGRAMS = collectd collectdmon
 bin_PROGRAMS = collectd-nagios
@@ -244,6 +245,7 @@ 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
@@ -254,6 +256,14 @@ collectd_LDADD += "-dlopen" exec.la
 collectd_DEPENDENCIES += exec.la
 endif
 
+if BUILD_PLUGIN_FILECOUNT
+pkglib_LTLIBRARIES += filecount.la
+filecount_la_SOURCES = filecount.c
+filecount_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" filecount.la
+collectd_DEPENDENCIES += filecount.la
+endif
+
 if BUILD_PLUGIN_HDDTEMP
 pkglib_LTLIBRARIES += hddtemp.la
 hddtemp_la_SOURCES = hddtemp.c
@@ -313,6 +323,7 @@ endif
 if BUILD_PLUGIN_IPVS
 pkglib_LTLIBRARIES += ipvs.la
 ipvs_la_SOURCES = ipvs.c
+ipvs_la_CFLAGS = $(KERNEL_CFLAGS)
 ipvs_la_LDFLAGS = -module -avoid-version
 collectd_LDADD += "-dlopen" ipvs.la
 collectd_DEPENDENCIES += ipvs.la
@@ -569,16 +580,12 @@ if BUILD_PLUGIN_PROCESSES
 pkglib_LTLIBRARIES += processes.la
 processes_la_SOURCES = processes.c
 processes_la_LDFLAGS = -module -avoid-version
+processes_la_LIBADD =
 collectd_LDADD += "-dlopen" processes.la
 collectd_DEPENDENCIES += processes.la
+if BUILD_WITH_LIBKVM_GETPROCS
+processes_la_LIBADD += -lkvm
 endif
-
-if BUILD_PLUGIN_QMAIL
-pkglib_LTLIBRARIES += qmail.la
-qmail_la_SOURCES = qmail.c
-qmail_la_LDFLAGS = -module -avoid-version
-collectd_LDADD += "-dlopen" qmail.la
-collectd_DEPENDENCIES += qmail.la
 endif
 
 if BUILD_PLUGIN_RRDCACHED
@@ -650,7 +657,7 @@ endif
 if BUILD_WITH_LIBDEVINFO
 swap_la_LIBADD += -ldevinfo
 endif
-if BUILD_WITH_LIBKVM
+if BUILD_WITH_LIBKVM_GETSWAPINFO
 swap_la_LIBADD += -lkvm
 endif
 if BUILD_WITH_LIBSTATGRAB
@@ -687,8 +694,12 @@ if BUILD_PLUGIN_TCPCONNS
 pkglib_LTLIBRARIES += tcpconns.la
 tcpconns_la_SOURCES = tcpconns.c
 tcpconns_la_LDFLAGS = -module -avoid-version
+tcpconns_la_LIBADD =
 collectd_LDADD += "-dlopen" tcpconns.la
 collectd_DEPENDENCIES += tcpconns.la
+if BUILD_WITH_LIBKVM_NLIST
+tcpconns_la_LIBADD += -lkvm
+endif
 endif
 
 if BUILD_PLUGIN_TEAMSPEAK2
@@ -710,6 +721,7 @@ 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 \
@@ -789,13 +801,24 @@ EXTRA_DIST = types.db
 
 EXTRA_DIST += collectd-email.pod collectd-exec.pod collectd-nagios.pod \
        collectd-perl.pod collectd-snmp.pod collectd-unixsock.pod \
-       collectd.conf.pod collectd.pod collectdmon.pod types.db.pod
+       collectd.conf.pod collectd.pod collectdmon.pod types.db.pod \
+       postgresql_default.conf
 
 .pod.1:
-       pod2man --release=$(VERSION) --center=$(PACKAGE) $< >.pod2man.tmp 2>/dev/null && mv -f .pod2man.tmp $@ || true
+       pod2man --release=$(VERSION) --center=$(PACKAGE) $< \
+               >.pod2man.tmp 2>/dev/null && mv -f .pod2man.tmp $@ || true
+       if grep '\<POD ERRORS\>' $@ >/dev/null 2>&1; \
+       then \
+               echo "$@ has some POD errors!"; false; \
+       fi
 
 .pod.5:
-       pod2man --section=5 --release=$(VERSION) --center=$(PACKAGE) $< >.pod2man.tmp 2>/dev/null && mv -f .pod2man.tmp $@ || true
+       pod2man --section=5 --release=$(VERSION) --center=$(PACKAGE) $< \
+               >.pod2man.tmp 2>/dev/null && mv -f .pod2man.tmp $@ || true
+       if grep '\<POD ERRORS\>' $@ >/dev/null 2>&1; \
+       then \
+               echo "$@ has some POD errors!"; false; \
+       fi
 
 install-exec-hook:
        $(mkinstalldirs) $(DESTDIR)$(sysconfdir)
@@ -806,3 +829,5 @@ install-exec-hook:
                $(INSTALL) -m 0640 collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf; \
        fi; \
        cp -f $(srcdir)/types.db $(DESTDIR)$(pkglibdir)/;
+       $(INSTALL) -D -m 0644 $(srcdir)/postgresql_default.conf \
+               $(DESTDIR)$(pkgdatadir)/postgresql_default.conf;
index 6296335..d312027 100644 (file)
@@ -91,7 +91,7 @@ static void as_submit (const char *type, const char *type_instance,
        sstrncpy (vl.host, hostname_g, sizeof (vl.host));
        sstrncpy (vl.plugin, "apple_sensors", sizeof (vl.plugin));
        sstrncpy (vl.plugin_instance, "", sizeof (vl.plugin_instance));
-       sstrncpy (vl.type, type, sizeof (vl.type))
+       sstrncpy (vl.type, type, sizeof (vl.type));
        sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
 
        plugin_dispatch_values (&vl);
index 9f1bee6..416f339 100644 (file)
@@ -315,7 +315,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 *name)
+static int battery_read_acpi (const char *dir, const char *name,
+               void *user_data)
 {
        double  current = INVALID_VALUE;
        double  voltage = INVALID_VALUE;
@@ -506,7 +507,8 @@ static int battery_read (void)
                        battery_submit ("0", "voltage", voltage);
        }
 
-       walk_directory (battery_acpi_dir, battery_read_acpi);
+       walk_directory (battery_acpi_dir, battery_read_acpi,
+                       /* user_data = */ NULL);
 
 #endif /* KERNEL_LINUX */
 
index 9882601..d935c70 100644 (file)
@@ -85,10 +85,11 @@ description of the format of this file.
 
 The I<OptionList> is an optional list of I<Options>, where each option if a
 key-value-pair. A list of currently understood options can be found below, all
-other options will be ignored.
+other options will be ignored. Values that contain spaces must be quoted with
+double quotes.
 
 I<Valuelist> is a colon-separated list of the time and the values, each either
-an integer if the data-source is a counter, of a double if the data-source if
+an integer if the data-source is a counter, or a double if the data-source is
 of type "gauge". You can submit an undefined gauge-value by using B<U>. When
 submitting B<U> to a counter the behavior is undefined. The time is given as
 epoch (i.E<nbsp>e. standard UNIX time).
@@ -139,10 +140,9 @@ Valid options are:
 =item B<message=>I<Message> (B<REQUIRED>)
 
 Sets the message of the notification. This is the message that will be made
-accessible to the user, so it should contain some useful information. This
-option must be the last option because the rest of the line will be its value,
-even if there are spaces and equal-signs following it! This option is
-mandatory.
+accessible to the user, so it should contain some useful information. As with
+all options: If the message includes spaces, it must be quoted with double
+quotes. This option is mandatory.
 
 =item B<severity=failure>|B<warning>|B<okay> (B<REQUIRED>)
 
@@ -171,11 +171,13 @@ table. All the options are optional, but B<plugin_instance> without B<plugin>
 or B<type_instance> without B<type> doesn't make much sense and should be
 avoided.
 
-Please note that this is the same format as used in the B<unixsock plugin>, see
-L<collectd-unixsock(5)>.
+=back
 
 =back
 
+Please note that this is the same format as used in the B<unixsock plugin>, see
+L<collectd-unixsock(5)>.
+
 When collectd exits it sends a B<SIGTERM> to all still running
 child-processes upon which they have to quit.
 
index b7ae9ca..c3fcb10 100644 (file)
@@ -11,6 +11,10 @@ collectd-perl - Documentation of collectd's C<perl plugin>
     BaseName "Collectd::Plugin"
     EnableDebugger ""
     LoadPlugin "FooBar"
+
+    <Plugin FooBar>
+      Foo "Bar"
+    </Plugin>
   </Plugin>
 
 =head1 DESCRIPTION
@@ -36,6 +40,16 @@ causes the Perl-interpreter to be initialized.
 Prepends I<Name>B<::> to all plugin names loaded after this option. This is
 provided for convenience to keep plugin names short.
 
+=item E<lt>B<Plugin> I<Name>E<gt> block
+
+This block may be used to pass on configuration settings to a Perl plugin. The
+configuration is converted into a config-item data type which is passed to the
+registered configuration callback. See below for details about the config-item
+data type and how to register callbacks.
+
+The I<name> identifies the callback. It is used literally and independent of
+the B<BaseName> setting.
+
 =item B<EnableDebugger> I<Package>[=I<option>,...]
 
 Run collectd under the control of the Perl source debugger. If I<Package> is
@@ -71,6 +85,13 @@ example. The following types of B<callback functions> are known to collectd
 
 =over 4
 
+=item configuration functions
+
+This type of functions is called during configuration if an appropriate
+B<Plugin> block has been encountered. It is called once for each B<Plugin>
+block which matches the name of the callback as provided with the
+B<plugin_register> method - see below.
+
 =item init functions
 
 This type of functions is called once after loading the module and before any
@@ -140,6 +161,19 @@ and collectd:
 
 =over 4
 
+=item Config-Item
+
+A config-item is one structure which keeps the informations provided in the
+configuration file. The array of children keeps one entry for each
+configuration option. Each such entry is another config-item structure, which
+may nest further if nested blocks are used.
+
+  {
+    key      => key,
+    values   => [ val1, val2, ... ],
+    children => [ { ... }, { ... }, ... ]
+  }
+
 =item Data-Set
 
 A data-set is a list of one or more data-sources. Each data-source defines a
@@ -204,6 +238,8 @@ I<type> can be one of:
 
 =over 4
 
+=item TYPE_CONFIG
+
 =item TYPE_INIT
 
 =item TYPE_READ
@@ -248,6 +284,11 @@ arguments:
 
 =over 4
 
+=item TYPE_CONFIG
+
+The only argument passed is I<config-item>. See above for the layout of this
+data type.
+
 =item TYPE_INIT
 
 =item TYPE_READ
@@ -263,8 +304,9 @@ string. For the layout of I<data-set> and I<value-list> see above.
 
 =item TYPE_FLUSH
 
-The only argument passed is I<timeout> which indicates that only data older
-than I<timeout> seconds is to be flushed.
+The arguments passed are I<timeout> and I<identifier>. I<timeout> indicates
+that only data older than I<timeout> seconds is to be flushed. I<identifier>
+specifies which values are to be flushed.
 
 =item TYPE_LOG
 
@@ -299,22 +341,33 @@ 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_flush> ([B<timeout> => I<timeout>,] [B<plugins> => I<...>])
+=item B<plugin_flush> ([B<timeout> => I<timeout>][, B<plugins> => I<...>][,
+B<identifiers> => I<...>])
 
-Flush one or more plugins. I<timeout> is passed on to the registered
-flush-callbacks. If omitted, C<-1> is used. If the I<plugins> argument has
-been specified, only named plugins will be flushed. The argument's value may
-either be a string or a reference to an array of strings.
+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>
+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.
 
 =item B<plugin_flush_one> (I<timeout>, I<plugin>)
 
 This is identical to using "plugin_flush (timeout =E<gt> I<timeout>, plugins
 =E<gt> I<plugin>".
 
+B<Note>: Starting with version 4.5 of collectd, B<plugin_flush_one> has been
+deprecated and will be removed in some future version of collectd. Use
+B<plugin_flush> instead.
+
 =item B<plugin_flush_all> (I<timeout>)
 
 This is identical to using "plugin_flush (timeout =E<gt> I<timeout>)".
 
+B<Note>: Starting with version 4.5 of collectd, B<plugin_flush_all> has been
+deprecated and will be removed in some future version of collectd. Use
+B<plugin_flush> instead.
+
 =item B<plugin_dispatch_notification> (I<notification>)
 
 Submits a I<notification> to the daemon which will then pass it to all
@@ -384,6 +437,8 @@ available (B<:all> will export all of them):
 
 =over 4
 
+=item B<TYPE_CONFIG>
+
 =item B<TYPE_INIT>
 
 =item B<TYPE_READ>
@@ -396,6 +451,8 @@ available (B<:all> will export all of them):
 
 =item B<TYPE_LOG>
 
+=item B<TYPE_DATASET>
+
 =back
 
 =item B<:ds_types>
@@ -546,6 +603,14 @@ reasons. Therefore, END blocks are only executed once when collectd shuts
 down. You should not rely on END blocks anyway - use B<shutdown functions>
 instead.
 
+=item
+
+The perl plugin exports the internal API of collectd which is considered
+unstable and subject to change at any time. We try hard to not break backwards
+compatibility in the Perl API during the life cycle of one major release.
+However, this cannot be guaranteed at all times. Watch out for warnings
+dispatched by the perl plugin after upgrades.
+
 =back
 
 =head1 KNOWN BUGS
index 1e00baf..ca00a6d 100644 (file)
@@ -94,7 +94,8 @@ data-sets is available in the B<types.db> file.
 
 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.
+other options will be ignored. Values that contain spaces must be quoted with
+double quotes.
 
 I<Valuelist> is a colon-separated list of the time and the values, each either
 an integer if the data-source is a counter, or a double if the data-source is
@@ -142,10 +143,9 @@ Valid options are:
 =item B<message=>I<Message> (B<REQUIRED>)
 
 Sets the message of the notification. This is the message that will be made
-accessible to the user, so it should contain some useful information. This
-option must be the last option because the rest of the line will be its value,
-even if there are spaces and equal-signs following it! This option is
-mandatory.
+accessible to the user, so it should contain some useful information. As with
+all options: If the message includes spaces, it must be quoted with double
+quotes. This option is mandatory.
 
 =item B<severity=failure>|B<warning>|B<okay> (B<REQUIRED>)
 
@@ -212,13 +212,15 @@ Value or value-lists are identified in a uniform fashion:
 I<Hostname>/I<Plugin>/I<Type>
 
 Where I<Plugin> and I<Type> are both either of type "I<Name>" or
-"I<Name>-I<Instance>". This sounds more complicated than it is, so here are
+"I<Name>-I<Instance>". If the identifier includes spaces, it must be quoted
+using double quotes. This sounds more complicated than it is, so here are
 some examples:
 
   myhost/cpu-0/cpu-user
   myhost/load/load
   myhost/memory/memory-used
   myhost/disk-sda/disk_octets
+  "myups/snmp/temperature-Outlet 1"
 
 =head1 ABSTRACTION LAYER
 
index 5484322..71eb940 100644 (file)
@@ -50,7 +50,7 @@ static int loop = 0;
 static void *do_flush (void *arg)
 {
        INFO ("Flushing all data.");
-       plugin_flush_all (-1);
+       plugin_flush (NULL, -1, NULL);
        INFO ("Finished flushing all data.");
        pthread_exit (NULL);
        return NULL;
index fa124fb..a09522c 100644 (file)
@@ -13,6 +13,19 @@ FQDNLookup   true
 #Interval     10
 #ReadThreads  5
 
+@BUILD_PLUGIN_LOGFILE_TRUE@LoadPlugin logfile
+@BUILD_PLUGIN_SYSLOG_TRUE@LoadPlugin syslog
+
+#<Plugin logfile>
+#      LogLevel info
+#      File STDOUT
+#      Timestamp true
+#</Plugin>
+
+#<Plugin syslog>
+#      LogLevel info
+#</Plugin>
+
 @BUILD_PLUGIN_APACHE_TRUE@LoadPlugin apache
 @BUILD_PLUGIN_APCUPS_TRUE@LoadPlugin apcups
 @BUILD_PLUGIN_APPLE_SENSORS_TRUE@LoadPlugin apple_sensors
@@ -27,6 +40,7 @@ FQDNLookup   true
 @BUILD_PLUGIN_EMAIL_TRUE@LoadPlugin email
 @BUILD_PLUGIN_ENTROPY_TRUE@LoadPlugin entropy
 @BUILD_PLUGIN_EXEC_TRUE@LoadPlugin exec
+@BUILD_PLUGIN_FILECOUNT_TRUE@LoadPlugin filecount
 @BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
 @BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
 @BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
@@ -35,7 +49,6 @@ FQDNLookup   true
 @BUILD_PLUGIN_IRQ_TRUE@LoadPlugin irq
 @BUILD_PLUGIN_LIBVIRT_TRUE@LoadPlugin libvirt
 @BUILD_PLUGIN_LOAD_TRUE@LoadPlugin load
-@BUILD_PLUGIN_LOGFILE_TRUE@LoadPlugin logfile
 @BUILD_PLUGIN_MBMON_TRUE@LoadPlugin mbmon
 @BUILD_PLUGIN_MEMCACHED_TRUE@LoadPlugin memcached
 @BUILD_PLUGIN_MEMORY_TRUE@LoadPlugin memory
@@ -61,7 +74,6 @@ FQDNLookup   true
 @BUILD_PLUGIN_SERIAL_TRUE@LoadPlugin serial
 @BUILD_PLUGIN_SNMP_TRUE@LoadPlugin snmp
 @BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
-@BUILD_PLUGIN_SYSLOG_TRUE@LoadPlugin syslog
 @BUILD_PLUGIN_TAIL_TRUE@LoadPlugin tail
 @BUILD_PLUGIN_TAPE_TRUE@LoadPlugin tape
 @BUILD_PLUGIN_TCPCONNS_TRUE@LoadPlugin tcpconns
@@ -129,6 +141,15 @@ FQDNLookup   true
 #      NotificationExec "user:group" "/path/to/exec"
 #</Plugin>
 
+#<Plugin filecount>
+#      <Directory "/path/to/dir">
+#              Instance "foodir"
+#              Name "*.conf"
+#              MTime "-5m"
+#              Size "+10k"
+#      </Directory>
+#</Plugin>
+
 @BUILD_PLUGIN_HDDTEMP_TRUE@<Plugin hddtemp>
 #      Host "127.0.0.1"
 #      Port "7634"
@@ -161,12 +182,6 @@ FQDNLookup   true
 #      HostnameFormat name
 #</Plugin>
 
-#<Plugin logfile>
-#      LogLevel info
-#      File STDOUT
-#      Timestamp true
-#</Plugin>
-
 #<Plugin mbmon>
 #      Host "127.0.0.1"
 #      Port "411"
@@ -257,6 +272,13 @@ FQDNLookup   true
 #</Plugin>
 
 #<Plugin postgresql>
+#      <Query magic>
+#              Query "SELECT magic, spells FROM wizard WHERE host = $1;"
+#              Param hostname
+#              Column gauge magic
+#              Column counter spells
+#      </Query>
+#
 #      <Database foo>
 #              Host "hostname"
 #              Port 5432
@@ -265,6 +287,8 @@ FQDNLookup   true
 #
 #              SSLMode "prefer"
 #              KRBSrvName "kerberos_service_name"
+#
+#              Query magic
 #      </Database>
 #
 #      <Database bar>
@@ -352,10 +376,6 @@ FQDNLookup   true
 #   </Host>
 #</Plugin>
 
-#<Plugin syslog>
-#      LogLevel info
-#</Plugin>
-
 #<Plugin "tail">
 #  <File "/var/log/exim4/mainlog">
 #    Instance "exim"
index 250f14d..9f0e34a 100644 (file)
@@ -420,6 +420,79 @@ expected from them. This is documented in great detail in L<collectd-exec(5)>.
 
 =back
 
+=head2 Plugin C<filecount>
+
+The C<filecount> plugin counts the number of files in a certain directory (and
+its subdirectories) and their combined size. The configuration is very straight
+forward:
+
+  <Plugin "filecount">
+    <Directory "/var/qmail/queue/mess">
+      Instance "qmail-message"
+    </Directory>
+    <Directory "/var/qmail/queue/todo">
+      Instance "qmail-todo"
+    </Directory>
+    <Directory "/var/lib/php5">
+      Instance "php5-sessions"
+      Name "sess_*"
+    </Directory>
+  </Plugin>
+
+The example above counts the number of files in QMail's queue directories and
+the number of PHP5 sessions. Jfiy: The "todo" queue holds the messages that
+QMail has not yet looked at, the "message" queue holds the messages that were
+classified into "local" and "remote".
+
+As you can see, the configuration consists of one or more C<Directory> blocks,
+each of which specifies a directory in which to count the files. Within those
+blocks, the following options are recognized:
+
+=over 4
+
+=item B<Instance> I<Instance>
+
+Sets the plugin instance to I<Instance>. That instance name must be unique, but
+it's your responsibility, the plugin doesn't check for that. If not given, the
+instance is set to the directory name with all slashes replaced by underscores
+and all leading underscores removed.
+
+=item B<Name> I<Pattern>
+
+Only count files that match I<Pattern>, where I<Pattern> is a shell-like
+wildcard as understood by L<fnmatch(3)>. Only the B<filename> is checked
+against the pattern, not the entire path. In case this makes it easier for you:
+This option has been named after the B<-name> parameter to L<find(1)>.
+
+=item B<MTime> I<Age>
+
+Count only files of a specific age: If I<Age> is greater than zero, only files
+that haven't been touched in the last I<Age> seconds are counted. If I<Age> is
+a negative number, this is inversed. For example, if B<-60> is specified, only
+files that have been modified in the last minute will be counted.
+
+The number can also be followed by a "multiplier" to easily specify a larger
+timespan. When given in this notation, the argument must in quoted, i.E<nbsp>e.
+must be passed as string. So the B<-60> could also be written as B<"-1m"> (one
+minute). Valid multipliers are C<s> (second), C<m> (minute), C<h> (hour), C<d>
+(day), C<w> (week), and C<y> (year). There is no "month" multiplier. You can
+also specify fractional numbers, e.E<nbsp>g. B<"0.5d"> is identical to
+B<"12h">.
+
+=item B<Size> I<Size>
+
+Count only files of a specific size. When I<Size> is a positive number, only
+files that are at least this big are counted. If I<Size> is a negative number,
+this is inversed, i.E<nbsp>e. only files smaller than the absolute value of
+I<Size> are counted.
+
+As with the B<MTime> option, a "multiplier" may be added. For a detailed
+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.
+
+=back
+
 =head2 Plugin C<hddtemp>
 
 To get values from B<hddtemp> collectd connects to B<localhost> (127.0.0.1),
@@ -472,6 +545,38 @@ other interfaces are collected.
 
 =back
 
+=head2 Plugin C<ipmi>
+
+=over 4
+
+=item B<Sensor> I<Sensor>
+
+Selects sensors to collect or to ignore, depending on B<IgnoreSelected>.
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+If no configuration if given, the B<ipmi> plugin will collect data from all
+sensors found of type "temperature", "voltage", "current" and "fanspeed".
+This option enables you to do that: By setting B<IgnoreSelected> to I<true>
+the effect of B<Sensor> is inverted: All selected sensors are ignored and
+all other sensors are collected.
+
+=item B<NotifySensorAdd> I<true>|I<false>
+
+If a sensor appears after initialization time of a minute a notification
+is sent.
+
+=item B<NotifySensorRemove> I<true>|I<false>
+
+If a sensor disappears a notification is sent.
+
+=item B<NotifySensorNotPresent> I<true>|I<false>
+
+If you have for example dual power supply and one of them is (un)plugged then
+a notification is sent.
+
+=back
+
 =head2 Plugin C<iptables>
 
 =over 4
@@ -934,9 +1039,18 @@ L<upsc(8)>.
 
 =head2 Plugin C<onewire>
 
+B<EXPERIMENTAL!> See notes below.
+
 The C<onewire> plugin uses the B<owcapi> library from the B<owfs> project
 L<http://owfs.org/> to read sensors connected via the onewire bus.
 
+Currently only temperature sensors (sensors with the family code C<10>,
+e.E<nbsp>g. DS1820, DS18S20, DS1920) can be read. If you have other sensors you
+would like to have included, please send a sort request to the mailing list.
+
+Hubs (the DS2409 chips) are working, but read the note, why this plugin is
+experimental, below.
+
 =over 4
 
 =item B<Device> I<Device>
@@ -953,6 +1067,8 @@ with that version, the following configuration worked for us:
     Device "-s localhost:4304"
   </Plugin>
 
+This directive is B<required> and does not have a default value.
+
 =item B<Sensor> I<Sensor>
 
 Selects sensors to collect or to ignore, depending on B<IgnoreSelected>, see
@@ -972,6 +1088,17 @@ interfaces are collected.
 
 =back
 
+B<EXPERIMENTAL!> The C<onewire> plugin is experimental, because it doesn't yet
+work with big setups. It works with one sensor being attached to one
+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>
+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<perl>
 
 This plugin embeds a Perl-interpreter into collectd and provides an interface
@@ -996,16 +1123,31 @@ Sets the Time-To-Live of generated ICMP packets.
 
 The C<postgresql> plugin queries statistics from PostgreSQL databases. It
 keeps a persistent connection to all configured databases and tries to
-reconnect if the connection has been interrupted. The statistics are collected
-from PostgreSQL's B<statistics collector> which thus has to be enabled for
-this plugin to work correctly. This should usually be the case by default.
-See the section "The Statistics Collector" of the B<PostgreSQL Documentation>
-for details.
+reconnect if the connection has been interrupted. A database is configured by
+specifying a B<Database> block as described below. The default statistics are
+collected from PostgreSQL's B<statistics collector> which thus has to be
+enabled for this plugin to work correctly. This should usually be the case by
+default. See the section "The Statistics Collector" of the B<PostgreSQL
+Documentation> for details.
+
+By specifying custom database queries using a B<Query> block as described
+below, you may collect any data that is available from some PostgreSQL
+database. This way, you are able to access statistics of external daemons
+which are available in a PostgreSQL database or use future or special
+statistics provided by PostgreSQL without the need to upgrade your collectd
+installation.
 
 The B<PostgreSQL Documentation> manual can be found at
 L<http://www.postgresql.org/docs/manuals/>.
 
   <Plugin postgresql>
+    <Query magic>
+      Query "SELECT magic, spells FROM wizard WHERE host = $1;"
+      Param hostname
+      Column gauge magic
+      Column counter spells
+    </Query>
+
     <Database foo>
       Host "hostname"
       Port "5432"
@@ -1013,15 +1155,121 @@ L<http://www.postgresql.org/docs/manuals/>.
       Password "secret"
       SSLMode "prefer"
       KRBSrvName "kerberos_service_name"
+      Query magic
     </Database>
     <Database bar>
       Service "service_name"
     </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:
+
+=over 4
+
+=item B<Query> I<sql query>
+
+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
+B<Param> configuration option - see below for details. To include a literal
+B<$> character followed by a number, surround it with single quotes (B<'>).
+
+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>
+
+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
+they appear in the configuration file. The value of the parameter is
+determined depending on the value of the B<Param> option as follows:
+
+=over 4
+
+=item I<hostname>
+
+The configured hostname of the database connection. If a UNIX domain socket is
+used, the parameter expands to "localhost".
+
+=item I<database>
+
+The name of the database of the current connection.
+
+=item I<username>
+
+The username used to connect to the database.
+
+=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<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.
+
+=item B<MinPGVersion> I<version>
+
+=item B<MaxPGVersion> I<version>
+
+Specify the minimum or maximum version of PostgreSQL that this query should be
+used with. Some statistics might only be available with certain versions of
+PostgreSQL. This allows you to specify multiple queries with the same name but
+which apply to different versions, thus allowing you to use the same
+configuration in a heterogeneous environment.
+
+The I<version> has to be specified as the concatenation of the major, minor
+and patch-level versions, each represented as two-decimal-digit numbers. For
+example, version 8.2.3 will become 80203.
+
+=back
+
+The following predefined queries are available (the definitions can be found
+in the F<postgresql_default.conf> file which, by default, is available at
+C<I<prefix>/share/collectd/>):
+
 =over 4
 
-=item B<Database> block
+=item B<backends>
+
+This query collects the number of backends, i.E<nbsp>e. the number of
+connected clients.
+
+=item B<transactions>
+
+This query collects the numbers of committed and rolled-back transactions of
+the user tables.
+
+=item B<queries>
+
+This query collects the numbers of various table modifications (i.E<nbsp>e.
+insertions, updates, deletions) of the user tables.
+
+=item B<query_plans>
+
+This query collects the numbers of various table scans and returned tuples of
+the user tables.
+
+=item B<table_states>
+
+This query collects the numbers of live and dead rows in the user tables.
+
+=item B<disk_io>
+
+This query collects disk block access counts for user tables.
+
+=item B<disk_usage>
+
+This query collects the on-disk size of the database in bytes.
+
+=back
 
 The B<Database> block defines one PostgreSQL database for which to collect
 statistics. It accepts a single mandatory argument which specifies the
@@ -1031,6 +1279,8 @@ L<psql(1)> manpage. However, be aware that those defaults may be influenced by
 the user collectd is run as and special environment variables. See the manpage
 for details.
 
+=over 4
+
 =item B<Host> I<hostname>
 
 Specify the hostname or IP of the PostgreSQL server to connect to. If the
@@ -1094,6 +1344,14 @@ service has to be defined in F<pg_service.conf> and holds additional
 connection parameters. See the section "The Connection Service File" in the
 B<PostgreSQL Documentation> for details.
 
+=item B<Query> I<query>
+
+Specify a I<query> which should be executed for the database connection. This
+may be any of the predefined or user-defined queries. If no such option is
+given, it defaults to "backends", "transactions", "queries", "query_plans",
+"table_states", "disk_io" and "disk_usage". Else, the specified queries are
+used only.
+
 =back
 
 =head2 Plugin C<powerdns>
@@ -1350,7 +1608,7 @@ Set the "XFiles Factor". The default is 0.1. If unsure, don't set this option.
 
 =item B<CacheFlush> I<Seconds>
 
-When the C<rrdtool plugin> uses a cache (by setting B<CacheTimeout>, see below)
+When the C<rrdtool> plugin uses a cache (by setting B<CacheTimeout>, see below)
 it writes all values for a certain RRD-file if the oldest value is older than
 (or equal to) the number of seconds specified. If some RRD-file is not updated
 anymore for some reason (the computer was shut down, the network is broken,
@@ -1369,6 +1627,30 @@ 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>
+
+When collecting many statistics with collectd and the C<rrdtool> plugin, you
+will run serious performance problems. The B<CacheFlush> setting and the
+internal update queue assert that collectd continues to work just fine even
+under heavy load, but the system may become very unresponsive and slow. This is
+a problem especially if create graphs from the RRD files on the same machine,
+for example using the C<graph.cgi> script included in the
+C<contrib/collection3/> directory.
+
+This setting is designed for very large setups. Setting this option to a value
+between 25 and 80 updates per second, depending on your hardware, will leave
+the server responsive enough to draw graphs even while all the cached values
+are written to disk. Flushed values, i.E<nbsp>e. values that are forced to disk
+by the B<FLUSH> command, are B<not> effected by this limit. They are still
+written as fast as possible, so that web frontends have up to date data when
+generating graphs.
+
+For example: If you have 100,000 RRD files and set B<WritesPerSecond> to 30
+updates per second, writing all values to disk will take approximately
+56E<nbsp>minutes. Together with the flushing ability that's integrated into
+"collection3" you'll end up with a responsive and fast system, up to date
+graphs and basically a "backup" of your values every hour.
+
 =back
 
 =head2 Plugin C<sensors>
@@ -1441,7 +1723,7 @@ user using (extended) regular expressions, as described in L<regex(7)>.
       <Match>
        Regex "\\<R=local_user\\>"
        DSType "CounterInc"
-       Type "email_count"
+       Type "counter"
        Instance "local_user"
       </Match>
     </File>
@@ -1560,6 +1842,8 @@ This option, although numeric, needs to be a string, i.E<nbsp>e. you B<must>
 use quotes around it! If no such statement is given only global information
 will be collected.
 
+=back
+
 =head2 Plugin C<tcpconns>
 
 The C<tcpconns plugin> counts the number of currently established TCP
@@ -1570,8 +1854,6 @@ fine-tune the ports you are interested in:
 
 =over 4
 
-=back
-
 =item B<ListeningPorts> I<true>|I<false>
 
 If this option is set to I<true>, statistics for all local ports for which a
index a262bf1..196530f 100644 (file)
 #define PLUGINDIR PREFIX "/lib/" PACKAGE_NAME
 #endif
 
+#ifndef PKGDATADIR
+#define PKGDATADIR PREFIX "/share/" PACKAGE_NAME
+#endif
+
 #ifndef COLLECTD_GRP_NAME
 # define COLLECTD_GRP_NAME "collectd"
 #endif
index fa27e77..c10ae78 100644 (file)
@@ -116,6 +116,8 @@ internal caches. E.E<nbsp>g. the C<rrdtool plugin> will write all pending data
 to the RRD files. This is the same as using the C<FLUSH -1> command of the
 C<unixsock plugin>.
 
+=back
+
 =head1 SEE ALSO
 
 L<collectd.conf(5)>,
index fd7c199..61a759b 100644 (file)
@@ -865,32 +865,44 @@ int notification_init (notification_t *n, int severity, const char *message,
        return (0);
 } /* int notification_init */
 
-int walk_directory (const char *dir, dirwalk_callback_f callback)
+int walk_directory (const char *dir, dirwalk_callback_f callback,
+               void *user_data)
 {
        struct dirent *ent;
        DIR *dh;
-       int ok = 0;
+       int success;
+       int failure;
+
+       success = 0;
+       failure = 0;
 
        if ((dh = opendir (dir)) == NULL)
        {
                char errbuf[1024];
-               ERROR ("Cannot open '%s': %s", dir,
+               ERROR ("walk_directory: Cannot open '%s': %s", dir,
                                sstrerror (errno, errbuf, sizeof (errbuf)));
                return -1;
        }
 
        while ((ent = readdir (dh)) != NULL)
        {
+               int status;
+
                if (ent->d_name[0] == '.')
                        continue;
 
-               if (!callback(ent->d_name))
-                       ++ok;
+               status = (*callback) (dir, ent->d_name, user_data);
+               if (status != 0)
+                       failure++;
+               else
+                       success++;
        }
 
        closedir (dh);
 
-       return ok ? 0 : -1;
+       if ((success == 0) && (failure > 0))
+               return (-1);
+       return (0);
 }
 
 int read_file_contents (const char *filename, char *buf, int bufsize)
index a5e2167..f463b77 100644 (file)
 #endif
 
 #define sfree(ptr) \
-       if((ptr) != NULL) { \
-               free(ptr); \
-       } \
-       (ptr) = NULL
+       do { \
+               if((ptr) != NULL) { \
+                       free(ptr); \
+               } \
+               (ptr) = NULL; \
+       } while (0)
 
 #define STATIC_ARRAY_SIZE(a) (sizeof (a) / sizeof (*(a)))
 
@@ -201,8 +203,10 @@ int notification_init (notification_t *n, int severity, const char *message,
                        (vl)->host, (vl)->plugin, (vl)->plugin_instance, \
                        (ds)->type, (vl)->type_instance)
 
-typedef int (*dirwalk_callback_f)(const char *filename);
-int walk_directory (const char *dir, dirwalk_callback_f callback);
+typedef int (*dirwalk_callback_f)(const char *dirname, const char *filename,
+               void *user_data);
+int walk_directory (const char *dir, dirwalk_callback_f callback,
+               void *user_data);
 int read_file_contents (const char *filename, char *buf, int bufsize);
 
 #endif /* COMMON_H */
index a80de1e..711ec99 100644 (file)
@@ -490,19 +490,19 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out, int *fd_err)
 
 static int parse_line (char *buffer) /* {{{ */
 {
-  char *fields[256];
-  int fields_num;
-
-  fields[0] = "PUTVAL";
-  fields_num = strsplit (buffer, fields + 1, STATIC_ARRAY_SIZE(fields) - 1);
-
-  if (strcasecmp (fields[1], "putval") == 0)
-    return (handle_putval (stdout, fields + 1, fields_num));
-  else if (strcasecmp (fields[1], "putnotif") == 0)
-    return (handle_putnotif (stdout, fields + 1, fields_num));
-
-  /* compatibility code */
-  return (handle_putval (stdout, fields, fields_num + 1));
+  if (strncasecmp ("PUTVAL", buffer, strlen ("PUTVAL")) == 0)
+    return (handle_putval (stdout, buffer));
+  else if (strncasecmp ("PUTNOTIF", buffer, strlen ("PUTNOTIF")) == 0)
+    return (handle_putnotif (stdout, buffer));
+  else
+  {
+    /* For backwards compatibility */
+    char tmp[1220];
+    /* Let's annoy the user a bit.. */
+    INFO ("exec plugin: Prepending `PUTVAL' to this line: %s", buffer);
+    ssnprintf (tmp, sizeof (tmp), "PUTVAL %s", buffer);
+    return (handle_putval (stdout, tmp));
+  }
 } /* int parse_line }}} */
 
 static void *exec_read_one (void *arg) /* {{{ */
diff --git a/src/filecount.c b/src/filecount.c
new file mode 100644 (file)
index 0000000..55b3655
--- /dev/null
@@ -0,0 +1,546 @@
+/**
+ * collectd - src/filecount.c
+ * Copyright (C) 2008  Alessandro Iurlano
+ * 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:
+ *   Alessandro Iurlano <alessandro.iurlano at gmail.com>
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"       
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <fnmatch.h>
+
+struct fc_directory_conf_s
+{
+  char *path;
+  char *instance;
+
+  /* Data counters */
+  uint64_t files_num;
+  uint64_t files_size;
+
+  /* Selectors */
+  char *name;
+  int64_t mtime;
+  int64_t size;
+
+  /* Helper for the recursive functions */
+  time_t now;
+};
+typedef struct fc_directory_conf_s fc_directory_conf_t;
+
+static fc_directory_conf_t **directories = NULL;
+static size_t directories_num = 0;
+
+static void fc_submit_dir (const fc_directory_conf_t *dir)
+{
+  value_t values[1];
+  value_list_t vl = VALUE_LIST_INIT;
+
+  values[0].gauge = (gauge_t) dir->files_num;
+
+  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));
+  sstrncpy (vl.type, "files", sizeof (vl.type));
+
+  plugin_dispatch_values (&vl);
+
+  values[0].gauge = (gauge_t) dir->files_size;
+  sstrncpy (vl.type, "bytes", sizeof (vl.type));
+
+  plugin_dispatch_values (&vl);
+} /* void fc_submit_dir */
+
+/*
+ * Config:
+ * <Plugin filecount>
+ *   <Directory /path/to/dir>
+ *     Instance "foobar"
+ *     Name "*.conf"
+ *     MTime -3600
+ *     Size "+10M"
+ *   </Directory>
+ * </Plugin>
+ *
+ * Collect:
+ * - Number of files
+ * - Total size
+ */
+
+static int fc_config_set_instance (fc_directory_conf_t *dir, const char *str)
+{
+  char buffer[1024];
+  char *ptr;
+  char *copy;
+
+  sstrncpy (buffer, str, sizeof (buffer));
+  for (ptr = buffer; *ptr != 0; ptr++)
+    if (*ptr == '/')
+      *ptr = '_';
+
+  for (ptr = buffer; *ptr == '_'; ptr++)
+    /* do nothing */;
+
+  if (*ptr == 0)
+    return (-1);
+
+  copy = strdup (ptr);
+  if (copy == NULL)
+    return (-1);
+
+  sfree (dir->instance);
+  dir->instance = copy;
+
+  return (0);
+} /* int fc_config_set_instance */
+
+static int fc_config_add_dir_instance (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("filecount plugin: The `Instance' config option needs exactly "
+        "one string argument.");
+    return (-1);
+  }
+
+  return (fc_config_set_instance (dir, ci->values[0].value.string));
+} /* int fc_config_add_dir_instance */
+
+static int fc_config_add_dir_name (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  char *temp;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("filecount plugin: The `Name' config option needs exactly one "
+        "string argument.");
+    return (-1);
+  }
+
+  temp = strdup (ci->values[0].value.string);
+  if (temp == NULL)
+  {
+    ERROR ("filecount plugin: strdup failed.");
+    return (-1);
+  }
+
+  sfree (dir->name);
+  dir->name = temp;
+
+  return (0);
+} /* int fc_config_add_dir_name */
+
+static int fc_config_add_dir_mtime (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  char *endptr;
+  double temp;
+
+  if ((ci->values_num != 1)
+      || ((ci->values[0].type != OCONFIG_TYPE_STRING)
+        && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
+  {
+    WARNING ("filecount plugin: The `MTime' config option needs exactly one "
+        "string or numeric argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
+  {
+    dir->mtime = (int64_t) ci->values[0].value.number;
+    return (0);
+  }
+
+  errno = 0;
+  endptr = NULL;
+  temp = strtod (ci->values[0].value.string, &endptr);
+  if ((errno != 0) || (endptr == NULL)
+      || (endptr == ci->values[0].value.string))
+  {
+    WARNING ("filecount plugin: Converting `%s' to a number failed.",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  switch (*endptr)
+  {
+    case 0:
+    case 's':
+    case 'S':
+      break;
+
+    case 'm':
+    case 'M':
+      temp *= 60;
+      break;
+
+    case 'h':
+    case 'H':
+      temp *= 3600;
+      break;
+
+    case 'd':
+    case 'D':
+      temp *= 86400;
+      break;
+
+    case 'w':
+    case 'W':
+      temp *= 7 * 86400;
+      break;
+
+    case 'y':
+    case 'Y':
+      temp *= 31557600; /* == 365.25 * 86400 */
+      break;
+
+    default:
+      WARNING ("filecount plugin: Invalid suffix for `MTime': `%c'", *endptr);
+      return (-1);
+  } /* switch (*endptr) */
+
+  dir->mtime = (int64_t) temp;
+
+  return (0);
+} /* int fc_config_add_dir_mtime */
+
+static int fc_config_add_dir_size (fc_directory_conf_t *dir,
+    oconfig_item_t *ci)
+{
+  char *endptr;
+  double temp;
+
+  if ((ci->values_num != 1)
+      || ((ci->values[0].type != OCONFIG_TYPE_STRING)
+        && (ci->values[0].type != OCONFIG_TYPE_NUMBER)))
+  {
+    WARNING ("filecount plugin: The `Size' config option needs exactly one "
+        "string or numeric argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].type == OCONFIG_TYPE_NUMBER)
+  {
+    dir->size = (int64_t) ci->values[0].value.number;
+    return (0);
+  }
+
+  errno = 0;
+  endptr = NULL;
+  temp = strtod (ci->values[0].value.string, &endptr);
+  if ((errno != 0) || (endptr == NULL)
+      || (endptr == ci->values[0].value.string))
+  {
+    WARNING ("filecount plugin: Converting `%s' to a number failed.",
+        ci->values[0].value.string);
+    return (-1);
+  }
+
+  switch (*endptr)
+  {
+    case 0:
+    case 'b':
+    case 'B':
+      break;
+
+    case 'k':
+    case 'K':
+      temp *= 1000.0;
+      break;
+
+    case 'm':
+    case 'M':
+      temp *= 1000.0 * 1000.0;
+      break;
+
+    case 'g':
+    case 'G':
+      temp *= 1000.0 * 1000.0 * 1000.0;
+      break;
+
+    case 't':
+    case 'T':
+      temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0;
+      break;
+
+    case 'p':
+    case 'P':
+      temp *= 1000.0 * 1000.0 * 1000.0 * 1000.0 * 1000.0;
+      break;
+
+    default:
+      WARNING ("filecount plugin: Invalid suffix for `Size': `%c'", *endptr);
+      return (-1);
+  } /* switch (*endptr) */
+
+  dir->size = (int64_t) temp;
+
+  return (0);
+} /* int fc_config_add_dir_size */
+
+static int fc_config_add_dir (oconfig_item_t *ci)
+{
+  fc_directory_conf_t *dir;
+  int status;
+  int i;
+
+  if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("filecount plugin: `Directory' needs exactly one string "
+        "argument.");
+    return (-1);
+  }
+
+  /* Initialize `dir' */
+  dir = (fc_directory_conf_t *) malloc (sizeof (*dir));
+  if (dir == NULL)
+  {
+    ERROR ("filecount plugin: malloc failed.");
+    return (-1);
+  }
+  memset (dir, 0, sizeof (*dir));
+
+  dir->path = strdup (ci->values[0].value.string);
+  if (dir->path == NULL)
+  {
+    ERROR ("filecount plugin: strdup failed.");
+    return (-1);
+  }
+
+  fc_config_set_instance (dir, dir->path);
+
+  dir->name = NULL;
+  dir->mtime = 0;
+  dir->size = 0;
+
+  status = 0;
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+
+    if (strcasecmp ("Instance", option->key) == 0)
+      status = fc_config_add_dir_instance (dir, option);
+    else if (strcasecmp ("Name", option->key) == 0)
+      status = fc_config_add_dir_name (dir, option);
+    else if (strcasecmp ("MTime", option->key) == 0)
+      status = fc_config_add_dir_mtime (dir, option);
+    else if (strcasecmp ("Size", option->key) == 0)
+      status = fc_config_add_dir_size (dir, option);
+    else
+    {
+      WARNING ("filecount plugin: fc_config_add_dir: "
+          "Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  } /* for (ci->children) */
+
+  if (status == 0)
+  {
+    fc_directory_conf_t **temp;
+
+    temp = (fc_directory_conf_t **) realloc (directories,
+        sizeof (*directories) * directories_num);
+    if (temp == NULL)
+    {
+      ERROR ("filecount plugin: realloc failed.");
+      status = -1;
+    }
+    else
+    {
+      directories = temp;
+      directories[directories_num] = dir;
+      directories_num++;
+    }
+  }
+
+  if (status != 0)
+  {
+    sfree (dir->name);
+    sfree (dir->instance);
+    sfree (dir->path);
+    sfree (dir);
+    return (-1);
+  }
+
+  return (0);
+} /* int fc_config_add_dir */
+
+static int fc_config (oconfig_item_t *ci)
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if (strcasecmp ("Directory", child->key) == 0)
+      fc_config_add_dir (child);
+    else
+    {
+      WARNING ("filecount plugin: Ignoring unknown config option `%s'.",
+          child->key);
+    }
+  } /* for (ci->children) */
+
+  return (0);
+} /* int fc_config */
+
+static int fc_init (void)
+{
+  if (directories_num < 1)
+  {
+    WARNING ("filecount plugin: No directories have been configured.");
+    return (-1);
+  }
+
+  return (0);
+} /* int fc_init */
+
+static int fc_read_dir_callback (const char *dirname, const char *filename,
+    void *user_data)
+{
+  fc_directory_conf_t *dir = user_data;
+  char abs_path[PATH_MAX];
+  struct stat statbuf;
+  int status;
+
+  if (dir == NULL)
+    return (-1);
+
+  ssnprintf (abs_path, sizeof (abs_path), "%s/%s", dirname, filename);
+
+  status = lstat (abs_path, &statbuf);
+  if (status != 0)
+  {
+    ERROR ("filecount plugin: stat (%s) failed.", abs_path);
+    return (-1);
+  }
+
+  if (S_ISDIR (statbuf.st_mode))
+  {
+    status = walk_directory (abs_path, fc_read_dir_callback, dir);
+    return (status);
+  }
+  else if (!S_ISREG (statbuf.st_mode))
+  {
+    return (0);
+  }
+
+  if (dir->name != NULL)
+  {
+    status = fnmatch (dir->name, filename, /* flags = */ 0);
+    if (status != 0)
+      return (0);
+  }
+
+  if (dir->mtime != 0)
+  {
+    time_t mtime = dir->now;
+
+    if (dir->mtime < 0)
+      mtime += dir->mtime;
+    else
+      mtime -= dir->mtime;
+
+    DEBUG ("filecount plugin: Only collecting files that were touched %s %u.",
+        (dir->mtime < 0) ? "after" : "before",
+        (unsigned int) mtime);
+
+    if (((dir->mtime < 0) && (statbuf.st_mtime < mtime))
+        || ((dir->mtime > 0) && (statbuf.st_mtime > mtime)))
+      return (0);
+  }
+
+  if (dir->size != 0)
+  {
+    off_t size;
+
+    if (dir->size < 0)
+      size = (off_t) ((-1) * dir->size);
+    else
+      size = (off_t) dir->size;
+
+    if (((dir->size < 0) && (statbuf.st_size > size))
+        || ((dir->size > 0) && (statbuf.st_size < size)))
+      return (0);
+  }
+
+  dir->files_num++;
+  dir->files_size += (uint64_t) statbuf.st_size;
+
+  return (0);
+} /* int fc_read_dir_callback */
+
+static int fc_read_dir (fc_directory_conf_t *dir)
+{
+  int status;
+
+  dir->files_num = 0;
+  dir->files_size = 0;
+
+  if (dir->mtime != 0)
+    dir->now = time (NULL);
+
+  status = walk_directory (dir->path, fc_read_dir_callback, dir);
+  if (status != 0)
+  {
+    WARNING ("filecount plugin: walk_directory (%s) failed.", dir->path);
+    return (-1);
+  }
+
+  fc_submit_dir (dir);
+
+  return (0);
+} /* int fc_read_dir */
+
+static int fc_read (void)
+{
+  size_t i;
+
+  for (i = 0; i < directories_num; i++)
+    fc_read_dir (directories[i]);
+
+  return (0);
+} /* int fc_read */
+
+void module_register (void)
+{
+  plugin_register_complex_config ("filecount", fc_config);
+  plugin_register_init ("filecount", fc_init);
+  plugin_register_read ("filecount", fc_read);
+} /* void module_register */
+
+/*
+ * vim: set sw=2 sts=2 et :
+ */
index b9cfa19..e219e89 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/ipmi.c
  * Copyright (C) 2008  Florian octo Forster
+ * Copyright (C) 2008  Peter Holik
  *
  * 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>
+ *   Peter Holik <peter at holik.at>
  **/
 
 #include "collectd.h"
@@ -41,6 +43,9 @@ typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t;
 struct c_ipmi_sensor_list_s
 {
   ipmi_sensor_id_t sensor_id;
+  char sensor_name[DATA_MAX_NAME_LEN];
+  char sensor_type[DATA_MAX_NAME_LEN];
+  int sensor_not_present;
   c_ipmi_sensor_list_t *next;
 };
 
@@ -50,18 +55,26 @@ struct c_ipmi_sensor_list_s
 static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER;
 static c_ipmi_sensor_list_t *sensor_list = NULL;
 
+static int c_ipmi_init_in_progress = 0;
 static int c_ipmi_active = 0;
 static pthread_t thread_id = (pthread_t) 0;
 
 static const char *config_keys[] =
 {
        "Sensor",
-       "IgnoreSelected"
+       "IgnoreSelected",
+       "NotifySensorAdd",
+       "NotifySensorRemove",
+       "NotifySensorNotPresent"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 static ignorelist_t *ignorelist = NULL;
 
+static int c_ipmi_nofiy_add = 0;
+static int c_ipmi_nofiy_remove = 0;
+static int c_ipmi_nofiy_notpresent = 0;
+
 /*
  * Misc private functions
  */
@@ -106,36 +119,73 @@ static void sensor_read_handler (ipmi_sensor_t *sensor,
   value_t values[1];
   value_list_t vl = VALUE_LIST_INIT;
 
-  char sensor_name[IPMI_SENSOR_NAME_LEN];
-  char *sensor_name_ptr;
-  int sensor_type;
-  const char *type;
-
-  memset (sensor_name, 0, sizeof (sensor_name));
-  ipmi_sensor_get_name (sensor, sensor_name, sizeof (sensor_name));
-  sensor_name[sizeof (sensor_name) - 1] = 0;
-
-  sensor_name_ptr = strstr (sensor_name, ").");
-  if (sensor_name_ptr == NULL)
-    sensor_name_ptr = sensor_name;
-  else
-    sensor_name_ptr += 2;
+  c_ipmi_sensor_list_t *list_item = (c_ipmi_sensor_list_t *)user_data;
 
   if (err != 0)
   {
-    INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
-        "because it failed with status %#x.",
-        sensor_name_ptr, err);
-    sensor_list_remove (sensor);
+    if ((err & 0xff) == IPMI_NOT_PRESENT_CC)
+    {
+      if (list_item->sensor_not_present == 0)
+      {
+        list_item->sensor_not_present = 1;
+
+        INFO ("ipmi plugin: sensor_read_handler: sensor %s "
+            "not present.", list_item->sensor_name);
+
+        if (c_ipmi_nofiy_notpresent)
+        {
+          notification_t n = { NOTIF_WARNING, time(NULL), "", "", "ipmi",
+            "", "", "", NULL };
+
+          sstrncpy (n.host, hostname_g, sizeof (n.host));
+          sstrncpy (n.type_instance, list_item->sensor_name,
+              sizeof (n.type_instance));
+          sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+          ssnprintf (n.message, sizeof (n.message),
+              "sensor %s not present", list_item->sensor_name);
+
+          plugin_dispatch_notification (&n);
+        }
+      }
+    }
+    else
+    {
+      INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+          "because it failed with status %#x.",
+          list_item->sensor_name, err);
+      sensor_list_remove (sensor);
+    }
     return;
   }
+  else if (list_item->sensor_not_present == 1)
+  {
+    list_item->sensor_not_present = 0;
+
+    INFO ("ipmi plugin: sensor_read_handler: sensor %s present.",
+        list_item->sensor_name);
+
+    if (c_ipmi_nofiy_notpresent)
+    {
+      notification_t n = { NOTIF_OKAY, time(NULL), "", "", "ipmi",
+        "", "", "", NULL };
+
+      sstrncpy (n.host, hostname_g, sizeof (n.host));
+      sstrncpy (n.type_instance, list_item->sensor_name,
+          sizeof (n.type_instance));
+      sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+      ssnprintf (n.message, sizeof (n.message),
+          "sensor %s present", list_item->sensor_name);
+
+      plugin_dispatch_notification (&n);
+    }
+  }
 
   if (value_present != IPMI_BOTH_VALUES_PRESENT)
   {
     INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
         "because it provides %s. If you need this sensor, "
         "please file a bug report.",
-        sensor_name_ptr,
+        list_item->sensor_name,
         (value_present == IPMI_RAW_VALUE_PRESENT)
         ? "only the raw value"
         : "no value");
@@ -143,13 +193,60 @@ static void sensor_read_handler (ipmi_sensor_t *sensor,
     return;
   }
 
-  /* Both `ignorelist' and `plugin_instance' may be NULL. */
-  if (ignorelist_match (ignorelist, sensor_name_ptr) != 0)
+  values[0].gauge = value;
+
+  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));
+  sstrncpy (vl.type, list_item->sensor_type, sizeof (vl.type));
+  sstrncpy (vl.type_instance, list_item->sensor_name, sizeof (vl.type_instance));
+
+  plugin_dispatch_values (&vl);
+} /* void sensor_read_handler */
+
+static int sensor_list_add (ipmi_sensor_t *sensor)
+{
+  ipmi_sensor_id_t sensor_id;
+  c_ipmi_sensor_list_t *list_item;
+  c_ipmi_sensor_list_t *list_prev;
+
+  char sensor_name[DATA_MAX_NAME_LEN];
+  char *sensor_name_ptr;
+  int sensor_type, len;
+  const char *type;
+  ipmi_entity_t *ent = ipmi_sensor_get_entity(sensor);
+
+  sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+  memset (sensor_name, 0, sizeof (sensor_name));
+  ipmi_sensor_get_name (sensor, sensor_name, sizeof (sensor_name));
+  sensor_name[sizeof (sensor_name) - 1] = 0;
+
+  len = DATA_MAX_NAME_LEN - strlen(sensor_name);
+  strncat(sensor_name, " ", len--);
+  strncat(sensor_name, ipmi_entity_get_entity_id_string(ent), len);
+
+  sensor_name_ptr = strstr (sensor_name, ").");
+  if (sensor_name_ptr == NULL)
+    sensor_name_ptr = sensor_name;
+  else
   {
-    sensor_list_remove (sensor);
-    return;
+    char *sensor_name_ptr_id = strstr (sensor_name, "(");
+
+    sensor_name_ptr += 2;
+    len = DATA_MAX_NAME_LEN - strlen(sensor_name);
+    strncat(sensor_name, " ", len--);
+    strncat(sensor_name, sensor_name_ptr_id, 
+      MIN(sensor_name_ptr - sensor_name_ptr_id - 1, len));
   }
 
+  /* Both `ignorelist' and `plugin_instance' may be NULL. */
+  if (ignorelist_match (ignorelist, sensor_name_ptr) != 0)
+    return (0);
+
   /* FIXME: Use rate unit or base unit to scale the value */
 
   sensor_type = ipmi_sensor_get_sensor_type (sensor);
@@ -174,39 +271,16 @@ static void sensor_read_handler (ipmi_sensor_t *sensor,
     default:
       {
         const char *sensor_type_str;
-        
+
         sensor_type_str = ipmi_sensor_get_sensor_type_string (sensor);
-        INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+        INFO ("ipmi plugin: sensor_list_add: Ignore sensor %s, "
             "because I don't know how to handle its type (%#x, %s). "
             "If you need this sensor, please file a bug report.",
             sensor_name_ptr, sensor_type, sensor_type_str);
-        sensor_list_remove (sensor);
-        return;
+        return (-1);
       }
   } /* switch (sensor_type) */
 
-  values[0].gauge = value;
-
-  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));
-  sstrncpy (vl.type, type, sizeof (vl.type));
-  sstrncpy (vl.type_instance, sensor_name_ptr, sizeof (vl.type_instance));
-
-  plugin_dispatch_values (&vl);
-} /* void sensor_read_handler */
-
-static int sensor_list_add (ipmi_sensor_t *sensor)
-{
-  ipmi_sensor_id_t sensor_id;
-  c_ipmi_sensor_list_t *list_item;
-  c_ipmi_sensor_list_t *list_prev;
-
-  sensor_id = ipmi_sensor_convert_to_id (sensor);
-
   pthread_mutex_lock (&sensor_list_lock);
 
   list_prev = NULL;
@@ -239,8 +313,27 @@ static int sensor_list_add (ipmi_sensor_t *sensor)
   else
     sensor_list = list_item;
 
+  sstrncpy (list_item->sensor_name, sensor_name_ptr,
+            sizeof (list_item->sensor_name));
+  sstrncpy (list_item->sensor_type, type, sizeof (list_item->sensor_type));
+
   pthread_mutex_unlock (&sensor_list_lock);
 
+  if (c_ipmi_nofiy_add && (c_ipmi_init_in_progress == 0))
+  {
+    notification_t n = { NOTIF_OKAY, time(NULL), "", "", "ipmi",
+                         "", "", "", NULL };
+
+    sstrncpy (n.host, hostname_g, sizeof (n.host));
+    sstrncpy (n.type_instance, list_item->sensor_name,
+              sizeof (n.type_instance));
+    sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+    ssnprintf (n.message, sizeof (n.message),
+              "sensor %s added", list_item->sensor_name);
+
+    plugin_dispatch_notification (&n);
+  }
+
   return (0);
 } /* int sensor_list_add */
 
@@ -280,6 +373,21 @@ static int sensor_list_remove (ipmi_sensor_t *sensor)
 
   pthread_mutex_unlock (&sensor_list_lock);
 
+  if (c_ipmi_nofiy_remove && c_ipmi_active)
+  {
+    notification_t n = { NOTIF_WARNING, time(NULL), "", "",
+                         "ipmi", "", "", "", NULL };
+
+    sstrncpy (n.host, hostname_g, sizeof (n.host));
+    sstrncpy (n.type_instance, list_item->sensor_name,
+              sizeof (n.type_instance));
+    sstrncpy (n.type, list_item->sensor_type, sizeof (n.type));
+    ssnprintf (n.message, sizeof (n.message),
+              "sensor %s removed", list_item->sensor_name);
+
+    plugin_dispatch_notification (&n);
+  }
+
   free (list_item);
   return (0);
 } /* int sensor_list_remove */
@@ -295,7 +403,7 @@ static int sensor_list_read_all (void)
       list_item = list_item->next)
   {
     ipmi_sensor_id_get_reading (list_item->sensor_id,
-        sensor_read_handler, /* user data = */ NULL);
+        sensor_read_handler, /* user data = */ list_item);
   } /* for (list_item) */
 
   pthread_mutex_unlock (&sensor_list_lock);
@@ -483,11 +591,32 @@ static int c_ipmi_config (const char *key, const char *value)
   {
     int invert = 1;
     if ((strcasecmp ("True", value) == 0)
-       || (strcasecmp ("Yes", value) == 0)
-       || (strcasecmp ("On", value) == 0))
+        || (strcasecmp ("Yes", value) == 0)
+        || (strcasecmp ("On", value) == 0))
       invert = 0;
     ignorelist_set_invert (ignorelist, invert);
   }
+  else if (strcasecmp ("NotifySensorAdd", key) == 0)
+  {
+    if ((strcasecmp ("True", value) == 0)
+        || (strcasecmp ("Yes", value) == 0)
+        || (strcasecmp ("On", value) == 0))
+      c_ipmi_nofiy_add = 1;
+  }
+  else if (strcasecmp ("NotifySensorRemove", key) == 0)
+  {
+    if ((strcasecmp ("True", value) == 0)
+        || (strcasecmp ("Yes", value) == 0)
+        || (strcasecmp ("On", value) == 0))
+      c_ipmi_nofiy_remove = 1;
+  }
+  else if (strcasecmp ("NotifySensorNotPresent", key) == 0)
+  {
+    if ((strcasecmp ("True", value) == 0)
+        || (strcasecmp ("Yes", value) == 0)
+        || (strcasecmp ("On", value) == 0))
+      c_ipmi_nofiy_notpresent = 1;
+  }
   else
   {
     return (-1);
@@ -500,6 +629,9 @@ static int c_ipmi_init (void)
 {
   int status;
 
+  /* Don't send `ADD' notifications during startup (~ 1 minute) */
+  c_ipmi_init_in_progress = 1 + (60 / interval_g);
+
   c_ipmi_active = 1;
 
   status = pthread_create (&thread_id, /* attr = */ NULL, thread_main,
@@ -524,7 +656,12 @@ static int c_ipmi_read (void)
   }
 
   sensor_list_read_all ();
-  
+
+  if (c_ipmi_init_in_progress > 0)
+    c_ipmi_init_in_progress--;
+  else
+    c_ipmi_init_in_progress = 0;
+
   return (0);
 } /* int c_ipmi_read */
 
index ddea2ea..7bdad0a 100644 (file)
@@ -127,17 +127,29 @@ static int memcached_query_daemon (char *buffer, int buffer_size) /* {{{ */
 
        {
                struct pollfd p;
-               int n;
+               int status;
 
+               memset (&p, 0, sizeof (p));
                p.fd = fd;
-               p.events = POLLIN|POLLERR|POLLHUP;
+               p.events = POLLIN | POLLERR | POLLHUP;
                p.revents = 0;
 
-               n = poll(&p, 1, 3);
-
-               if (n <= 0) {
-                       ERROR ("memcached: poll() failed or timed out");
-                       return -1;
+               status = poll (&p, /* nfds = */ 1, /* timeout = */ 1000 * interval_g);
+               if (status <= 0)
+               {
+                       if (status == 0)
+                       {
+                               ERROR ("memcached: poll(2) timed out after %i seconds.", interval_g);
+                       }
+                       else
+                       {
+                               char errbuf[1024];
+                               ERROR ("memcached: poll(2) failed: %s",
+                                               sstrerror (errno, errbuf, sizeof (errbuf)));
+                       }
+                       shutdown (fd, SHUT_RDWR);
+                       close (fd);
+                       return (-1);
                }
        }
 
index c1564c8..c31b30e 100644 (file)
@@ -319,7 +319,7 @@ static int memory_read (void)
        if ((ios = sg_get_mem_stats ()) != NULL)
        {
                memory_submit ("used",   ios->used);
-               memory_submit ("cached", ios->cached);
+               memory_submit ("cached", ios->cache);
                memory_submit ("free",   ios->free);
        }
 #endif /* HAVE_LIBSTATGRAB */
index 2b670de..dd7f527 100644 (file)
 
 #include <owcapi.h>
 
+#define OW_FAMILY_LENGTH 8
 #define OW_FAMILY_MAX_FEATURES 2
 struct ow_family_features_s
 {
-  char *family;
+  char family[OW_FAMILY_LENGTH];
   struct
   {
     char filename[DATA_MAX_NAME_LEN];
@@ -44,7 +45,7 @@ typedef struct ow_family_features_s ow_family_features_t;
 static ow_family_features_t ow_family_features[] =
 {
   {
-    /* family = */ "10",
+    /* family = */ "10.",
     {
       {
         /* filename = */ "temperature",
@@ -61,7 +62,6 @@ static char *device_g = NULL;
 
 static const char *config_keys[] =
 {
-  "Alias",
   "Device",
   "IgnoreSelected",
   "Sensor",
@@ -104,10 +104,6 @@ static int cow_load_config (const char *key, const char *value)
     sfree (device_g);
     device_g = temp;
   }
-  else if (strcasecmp (key, "Alias") == 0)
-  {
-    /* azogtodo alias-list */
-  }
   else
   {
     return (-1);
@@ -204,6 +200,31 @@ static int cow_read_values (const char *path, const char *name,
   return ((success > 0) ? 0 : -1);
 } /* int cow_read_values */
 
+/* Forward declaration so the recursion below works */
+static int cow_read_bus (const char *path);
+
+/*
+ * cow_read_ds2409
+ *
+ * Handles:
+ * - DS2409 - MicroLAN Coupler
+ */
+static int cow_read_ds2409 (const char *path)
+{
+  char subpath[4096];
+  int status;
+
+  status = ssnprintf (subpath, sizeof (subpath), "%s/main", path);
+  if ((status > 0) && (status < sizeof (subpath)))
+    cow_read_bus (subpath);
+
+  status = ssnprintf (subpath, sizeof (subpath), "%s/aux", path);
+  if ((status > 0) && (status < sizeof (subpath)))
+    cow_read_bus (subpath);
+
+  return (0);
+} /* int cow_read_ds2409 */
+
 static int cow_read_bus (const char *path)
 {
   char *buffer;
@@ -214,7 +235,6 @@ static int cow_read_bus (const char *path)
   char *dummy;
   char *saveptr;
   char subpath[4096];
-  char family_dummy[3]; /* a family only has 2 digits */
 
   status = OW_get (path, &buffer, &buffer_size);
   if (status < 0)
@@ -223,6 +243,8 @@ static int cow_read_bus (const char *path)
         path, status);
     return (-1);
   }
+  DEBUG ("onewire plugin: OW_get (%s) returned: %s",
+      path, buffer);
 
   dummy = buffer;
   saveptr = NULL;
@@ -232,16 +254,33 @@ static int cow_read_bus (const char *path)
 
     dummy = NULL;
 
-    snprintf (subpath, sizeof (subpath), "%s/%s", path, buffer_ptr);
-    subpath[sizeof (subpath) - 1] = 0;
+    if (strcmp ("/", path) == 0)
+      status = ssnprintf (subpath, sizeof (subpath), "/%s", buffer_ptr);
+    else
+      status = ssnprintf (subpath, sizeof (subpath), "%s/%s",
+          path, buffer_ptr);
+    if ((status <= 0) || (status >= sizeof (subpath)))
+      continue;
 
     for (i = 0; i < ow_family_features_num; i++)
     {
-      snprintf (family_dummy, sizeof (family_dummy), "%s%s", ow_family_features[i].family, ".");
-      if (strncmp (family_dummy, buffer_ptr, strlen (family_dummy)) != 0)
+      if (strncmp (ow_family_features[i].family, buffer_ptr,
+            strlen (ow_family_features[i].family)) != 0)
         continue;
 
-      cow_read_values (subpath, buffer_ptr + 3, ow_family_features + i);
+      cow_read_values (subpath,
+          buffer_ptr + strlen (ow_family_features[i].family),
+          ow_family_features + i);
+      break;
+    }
+    if (i < ow_family_features_num)
+      continue;
+
+    /* DS2409 */
+    if (strncmp ("1F.", buffer_ptr, strlen ("1F.")) == 0)
+    {
+      cow_read_ds2409 (subpath);
+      continue;
     }
   } /* while (strtok_r) */
 
@@ -251,40 +290,7 @@ static int cow_read_bus (const char *path)
 
 static int cow_read (void)
 {
-  char *buffer;
-  size_t buffer_size;
-  int status;
-
-  char *buffer_ptr;
-  char *dummy;
-  char *saveptr;
-
-  status = OW_get ("/uncached/", &buffer, &buffer_size);
-  if (status < 0)
-  {
-    ERROR ("onewire plugin: OW_get (\"/\") failed. status = %#x;",
-        status);
-    return (-1);
-  }
-
-  printf ("-- 8< --\n");
-
-  dummy = buffer;
-  saveptr = NULL;
-  while ((buffer_ptr = strtok_r (dummy, ",/", &saveptr)) != NULL)
-  {
-    dummy = NULL;
-    if (strncmp ("bus", buffer_ptr, strlen ("bus")) == 0)
-    {
-      cow_read_bus (buffer_ptr);
-    }
-  } /* while (strtok_r) */
-
-  printf ("-- >8 --\n");
-
-  free (buffer);
-
-  return (0);
+  return (cow_read_bus ("/"));
 } /* int cow_read */
 
 static int cow_shutdown (void)
index 268e1d1..e6f7de0 100644 (file)
@@ -71,6 +71,7 @@
 
 #define PLUGIN_TYPES    7
 
+#define PLUGIN_CONFIG   254
 #define PLUGIN_DATASET  255
 
 #define log_debug(...) DEBUG ("perl: " __VA_ARGS__)
@@ -84,8 +85,7 @@ 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_flush_one);
-static XS (Collectd_plugin_flush_all);
+static XS (Collectd__plugin_flush);
 static XS (Collectd_plugin_dispatch_notification);
 static XS (Collectd_plugin_log);
 static XS (Collectd_call_by_name);
@@ -139,8 +139,7 @@ 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_flush_one",           Collectd_plugin_flush_one },
-       { "Collectd::plugin_flush_all",           Collectd_plugin_flush_all },
+       { "Collectd::_plugin_flush",              Collectd__plugin_flush },
        { "Collectd::plugin_dispatch_notification",
                Collectd_plugin_dispatch_notification },
        { "Collectd::plugin_log",                 Collectd_plugin_log },
@@ -160,6 +159,7 @@ struct {
        { "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 },
@@ -426,6 +426,81 @@ static int notification2hv (pTHX_ notification_t *n, HV *hash)
        return 0;
 } /* static int notification2hv (notification_t *, HV *) */
 
+static int oconfig_item2hv (pTHX_ oconfig_item_t *ci, HV *hash)
+{
+       int i;
+
+       AV *values;
+       AV *children;
+
+       if (NULL == hv_store (hash, "key", 3, newSVpv (ci->key, 0), 0))
+               return -1;
+
+       values = newAV ();
+       if (0 < ci->values_num)
+               av_extend (values, ci->values_num);
+
+       if (NULL == hv_store (hash, "values", 6, newRV_noinc ((SV *)values), 0)) {
+               av_clear (values);
+               av_undef (values);
+               return -1;
+       }
+
+       for (i = 0; i < ci->values_num; ++i) {
+               SV *value;
+
+               switch (ci->values[i].type) {
+                       case OCONFIG_TYPE_STRING:
+                               value = newSVpv (ci->values[i].value.string, 0);
+                               break;
+                       case OCONFIG_TYPE_NUMBER:
+                               value = newSVnv ((NV)ci->values[i].value.number);
+                               break;
+                       case OCONFIG_TYPE_BOOLEAN:
+                               value = ci->values[i].value.boolean ? &PL_sv_yes : &PL_sv_no;
+                               break;
+                       default:
+                               log_err ("oconfig_item2hv: Invalid value type %i.",
+                                               ci->values[i].type);
+                               value = &PL_sv_undef;
+               }
+
+               if (NULL == av_store (values, i, value)) {
+                       sv_free (value);
+                       return -1;
+               }
+       }
+
+       /* ignoring 'parent' member which is uninteresting in this case */
+
+       children = newAV ();
+       if (0 < ci->children_num)
+               av_extend (children, ci->children_num);
+
+       if (NULL == hv_store (hash, "children", 8, newRV_noinc ((SV *)children), 0)) {
+               av_clear (children);
+               av_undef (children);
+               return -1;
+       }
+
+       for (i = 0; i < ci->children_num; ++i) {
+               HV *child = newHV ();
+
+               if (0 != oconfig_item2hv (aTHX_ ci->children + i, child)) {
+                       hv_clear (child);
+                       hv_undef (child);
+                       return -1;
+               }
+
+               if (NULL == av_store (children, i, newRV_noinc ((SV *)child))) {
+                       hv_clear (child);
+                       hv_undef (child);
+                       return -1;
+               }
+       }
+       return 0;
+} /* static int oconfig_item2hv (pTHX_ oconfig_item_t *, HV *) */
+
 /*
  * Internal functions.
  */
@@ -769,8 +844,10 @@ static int pplugin_call_all (pTHX_ int type, ...)
        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;
@@ -918,52 +995,47 @@ static XS (Collectd_plugin_dispatch_values)
 } /* static XS (Collectd_plugin_dispatch_values) */
 
 /*
- * Collectd::plugin_flush_one (timeout, name).
+ * Collectd::_plugin_flush (plugin, timeout, identifier).
+ *
+ * plugin:
+ *   name of the plugin to flush
  *
  * timeout:
  *   timeout to use when flushing the data
  *
- * name:
- *   name of the plugin to flush
+ * identifier:
+ *   data-set identifier to flush
  */
-static XS (Collectd_plugin_flush_one)
+static XS (Collectd__plugin_flush)
 {
+       char *plugin  = NULL;
+       int   timeout = -1;
+       char *id      = NULL;
+
        dXSARGS;
 
-       if (2 != items) {
-               log_err ("Usage: Collectd::plugin_flush_one(timeout, name)");
+       if (3 != items) {
+               log_err ("Usage: Collectd::_plugin_flush(plugin, timeout, id)");
                XSRETURN_EMPTY;
        }
 
-       log_debug ("Collectd::plugin_flush_one: timeout = %i, name = \"%s\"",
-                       (int)SvIV (ST (0)), SvPV_nolen (ST (1)));
+       if (SvOK (ST (0)))
+               plugin = SvPV_nolen (ST (0));
 
-       if (0 == plugin_flush_one ((int)SvIV (ST (0)), SvPV_nolen (ST (1))))
-               XSRETURN_YES;
-       else
-               XSRETURN_EMPTY;
-} /* static XS (Collectd_plugin_flush_one) */
+       if (SvOK (ST (1)))
+               timeout = (int)SvIV (ST (1));
 
-/*
- * Collectd::plugin_flush_all (timeout).
- *
- * timeout:
- *   timeout to use when flushing the data
- */
-static XS (Collectd_plugin_flush_all)
-{
-       dXSARGS;
+       if (SvOK (ST (2)))
+               id = SvPV_nolen (ST (2));
 
-       if (1 != items) {
-               log_err ("Usage: Collectd::plugin_flush_all(timeout)");
-               XSRETURN_EMPTY;
-       }
-
-       log_debug ("Collectd::plugin_flush_all: timeout = %i", (int)SvIV (ST (0)));
+       log_debug ("Collectd::_plugin_flush: plugin = \"%s\", timeout = %i, "
+                       "id = \"%s\"", plugin, timeout, id);
 
-       plugin_flush_all ((int)SvIV (ST (0)));
-       XSRETURN_YES;
-} /* static XS (Collectd_plugin_flush_all) */
+       if (0 == plugin_flush (plugin, timeout, id))
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd__plugin_flush) */
 
 /*
  * Collectd::plugin_dispatch_notification (notif).
@@ -1270,7 +1342,6 @@ static int perl_notify (const notification_t *notif)
        return pplugin_call_all (aTHX_ PLUGIN_NOTIF, notif);
 } /* static int perl_notify (const notification_t *) */
 
-/* TODO: Implement flushing of single identifiers. */
 static int perl_flush (int timeout, const char *identifier)
 {
        dTHX;
@@ -1287,7 +1358,7 @@ static int perl_flush (int timeout, const char *identifier)
 
                aTHX = t->interp;
        }
-       return pplugin_call_all (aTHX_ PLUGIN_FLUSH, timeout);
+       return pplugin_call_all (aTHX_ PLUGIN_FLUSH, timeout, identifier);
 } /* static int perl_flush (const int) */
 
 static int perl_shutdown (void)
@@ -1652,20 +1723,74 @@ static int perl_config_includedir (pTHX_ oconfig_item_t *ci)
        return 0;
 } /* static int perl_config_includedir (oconfig_item_it *) */
 
+/*
+ * <Plugin> block
+ */
+static int perl_config_plugin (pTHX_ oconfig_item_t *ci)
+{
+       int retvals = 0;
+       int ret     = 0;
+
+       char *plugin;
+       HV   *config;
+
+       dSP;
+
+       if ((1 != ci->values_num) || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("LoadPlugin expects a single string argument.");
+               return 1;
+       }
+
+       plugin = ci->values[0].value.string;
+       config = newHV ();
+
+       if (0 != oconfig_item2hv (aTHX_ ci, config)) {
+               hv_clear (config);
+               hv_undef (config);
+
+               log_err ("Unable to convert configuration to a Perl hash value.");
+               config = Nullhv;
+       }
+
+       ENTER;
+       SAVETMPS;
+
+       PUSHMARK (SP);
+
+       XPUSHs (sv_2mortal (newSVpv (plugin, 0)));
+       XPUSHs (sv_2mortal (newRV_noinc ((SV *)config)));
+
+       PUTBACK;
+
+       retvals = call_pv ("Collectd::_plugin_dispatch_config", G_SCALAR);
+
+       SPAGAIN;
+       if (0 < retvals) {
+               SV *tmp = POPs;
+               if (! SvTRUE (tmp))
+                       ret = -1;
+       }
+       else
+               ret = -1;
+
+       PUTBACK;
+       FREETMPS;
+       LEAVE;
+       return ret;
+} /* static int perl_config_plugin (oconfig_item_it *) */
+
 static int perl_config (oconfig_item_t *ci)
 {
        int i = 0;
 
-       dTHX;
-
-       /* dTHX does not get any valid values in case Perl
-        * has not been initialized */
-       if (NULL == perl_threads)
-               aTHX = NULL;
+       dTHXa (NULL);
 
        for (i = 0; i < ci->children_num; ++i) {
                oconfig_item_t *c = ci->children + i;
 
+               if (NULL != perl_threads)
+                       aTHX = PERL_GET_CONTEXT;
+
                if (0 == strcasecmp (c->key, "LoadPlugin"))
                        perl_config_loadplugin (aTHX_ c);
                else if (0 == strcasecmp (c->key, "BaseName"))
@@ -1674,6 +1799,8 @@ static int perl_config (oconfig_item_t *ci)
                        perl_config_enabledebugger (aTHX_ c);
                else if (0 == strcasecmp (c->key, "IncludeDir"))
                        perl_config_includedir (aTHX_ c);
+               else if (0 == strcasecmp (c->key, "Plugin"))
+                       perl_config_plugin (aTHX_ c);
                else
                        log_warn ("Ignoring unknown config key \"%s\".", c->key);
        }
index bf8fb08..2b5a34d 100644 (file)
@@ -149,14 +149,14 @@ static int plugin_load_file (char *file)
        {
                const char *error = lt_dlerror ();
 
-               ERROR ("lt_dlopen failed: %s", error);
-               fprintf (stderr, "lt_dlopen failed: %s\n", error);
+               ERROR ("lt_dlopen (%s) failed: %s", file, error);
+               fprintf (stderr, "lt_dlopen (%s) failed: %s\n", file, error);
                return (1);
        }
 
        if ((reg_handle = (void (*) (void)) lt_dlsym (dlh, "module_register")) == NULL)
        {
-               WARNING ("Couldn't find symbol ``module_register'' in ``%s'': %s\n",
+               WARNING ("Couldn't find symbol `module_register' in `%s': %s\n",
                                file, lt_dlerror ());
                lt_dlclose (dlh);
                return (-1);
@@ -665,43 +665,6 @@ void plugin_read_all (void)
        pthread_mutex_unlock (&read_lock);
 } /* void plugin_read_all */
 
-int plugin_flush_one (int timeout, const char *name)
-{
-       int (*callback) (int);
-       llentry_t *le;
-       int status;
-
-       if (list_flush == NULL)
-               return (-1);
-
-       le = llist_search (list_flush, name);
-       if (le == NULL)
-               return (-1);
-       callback = (int (*) (int)) le->value;
-
-       status = (*callback) (timeout);
-
-       return (status);
-} /* int plugin_flush_ont */
-
-void plugin_flush_all (int timeout)
-{
-       int (*callback) (int);
-       llentry_t *le;
-
-       if (list_flush == NULL)
-               return;
-
-       le = llist_head (list_flush);
-       while (le != NULL)
-       {
-               callback = (int (*) (int)) le->value;
-               le = le->next;
-
-               (*callback) (timeout);
-       }
-} /* void plugin_flush_all */
-
 int plugin_flush (const char *plugin, int timeout, const char *identifier)
 {
   int (*callback) (int timeout, const char *identifier);
@@ -714,13 +677,16 @@ int plugin_flush (const char *plugin, int timeout, const char *identifier)
   while (le != NULL)
   {
     if ((plugin != NULL)
-       && (strcmp (plugin, le->key) != 0))
+        && (strcmp (plugin, le->key) != 0))
+    {
+      le = le->next;
       continue;
+    }
 
     callback = (int (*) (int, const char *)) le->value;
-    le = le->next;
-
     (*callback) (timeout, identifier);
+
+    le = le->next;
   }
   return (0);
 } /* int plugin_flush */
@@ -907,9 +873,9 @@ const data_set_t *plugin_get_ds (const char *name)
 } /* data_set_t *plugin_get_ds */
 
 static int plugin_notification_meta_add (notification_t *n,
-               const char *name,
-               enum notification_meta_type_e type,
-               const void *value)
+    const char *name,
+    enum notification_meta_type_e type,
+    const void *value)
 {
   notification_meta_t *meta;
   notification_meta_t *tail;
@@ -1034,19 +1000,19 @@ int plugin_notification_meta_copy (notification_t *dst,
   {
     if (meta->type == NM_TYPE_STRING)
       plugin_notification_meta_add_string (dst, meta->name,
-         meta->value_string);
+          meta->value_string);
     else if (meta->type == NM_TYPE_SIGNED_INT)
       plugin_notification_meta_add_signed_int (dst, meta->name,
-         meta->value_signed_int);
+          meta->value_signed_int);
     else if (meta->type == NM_TYPE_UNSIGNED_INT)
       plugin_notification_meta_add_unsigned_int (dst, meta->name,
-         meta->value_unsigned_int);
+          meta->value_unsigned_int);
     else if (meta->type == NM_TYPE_DOUBLE)
       plugin_notification_meta_add_double (dst, meta->name,
-         meta->value_double);
+          meta->value_double);
     else if (meta->type == NM_TYPE_BOOLEAN)
       plugin_notification_meta_add_boolean (dst, meta->name,
-         meta->value_boolean);
+          meta->value_boolean);
   }
 
   return (0);
index 3bf7e4c..3ffde46 100644 (file)
@@ -178,8 +178,6 @@ void plugin_init_all (void);
 void plugin_read_all (void);
 void plugin_shutdown_all (void);
 
-void plugin_flush_all (int timeout);
-int plugin_flush_one (int timeout, const char *name);
 int plugin_flush (const char *plugin, int timeout, const char *identifier);
 
 /*
index 40f8ec5..9438c57 100644 (file)
 #define log_warn(...) WARNING ("postgresql: " __VA_ARGS__)
 #define log_info(...) INFO ("postgresql: " __VA_ARGS__)
 
+#ifndef C_PSQL_DEFAULT_CONF
+# define C_PSQL_DEFAULT_CONF PKGDATADIR "/postgresql_default.conf"
+#endif
+
 /* Appends the (parameter, value) pair to the string
  * pointed to by 'buf' suitable to be used as argument
  * for PQconnectdb(). If value equals NULL, the pair
        C_PSQL_IS_UNIX_DOMAIN_SOCKET (host) ? "/.s.PGSQL." : ":", \
        port
 
+typedef enum {
+       C_PSQL_PARAM_HOST = 1,
+       C_PSQL_PARAM_DB,
+       C_PSQL_PARAM_USER,
+} c_psql_param_t;
+
+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;
+
 typedef struct {
        PGconn      *conn;
        c_complain_t conn_complaint;
 
+       int proto_version;
+
+       int max_params_num;
+
        /* user configuration */
+       c_psql_query_t **queries;
+       int              queries_num;
+
        char *host;
        char *port;
        char *database;
@@ -90,9 +127,141 @@ typedef struct {
        char *service;
 } c_psql_database_t;
 
+static char *def_queries[] = {
+       "backends",
+       "transactions",
+       "queries",
+       "query_plans",
+       "table_states",
+       "disk_io",
+       "disk_usage"
+};
+static int def_queries_num = STATIC_ARRAY_SIZE (def_queries);
+
+static c_psql_query_t *queries          = NULL;
+static int             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;
+
+       ++databases_num;
+       if (NULL == (databases = (c_psql_database_t *)realloc (databases,
+                               databases_num * sizeof (*databases)))) {
+               log_err ("Out of memory.");
+               exit (5);
+       }
+
+       db = databases + (databases_num - 1);
+
+       db->conn = NULL;
+
+       db->conn_complaint.last     = 0;
+       db->conn_complaint.interval = 0;
+
+       db->proto_version = 0;
+
+       db->max_params_num = 0;
+
+       db->queries     = NULL;
+       db->queries_num = 0;
+
+       db->database   = sstrdup (name);
+       db->host       = NULL;
+       db->port       = NULL;
+       db->user       = NULL;
+       db->password   = NULL;
+
+       db->sslmode    = NULL;
+
+       db->krbsrvname = NULL;
+
+       db->service    = NULL;
+       return db;
+} /* c_psql_database_new */
+
+static void c_psql_database_delete (c_psql_database_t *db)
+{
+       PQfinish (db->conn);
+
+       sfree (db->queries);
+       db->queries_num = 0;
+
+       sfree (db->database);
+       sfree (db->host);
+       sfree (db->port);
+       sfree (db->user);
+       sfree (db->password);
+
+       sfree (db->sslmode);
+
+       sfree (db->krbsrvname);
+
+       sfree (db->service);
+       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)
@@ -167,6 +336,11 @@ static int c_psql_check_connection (c_psql_database_t *db)
                                        db->database, PQerrorMessage (db->conn));
                        return -1;
                }
+
+               db->proto_version = PQprotocolVersion (db->conn);
+               if (3 > db->proto_version)
+                       log_warn ("Protocol version %d does not support parameters.",
+                                       db->proto_version);
        }
 
        c_release (LOG_INFO, &db->conn_complaint,
@@ -174,141 +348,102 @@ static int c_psql_check_connection (c_psql_database_t *db)
        return 0;
 } /* c_psql_check_connection */
 
-static int c_psql_stat_database (c_psql_database_t *db)
+static PGresult *c_psql_exec_query_params (c_psql_database_t *db,
+               c_psql_query_t *query)
 {
-       const char *const query =
-               "SELECT numbackends, xact_commit, xact_rollback "
-                       "FROM pg_stat_database "
-                       "WHERE datname = $1;";
-
-       PGresult *res;
-
-       int n;
-
-       res = PQexecParams (db->conn, query, /* number of parameters */ 1,
-                       NULL, (const char *const *)&db->database, NULL, NULL,
-                       /* return text data */ 0);
-
-       if (PGRES_TUPLES_OK != PQresultStatus (res)) {
-               log_err ("Failed to execute SQL query: %s",
-                               PQerrorMessage (db->conn));
-               log_info ("SQL query was: %s", query);
-               PQclear (res);
-               return -1;
-       }
-
-       n = PQntuples (res);
-       if (1 < n) {
-               log_warn ("pg_stat_database has more than one entry "
-                               "for database %s - ignoring additional results.",
-                               db->database);
-       }
-       else if (1 > n) {
-               log_err ("pg_stat_database has no entry for database %s",
-                               db->database);
-               PQclear (res);
-               return -1;
+       char *params[db->max_params_num];
+       int   i;
+
+       assert (db->max_params_num >= query->params_num);
+
+       for (i = 0; i < query->params_num; ++i) {
+               switch (query->params[i]) {
+                       case C_PSQL_PARAM_HOST:
+                               params[i] = C_PSQL_IS_UNIX_DOMAIN_SOCKET (db->host)
+                                       ? "localhost" : db->host;
+                               break;
+                       case C_PSQL_PARAM_DB:
+                               params[i] = db->database;
+                               break;
+                       case C_PSQL_PARAM_USER:
+                               params[i] = db->user;
+                               break;
+                       default:
+                               assert (0);
+               }
        }
 
-       submit_gauge (db, "pg_numbackends", NULL,  PQgetvalue (res, 0, 0));
+       return PQexecParams (db->conn, query->query, query->params_num, NULL,
+                       (const char *const *)((0 == query->params_num) ? NULL : params),
+                       NULL, NULL, /* return text data */ 0);
+} /* c_psql_exec_query_params */
 
-       submit_counter (db, "pg_xact", "commit",   PQgetvalue (res, 0, 1));
-       submit_counter (db, "pg_xact", "rollback", PQgetvalue (res, 0, 2));
-
-       PQclear (res);
-       return 0;
-} /* c_psql_stat_database */
+static PGresult *c_psql_exec_query_noparams (c_psql_database_t *db,
+               c_psql_query_t *query)
+{
+       return PQexec (db->conn, query->query);
+} /* c_psql_exec_query_noparams */
 
-static int c_psql_stat_user_tables (c_psql_database_t *db)
+static int c_psql_exec_query (c_psql_database_t *db, int idx)
 {
-       const char *const query =
-               "SELECT sum(seq_scan), sum(seq_tup_read), "
-                               "sum(idx_scan), sum(idx_tup_fetch), "
-                               "sum(n_tup_ins), sum(n_tup_upd), sum(n_tup_del), "
-                               "sum(n_tup_hot_upd), sum(n_live_tup), sum(n_dead_tup) "
-                       "FROM pg_stat_user_tables;";
+       c_psql_query_t *query;
+       PGresult       *res;
 
-       PGresult *res;
+       int rows, cols;
+       int i;
 
-       int n;
+       if (idx >= db->queries_num)
+               return -1;
 
-       res = PQexec (db->conn, query);
+       query = db->queries[idx];
 
-       if (PGRES_TUPLES_OK != PQresultStatus (res)) {
-               log_err ("Failed to execute SQL query: %s",
-                               PQerrorMessage (db->conn));
-               log_info ("SQL query was: %s", query);
-               PQclear (res);
+       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);
+       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);
                return -1;
        }
 
-       n = PQntuples (res);
-       assert (1 >= n);
-
-       if (1 > n) /* no user tables */
-               return 0;
-
-       submit_counter (db, "pg_scan", "seq",           PQgetvalue (res, 0, 0));
-       submit_counter (db, "pg_scan", "seq_tup_read",  PQgetvalue (res, 0, 1));
-       submit_counter (db, "pg_scan", "idx",           PQgetvalue (res, 0, 2));
-       submit_counter (db, "pg_scan", "idx_tup_fetch", PQgetvalue (res, 0, 3));
-
-       submit_counter (db, "pg_n_tup_c", "ins",        PQgetvalue (res, 0, 4));
-       submit_counter (db, "pg_n_tup_c", "upd",        PQgetvalue (res, 0, 5));
-       submit_counter (db, "pg_n_tup_c", "del",        PQgetvalue (res, 0, 6));
-       submit_counter (db, "pg_n_tup_c", "hot_upd",    PQgetvalue (res, 0, 7));
-
-       submit_gauge (db, "pg_n_tup_g", "live",         PQgetvalue (res, 0, 8));
-       submit_gauge (db, "pg_n_tup_g", "dead",         PQgetvalue (res, 0, 9));
-
-       PQclear (res);
-       return 0;
-} /* c_psql_stat_user_tables */
-
-static int c_psql_statio_user_tables (c_psql_database_t *db)
-{
-       const char *const query =
-               "SELECT sum(heap_blks_read), sum(heap_blks_hit), "
-                               "sum(idx_blks_read), sum(idx_blks_hit), "
-                               "sum(toast_blks_read), sum(toast_blks_hit), "
-                               "sum(tidx_blks_read), sum(tidx_blks_hit) "
-                       "FROM pg_statio_user_tables;";
-
-       PGresult *res;
-
-       int n;
-
-       res = PQexec (db->conn, query);
-
        if (PGRES_TUPLES_OK != PQresultStatus (res)) {
                log_err ("Failed to execute SQL query: %s",
                                PQerrorMessage (db->conn));
-               log_info ("SQL query was: %s", query);
+               log_info ("SQL query was: %s", query->query);
                PQclear (res);
                return -1;
        }
 
-       n = PQntuples (res);
-       assert (1 >= n);
-
-       if (1 > n) /* no user tables */
+       rows = PQntuples (res);
+       if (1 > rows)
                return 0;
 
-       submit_counter (db, "pg_blks", "heap_read",  PQgetvalue (res, 0, 0));
-       submit_counter (db, "pg_blks", "heap_hit",   PQgetvalue (res, 0, 1));
+       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);
+               return -1;
+       }
 
-       submit_counter (db, "pg_blks", "idx_read",   PQgetvalue (res, 0, 2));
-       submit_counter (db, "pg_blks", "idx_hit",    PQgetvalue (res, 0, 3));
+       for (i = 0; i < rows; ++i) {
+               int j;
 
-       submit_counter (db, "pg_blks", "toast_read", PQgetvalue (res, 0, 4));
-       submit_counter (db, "pg_blks", "toast_hit",  PQgetvalue (res, 0, 5));
+               for (j = 0; j < cols; ++j) {
+                       c_psql_col_t col = query->cols[j];
 
-       submit_counter (db, "pg_blks", "tidx_read",  PQgetvalue (res, 0, 6));
-       submit_counter (db, "pg_blks", "tidx_hit",   PQgetvalue (res, 0, 7));
+                       char *value = PQgetvalue (res, i, j);
 
-       PQclear (res);
+                       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);
+               }
+       }
        return 0;
-} /* c_psql_statio_user_tables */
+} /* c_psql_exec_query */
 
 static int c_psql_read (void)
 {
@@ -318,14 +453,15 @@ static int c_psql_read (void)
        for (i = 0; i < databases_num; ++i) {
                c_psql_database_t *db = databases + i;
 
+               int j;
+
                assert (NULL != db->database);
 
                if (0 != c_psql_check_connection (db))
                        continue;
 
-               c_psql_stat_database (db);
-               c_psql_stat_user_tables (db);
-               c_psql_statio_user_tables (db);
+               for (j = 0; j < db->queries_num; ++j)
+                       c_psql_exec_query (db, j);
 
                ++success;
        }
@@ -347,24 +483,19 @@ static int c_psql_shutdown (void)
 
        for (i = 0; i < databases_num; ++i) {
                c_psql_database_t *db = databases + i;
-
-               PQfinish (db->conn);
-
-               sfree (db->database);
-               sfree (db->host);
-               sfree (db->port);
-               sfree (db->user);
-               sfree (db->password);
-
-               sfree (db->sslmode);
-
-               sfree (db->krbsrvname);
-
-               sfree (db->service);
+               c_psql_database_delete (db);
        }
 
        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);
+       queries_num = 0;
        return 0;
 } /* c_psql_shutdown */
 
@@ -375,6 +506,33 @@ 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;
 
@@ -386,6 +544,8 @@ static int c_psql_init (void)
                char *server_host;
                int   server_version;
 
+               int j;
+
                status = ssnprintf (buf, buf_len, "dbname = '%s'", db->database);
                if (0 < status) {
                        buf     += status;
@@ -404,15 +564,48 @@ static int c_psql_init (void)
                if (0 != c_psql_check_connection (db))
                        continue;
 
+               db->proto_version = PQprotocolVersion (db->conn);
+
                server_host    = PQhost (db->conn);
                server_version = PQserverVersion (db->conn);
                log_info ("Sucessfully connected to database %s (user %s) "
                                "at server %s%s%s (server version: %d.%d.%d, "
                                "protocol version: %d, pid: %d)",
                                PQdb (db->conn), PQuser (db->conn),
-                               C_PSQL_SOCKET3(server_host, PQport (db->conn)),
+                               C_PSQL_SOCKET3 (server_host, PQport (db->conn)),
                                C_PSQL_SERVER_VERSION3 (server_version),
-                               PQprotocolVersion (db->conn), PQbackendPID (db->conn));
+                               db->proto_version, PQbackendPID (db->conn));
+
+               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);
@@ -420,7 +613,7 @@ static int c_psql_init (void)
        return 0;
 } /* c_psql_init */
 
-static int config_set (char *name, char **var, const oconfig_item_t *ci)
+static int config_set_s (char *name, char **var, const oconfig_item_t *ci)
 {
        if ((0 != ci->children_num) || (1 != ci->values_num)
                        || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
@@ -431,77 +624,260 @@ static int config_set (char *name, char **var, const oconfig_item_t *ci)
        sfree (*var);
        *var = sstrdup (ci->values[0].value.string);
        return 0;
-} /* config_set */
+} /* config_set_s */
 
-static int c_psql_config_database (oconfig_item_t *ci)
+static int config_set_i (char *name, int *var, const oconfig_item_t *ci)
 {
-       c_psql_database_t *db;
+       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;
+       }
 
-       int i;
+       *var = (int)ci->values[0].value.number;
+       return 0;
+} /* config_set_i */
 
-       if ((1 != ci->values_num)
+static int config_set_param (c_psql_query_t *query, const oconfig_item_t *ci)
+{
+       c_psql_param_t param;
+       char          *param_str;
+
+       if ((0 != ci->children_num) || (1 != ci->values_num)
                        || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
-               log_err ("<Database> expects a single string argument.");
+               log_err ("Param expects a single string argument.");
                return 1;
        }
 
-       ++databases_num;
-       if (NULL == (databases = (c_psql_database_t *)realloc (databases,
-                               databases_num * sizeof (*databases)))) {
+       param_str = ci->values[0].value.string;
+       if (0 == strcasecmp (param_str, "hostname"))
+               param = C_PSQL_PARAM_HOST;
+       else if (0 == strcasecmp (param_str, "database"))
+               param = C_PSQL_PARAM_DB;
+       else if (0 == strcasecmp (param_str, "username"))
+               param = C_PSQL_PARAM_USER;
+       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);
        }
 
-       db = databases + (databases_num - 1);
+       query->params[query->params_num - 1] = param;
+       return 0;
+} /* config_set_param */
 
-       db->conn = NULL;
+static int config_set_column (c_psql_query_t *query, const oconfig_item_t *ci)
+{
+       c_psql_col_t *col;
 
-       db->conn_complaint.last     = 0;
-       db->conn_complaint.interval = 0;
+       int i;
 
-       db->database   = sstrdup (ci->values[0].value.string);
-       db->host       = NULL;
-       db->port       = NULL;
-       db->user       = NULL;
-       db->password   = NULL;
+       if ((0 != ci->children_num)
+                       || (1 > ci->values_num) || (2 < ci->values_num)) {
+               log_err ("Column expects either one or two arguments.");
+               return 1;
+       }
 
-       db->sslmode    = NULL;
+       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;
+               }
+       }
 
-       db->krbsrvname = NULL;
+       ++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);
+       }
 
-       db->service    = NULL;
+       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;
+
+       db->queries[db->queries_num - 1] = query;
+       return 0;
+} /* set_query */
+
+static int config_set_query (c_psql_database_t *db, const 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 */
+
+static int c_psql_config_query (oconfig_item_t *ci)
+{
+       c_psql_query_t *query;
+
+       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 */
+
+static int c_psql_config_database (oconfig_item_t *ci)
+{
+       c_psql_database_t *db;
+
+       int i;
+
+       if ((1 != ci->values_num)
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("<Database> expects a single string argument.");
+               return 1;
+       }
+
+       db = c_psql_database_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, "Host"))
-                       config_set ("Host", &db->host, c);
+                       config_set_s ("Host", &db->host, c);
                else if (0 == strcasecmp (c->key, "Port"))
-                       config_set ("Port", &db->port, c);
+                       config_set_s ("Port", &db->port, c);
                else if (0 == strcasecmp (c->key, "User"))
-                       config_set ("User", &db->user, c);
+                       config_set_s ("User", &db->user, c);
                else if (0 == strcasecmp (c->key, "Password"))
-                       config_set ("Password", &db->password, c);
+                       config_set_s ("Password", &db->password, c);
                else if (0 == strcasecmp (c->key, "SSLMode"))
-                       config_set ("SSLMode", &db->sslmode, c);
+                       config_set_s ("SSLMode", &db->sslmode, c);
                else if (0 == strcasecmp (c->key, "KRBSrvName"))
-                       config_set ("KRBSrvName", &db->krbsrvname, c);
+                       config_set_s ("KRBSrvName", &db->krbsrvname, c);
                else if (0 == strcasecmp (c->key, "Service"))
-                       config_set ("Service", &db->service, c);
+                       config_set_s ("Service", &db->service, c);
+               else if (0 == strcasecmp (c->key, "Query"))
+                       config_set_query (db, c);
                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]);
+       }
        return 0;
 }
 
 static int c_psql_config (oconfig_item_t *ci)
 {
+       static int have_def_config = 0;
+
        int i;
 
+       if (0 == have_def_config) {
+               oconfig_item_t *c;
+
+               have_def_config = 1;
+
+               c = oconfig_parse_file (C_PSQL_DEFAULT_CONF);
+               if (NULL == c)
+                       log_err ("Failed to read default config ("C_PSQL_DEFAULT_CONF").");
+               else
+                       c_psql_config (c);
+
+               if (NULL == queries)
+                       log_err ("Default config ("C_PSQL_DEFAULT_CONF") did not define "
+                                       "any queries - please check your installation.");
+       }
+
        for (i = 0; i < ci->children_num; ++i) {
                oconfig_item_t *c = ci->children + i;
 
-               if (0 == strcasecmp (c->key, "Database"))
+               if (0 == strcasecmp (c->key, "Query"))
+                       c_psql_config_query (c);
+               else if (0 == strcasecmp (c->key, "Database"))
                        c_psql_config_database (c);
                else
                        log_warn ("Ignoring unknown config key \"%s\".", c->key);
diff --git a/src/postgresql_default.conf b/src/postgresql_default.conf
new file mode 100644 (file)
index 0000000..61844a0
--- /dev/null
@@ -0,0 +1,93 @@
+# Pre-defined queries of collectd's postgresql plugin.
+
+<Query backends>
+       Query "SELECT count(*) \
+               FROM pg_stat_activity \
+               WHERE datname = $1;"
+
+       Param database
+
+       Column pg_numbackends
+</Query>
+
+<Query transactions>
+       Query "SELECT xact_commit, xact_rollback \
+               FROM pg_stat_database \
+               WHERE datname = $1;"
+
+       Param database
+
+       Column pg_xact commit
+       Column pg_xact rollback
+</Query>
+
+<Query queries>
+       Query "SELECT sum(n_tup_ins), sum(n_tup_upd), sum(n_tup_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
+</Query>
+
+<Query queries>
+       Query "SELECT sum(n_tup_ins), sum(n_tup_upd), sum(n_tup_del), \
+                       sum(n_tup_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
+</Query>
+
+<Query query_plans>
+       Query "SELECT sum(seq_scan), sum(seq_tup_read), \
+                       sum(idx_scan), sum(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
+</Query>
+
+<Query table_states>
+       Query "SELECT sum(n_live_tup), sum(n_dead_tup) \
+               FROM pg_stat_user_tables;"
+
+       Column pg_n_tup_g live
+       Column pg_n_tup_g dead
+
+       MinPGVersion 80300
+</Query>
+
+<Query disk_io>
+       Query "SELECT sum(heap_blks_read), sum(heap_blks_hit), \
+                       sum(idx_blks_read), sum(idx_blks_hit), \
+                       sum(toast_blks_read), sum(toast_blks_hit), \
+                       sum(tidx_blks_read), sum(tidx_blks_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
+</Query>
+
+<Query disk_usage>
+       Query "SELECT pg_database_size($1);"
+
+       Param database
+
+       Column pg_db_size
+</Query>
+
index 7531a6a..f229804 100644 (file)
 #  endif
 /* #endif KERNEL_LINUX */
 
-#elif HAVE_KVM_H
+#elif HAVE_LIBKVM_GETPROCS
 #  include <kvm.h>
 #  include <sys/user.h>
 #  include <sys/proc.h>
 #  if HAVE_SYS_SYSCTL_H
 #    include <sys/sysctl.h>
 #  endif
-/* #endif HAVE_KVM_H */
+/* #endif HAVE_LIBKVM_GETPROCS */
 
 #else
 # error "No applicable input method."
@@ -167,7 +167,11 @@ static mach_msg_type_number_t     pset_list_len;
 
 #elif KERNEL_LINUX
 static long pagesize_g;
-#endif /* KERNEL_LINUX */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKVM_GETPROCS
+/* no global variables */
+#endif /* HAVE_LIBKVM_GETPROCS */
 
 /* put name of process from config to list_head_g tree
    list_head_g is a list of 'procstat_t' structs with
@@ -508,7 +512,11 @@ static int ps_init (void)
        pagesize_g = sysconf(_SC_PAGESIZE);
        DEBUG ("pagesize_g = %li; CONFIG_HZ = %i;",
                        pagesize_g, CONFIG_HZ);
-#endif /* KERNEL_LINUX */
+/* #endif KERNEL_LINUX */
+
+#elif HAVE_LIBKVM_GETPROCS
+/* no initialization */
+#endif /* HAVE_LIBKVM_GETPROCS */
 
        return (0);
 } /* int ps_init */
@@ -971,7 +979,7 @@ static int ps_read (void)
                                         * There's only zombie tasks, which are
                                         * handled above. */
                                        default:
-                                               WARNING ("Unknown thread status: %s",
+                                               WARNING ("Unknown thread status: %i",
                                                                thread_data.run_state);
                                                break;
                                } /* switch (thread_data.run_state) */
@@ -1131,7 +1139,7 @@ static int ps_read (void)
                ps_submit_proc_list (ps_ptr);
 /* #endif KERNEL_LINUX */
 
-#elif HAVE_LIBKVM
+#elif HAVE_LIBKVM_GETPROCS
        int running  = 0;
        int sleeping = 0;
        int zombies  = 0;
@@ -1251,7 +1259,7 @@ static int ps_read (void)
 
        for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next)
                ps_submit_proc_list (ps_ptr);
-#endif /* HAVE_LIBKVM */
+#endif /* HAVE_LIBKVM_GETPROCS */
 
        return (0);
 } /* int ps_read */
diff --git a/src/qmail.c b/src/qmail.c
deleted file mode 100644 (file)
index 063eee9..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-/**
- * collectd - src/qmail.c
- * Copyright (C) 2008  Alessandro Iurlano
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; only version 2 of the License is applicable.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
- *
- * Author:
- *   Alessandro Iurlano <alessandro.iurlano at gmail.com>
- **/
-
-#include "collectd.h"
-#include "common.h"
-#include "plugin.h"       
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <dirent.h>
-
-#define DEFAULT_BASE_DIR "/var/qmail"
-
-static char *qmail_base_dir;
-
-static const char *config_keys[] =
-{
-  "QmailDir"
-};
-static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
-
-static void qmail_submit (const char *plugin_instance, gauge_t value)
-{
-  value_t values[1];
-  value_list_t vl = VALUE_LIST_INIT;
-
-  values[0].gauge = value;
-
-  vl.values = values;
-  vl.values_len = STATIC_ARRAY_SIZE (values);
-  vl.time = time (NULL);
-  sstrncpy (vl.host, hostname_g, sizeof (vl.host));
-  sstrncpy (vl.type, "gauge", sizeof (vl.type));
-  sstrncpy (vl.plugin, "qmail", sizeof (vl.plugin));
-  sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
-
-  plugin_dispatch_values (&vl);
-} /* void qmail_submit */
-
-static int count_files_in_subtree (const char *path, int depth)
-{
-#if NAME_MAX < 1024
-# define DIRENT_BUFFER_SIZE (sizeof (struct dirent) + 1024 + 1)
-#else
-# define DIRENT_BUFFER_SIZE (sizeof (struct dirent) + NAME_MAX + 1)
-#endif
-
-  DIR *dh;
-  struct dirent *de;
-  char dirent_buffer[DIRENT_BUFFER_SIZE];
-  int status;
-
-  char **subdirs;
-  size_t subdirs_num;
-
-  int count;
-  int i;
-
-  dh = opendir (path);
-  if (dh == NULL)
-  {
-    ERROR ("qmail plugin: opendir (%s) failed.", path);
-    return (-1);
-  }
-
-  subdirs = NULL;
-  subdirs_num = 0;
-
-  count = 0;
-  while (42)
-  {
-    char abs_path[PATH_MAX];
-    struct stat statbuf;
-
-    de = NULL;
-    status = readdir_r (dh, (struct dirent *) dirent_buffer, &de);
-    if (status != 0)
-    {
-      char errbuf[4096];
-      ERROR ("qmail plugin: readdir_r failed: %s",
-          sstrerror (errno, errbuf, sizeof (errbuf)));
-      closedir (dh);
-      return (-1);
-    }
-    else if (de == NULL)
-    {
-      /* end of directory */
-      break;
-    }
-
-    if (de->d_name[0] == '.')
-      continue;
-
-    ssnprintf (abs_path, sizeof (abs_path), "%s/%s", path, de->d_name);
-
-    status = lstat (abs_path, &statbuf);
-    if (status != 0)
-    {
-      ERROR ("qmail plugin: stat (%s) failed.", abs_path);
-      continue;
-    }
-
-    if (S_ISREG (statbuf.st_mode))
-    {
-      count++;
-    }
-    else if (S_ISDIR (statbuf.st_mode))
-    {
-      char **temp;
-
-      temp = (char **) realloc (subdirs, sizeof (char *) * (subdirs_num + 1));
-      if (temp == NULL)
-      {
-        ERROR ("qmail plugin: realloc failed.");
-        continue;
-      }
-      subdirs = temp;
-
-      subdirs[subdirs_num] = strdup (abs_path);
-      if (subdirs[subdirs_num] == NULL)
-      {
-        ERROR ("qmail plugin: strdup failed.");
-        continue;
-      }
-      subdirs_num++;
-    }
-  }
-
-  closedir (dh);
-  dh = NULL;
-
-  if (depth > 0)
-  {
-    for (i = 0; i < subdirs_num; i++)
-    {
-      status = count_files_in_subtree (subdirs[i], depth - 1);
-      if (status > 0)
-        count += status;
-    }
-  }
-
-  for (i = 0; i < subdirs_num; i++)
-  {
-    sfree (subdirs[i]);
-  }
-  sfree (subdirs);
-
-  return (count);
-} /* int count_files_in_subtree */
-
-static int read_queue_length (const char *queue_name, const char *path)
-{
-  int64_t num_files;
-
-  num_files = count_files_in_subtree (path, /* depth = */ 1);
-  if (num_files < 0)
-  {
-    ERROR ("qmail plugin: Counting files in `%s' failed.", path);
-    return (-1);
-  }
-
-  qmail_submit (queue_name, (gauge_t) num_files);
-  return (0);
-} /* int read_queue_length */
-
-static int queue_len_read (void)
-{
-  char path[4096];
-  int success;
-  int status;
-
-  success = 0;
-  
-  ssnprintf (path, sizeof (path), "%s/queue/mess",
-      (qmail_base_dir != NULL)
-      ? qmail_base_dir
-      : DEFAULT_BASE_DIR);
-
-  status = read_queue_length ("messages", path);
-  if (status == 0)
-    success++;
-
-  ssnprintf (path, sizeof (path), "%s/queue/todo",
-      (qmail_base_dir != NULL)
-      ? qmail_base_dir
-      : DEFAULT_BASE_DIR);
-
-  status = read_queue_length ("todo", path);
-  if (status == 0)
-    success++;
-
-  if (success > 0)
-    return 0;
-  return (-1);
-} /* int queue_len_read */
-
-static int qmail_config (const char *key, const char *val)
-{
-  if (strcasecmp ("QmailDir", key) == 0)
-  {
-    size_t qmail_base_dir_len;
-
-    sfree (qmail_base_dir);
-    qmail_base_dir = strdup(val);
-    if (qmail_base_dir == NULL)
-    {
-      ERROR ("qmail plugin: strdup failed.");
-      return (1);
-    }
-
-    qmail_base_dir_len = strlen (qmail_base_dir);
-    while ((qmail_base_dir_len > 0)
-        && (qmail_base_dir[qmail_base_dir_len - 1] == '/'))
-    {
-      qmail_base_dir[qmail_base_dir_len - 1] = 0;
-      qmail_base_dir_len--;
-    }
-
-    if (qmail_base_dir_len == 0)
-    {
-      ERROR ("qmail plugin: QmailDir is invalid.");
-      sfree (qmail_base_dir);
-      qmail_base_dir = NULL;
-      return (1);
-    }
-  }
-  else
-  {
-    return (-1);
-  }
-
-  return (0);
-} /* int qmail_config */
-
-void module_register (void)
-{
-  plugin_register_config ("qmail", qmail_config,
-      config_keys, config_keys_num);
-  plugin_register_read ("qmail", queue_len_read);
-} /* void module_register */
-
-/*
- * vim: set sw=2 sts=2 et :
- */
index 1c4f5dd..9b236cb 100644 (file)
@@ -43,7 +43,8 @@ struct rrd_cache_s
        enum
        {
                FLAG_NONE   = 0x00,
-               FLAG_QUEUED = 0x01
+               FLAG_QUEUED = 0x01,
+               FLAG_FLUSHQ = 0x02
        } flags;
 };
 typedef struct rrd_cache_s rrd_cache_t;
@@ -74,14 +75,16 @@ static const char *config_keys[] =
        "HeartBeat",
        "RRARows",
        "RRATimespan",
-       "XFF"
+       "XFF",
+       "WritesPerSecond"
 };
 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 char *datadir   = NULL;
+static double write_rate = 0.0;
 static rrdcreate_config_t rrdcreate_config =
 {
        /* stepsize = */ 0,
@@ -106,6 +109,8 @@ static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
 
 static rrd_queue_t    *queue_head = NULL;
 static rrd_queue_t    *queue_tail = NULL;
+static rrd_queue_t    *flushq_head = NULL;
+static rrd_queue_t    *flushq_tail = NULL;
 static pthread_t       queue_thread = 0;
 static pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t  queue_cond = PTHREAD_COND_INITIALIZER;
@@ -263,6 +268,11 @@ static int value_list_to_filename (char *buffer, int buffer_len,
 
 static void *rrd_queue_thread (void *data)
 {
+        struct timeval tv_next_update;
+        struct timeval tv_now;
+
+        gettimeofday (&tv_next_update, /* timezone = */ NULL);
+
        while (42)
        {
                rrd_queue_t *queue_entry;
@@ -271,27 +281,79 @@ static void *rrd_queue_thread (void *data)
                int    values_num;
                int    i;
 
-               /* XXX: If you need to lock both, cache_lock and queue_lock, at
-                * the same time, ALWAYS lock `cache_lock' first! */
-
-               /* wait until an entry is available */
-               pthread_mutex_lock (&queue_lock);
-               while ((queue_head == NULL) && (do_shutdown == 0))
-                       pthread_cond_wait (&queue_cond, &queue_lock);
-
-               /* We're in the shutdown phase */
-               if (queue_head == NULL)
-               {
-                       pthread_mutex_unlock (&queue_lock);
-                       break;
-               }
-
-               /* Dequeue the first entry */
-               queue_entry = queue_head;
-               if (queue_head == queue_tail)
-                       queue_head = queue_tail = NULL;
-               else
-                       queue_head = queue_head->next;
+                pthread_mutex_lock (&queue_lock);
+                /* Wait for values to arrive */
+                while (true)
+                {
+                  struct timespec ts_wait;
+                  int status;
+
+                  while ((flushq_head == NULL) && (queue_head == NULL)
+                      && (do_shutdown == 0))
+                    pthread_cond_wait (&queue_cond, &queue_lock);
+
+                  if ((flushq_head == NULL) && (queue_head == NULL))
+                    break;
+
+                  /* Don't delay if there's something to flush */
+                  if (flushq_head != NULL)
+                    break;
+
+                  /* Don't delay if we're shutting down */
+                  if (do_shutdown != 0)
+                    break;
+
+                  /* Don't delay if no delay was configured. */
+                  if (write_rate <= 0.0)
+                    break;
+
+                  gettimeofday (&tv_now, /* timezone = */ NULL);
+                  status = timeval_sub_timespec (&tv_next_update, &tv_now,
+                      &ts_wait);
+                  /* We're good to go */
+                  if (status != 0)
+                    break;
+
+                  /* We're supposed to wait a bit with this update, so we'll
+                   * wait for the next addition to the queue or to the end of
+                   * the wait period - whichever comes first. */
+                  ts_wait.tv_sec = tv_next_update.tv_sec;
+                  ts_wait.tv_nsec = 1000 * tv_next_update.tv_usec;
+
+                  status = pthread_cond_timedwait (&queue_cond, &queue_lock,
+                      &ts_wait);
+                  if (status == ETIMEDOUT)
+                    break;
+                } /* while (true) */
+
+                /* XXX: If you need to lock both, cache_lock and queue_lock, at
+                 * the same time, ALWAYS lock `cache_lock' first! */
+
+                /* We're in the shutdown phase */
+                if ((flushq_head == NULL) && (queue_head == NULL))
+                {
+                  pthread_mutex_unlock (&queue_lock);
+                  break;
+                }
+
+                if (flushq_head != NULL)
+                {
+                  /* Dequeue the first flush entry */
+                  queue_entry = flushq_head;
+                  if (flushq_head == flushq_tail)
+                    flushq_head = flushq_tail = NULL;
+                  else
+                    flushq_head = flushq_head->next;
+                }
+                else /* if (queue_head != NULL) */
+                {
+                  /* Dequeue the first regular entry */
+                  queue_entry = queue_head;
+                  if (queue_head == queue_tail)
+                    queue_head = queue_tail = NULL;
+                  else
+                    queue_head = queue_head->next;
+                }
 
                /* Unlock the queue again */
                pthread_mutex_unlock (&queue_lock);
@@ -311,6 +373,20 @@ static void *rrd_queue_thread (void *data)
 
                pthread_mutex_unlock (&cache_lock);
 
+               /* Update `tv_next_update' */
+               if (write_rate > 0.0) 
+                {
+                  gettimeofday (&tv_now, /* timezone = */ NULL);
+                  tv_next_update.tv_sec = tv_now.tv_sec;
+                  tv_next_update.tv_usec = tv_now.tv_usec
+                    + ((suseconds_t) (1000000 * write_rate));
+                  while (tv_next_update.tv_usec > 1000000)
+                  {
+                    tv_next_update.tv_sec++;
+                    tv_next_update.tv_usec -= 1000000;
+                  }
+                }
+
                /* Write the values to the RRD-file */
                srrd_update (queue_entry->filename, NULL,
                                values_num, (const char **)values);
@@ -335,7 +411,8 @@ static void *rrd_queue_thread (void *data)
        return ((void *) 0);
 } /* void *rrd_queue_thread */
 
-static int rrd_queue_cache_entry (const char *filename, rrd_queue_dir_t dir)
+static int rrd_queue_enqueue (const char *filename,
+    rrd_queue_t **head, rrd_queue_t **tail)
 {
   rrd_queue_t *queue_entry;
 
@@ -353,55 +430,60 @@ static int rrd_queue_cache_entry (const char *filename, rrd_queue_dir_t dir)
   queue_entry->next = NULL;
 
   pthread_mutex_lock (&queue_lock);
-  if (dir == QUEUE_INSERT_FRONT)
-  {
-    queue_entry->next = queue_head;
-    queue_head = queue_entry;
-    if (queue_tail == NULL)
-      queue_tail = queue_head;
-  }
-  else /* (dir == QUEUE_INSERT_BACK) */
-  {
-    if (queue_tail == NULL)
-      queue_head = queue_entry;
-    else
-      queue_tail->next = queue_entry;
-    queue_tail = queue_entry;
-  }
+
+  if (*tail == NULL)
+    *head = queue_entry;
+  else
+    (*tail)->next = queue_entry;
+  *tail = queue_entry;
+
   pthread_cond_signal (&queue_cond);
   pthread_mutex_unlock (&queue_lock);
 
-  DEBUG ("rrdtool plugin: Put `%s' into the update queue", filename);
-
   return (0);
-} /* int rrd_queue_cache_entry */
+} /* int rrd_queue_enqueue */
 
-static int rrd_queue_move_to_front (const char *filename)
+static int rrd_queue_dequeue (const char *filename,
+    rrd_queue_t **head, rrd_queue_t **tail)
 {
   rrd_queue_t *this;
   rrd_queue_t *prev;
 
-  this = NULL;
-  prev = NULL;
   pthread_mutex_lock (&queue_lock);
-  for (this = queue_head; this != NULL; this = this->next)
+
+  prev = NULL;
+  this = *head;
+
+  while (this != NULL)
   {
     if (strcmp (this->filename, filename) == 0)
       break;
+    
     prev = this;
+    this = this->next;
   }
 
-  /* Check if we found the entry and if it is NOT the first entry. */
-  if ((this != NULL) && (prev != NULL))
+  if (this == NULL)
   {
-    prev->next = this->next;
-    this->next = queue_head;
-    queue_head = this;
+    pthread_mutex_unlock (&queue_lock);
+    return (-1);
   }
+
+  if (prev == NULL)
+    *head = this->next;
+  else
+    prev->next = this->next;
+
+  if (this->next == NULL)
+    *tail = prev;
+
   pthread_mutex_unlock (&queue_lock);
 
+  sfree (this->filename);
+  sfree (this);
+
   return (0);
-} /* int rrd_queue_move_to_front */
+} /* int rrd_queue_dequeue */
 
 static void rrd_cache_flush (int timeout)
 {
@@ -423,13 +505,16 @@ static void rrd_cache_flush (int timeout)
        iter = c_avl_get_iterator (cache);
        while (c_avl_iterator_next (iter, (void *) &key, (void *) &rc) == 0)
        {
-               if (rc->flags == FLAG_QUEUED)
+               if (rc->flags != FLAG_NONE)
                        continue;
                else if ((now - rc->first_value) < timeout)
                        continue;
                else if (rc->values_num > 0)
                {
-                       if (rrd_queue_cache_entry (key, QUEUE_INSERT_BACK) == 0)
+                       int status;
+
+                       status = rrd_queue_enqueue (key, &queue_head,  &queue_tail);
+                       if (status == 0)
                                rc->flags = FLAG_QUEUED;
                }
                else /* ancient and no values -> waste of memory */
@@ -491,31 +576,42 @@ static int rrd_cache_flush_identifier (int timeout, const char *identifier)
   now = time (NULL);
 
   if (datadir == NULL)
-         snprintf (key, sizeof (key), "%s.rrd",
-                         identifier);
+    snprintf (key, sizeof (key), "%s.rrd",
+        identifier);
   else
-         snprintf (key, sizeof (key), "%s/%s.rrd",
-                         datadir, identifier);
+    snprintf (key, sizeof (key), "%s/%s.rrd",
+        datadir, identifier);
   key[sizeof (key) - 1] = 0;
 
   status = c_avl_get (cache, key, (void *) &rc);
   if (status != 0)
   {
     WARNING ("rrdtool plugin: rrd_cache_flush_identifier: "
-       "c_avl_get (%s) failed. Does that file really exist?",
-       key);
+        "c_avl_get (%s) failed. Does that file really exist?",
+        key);
     return (status);
   }
 
-  if (rc->flags == FLAG_QUEUED)
-    status = rrd_queue_move_to_front (key);
+  if (rc->flags == FLAG_FLUSHQ)
+  {
+    status = 0;
+  }
+  else if (rc->flags == FLAG_QUEUED)
+  {
+    rrd_queue_dequeue (key, &queue_head, &queue_tail);
+    status = rrd_queue_enqueue (key, &flushq_head, &flushq_tail);
+    if (status == 0)
+      rc->flags = FLAG_FLUSHQ;
+  }
   else if ((now - rc->first_value) < timeout)
+  {
     status = 0;
+  }
   else if (rc->values_num > 0)
   {
-    status = rrd_queue_cache_entry (key, QUEUE_INSERT_FRONT);
+    status = rrd_queue_enqueue (key, &flushq_head, &flushq_tail);
     if (status == 0)
-      rc->flags = FLAG_QUEUED;
+      rc->flags = FLAG_FLUSHQ;
   }
 
   return (status);
@@ -615,9 +711,12 @@ static int rrd_cache_insert (const char *filename,
        {
                /* XXX: If you need to lock both, cache_lock and queue_lock, at
                 * the same time, ALWAYS lock `cache_lock' first! */
-               if (rc->flags != FLAG_QUEUED)
+               if (rc->flags == FLAG_NONE)
                {
-                       if (rrd_queue_cache_entry (filename, QUEUE_INSERT_BACK) == 0)
+                       int status;
+
+                       status = rrd_queue_enqueue (filename, &queue_head, &queue_tail);
+                       if (status == 0)
                                rc->flags = FLAG_QUEUED;
                }
                else
@@ -630,7 +729,6 @@ static int rrd_cache_insert (const char *filename,
                        ((time (NULL) - cache_flush_last) > cache_flush_timeout))
                rrd_cache_flush (cache_flush_timeout);
 
-
        pthread_mutex_unlock (&cache_lock);
 
        return (0);
@@ -828,6 +926,25 @@ static int rrd_config (const char *key, const char *value)
                }
                rrdcreate_config.xff = tmp;
        }
+       else if (strcasecmp ("WritesPerSecond", key) == 0)
+       {
+               double wps = atof (value);
+
+               if (wps < 0.0)
+               {
+                       fprintf (stderr, "rrdtool: `WritesPerSecond' must be "
+                                       "greater than or equal to zero.");
+                       return (1);
+               }
+               else if (wps == 0.0)
+               {
+                       write_rate = 0.0;
+               }
+               else
+               {
+                       write_rate = 1.0 / wps;
+               }
+       }
        else
        {
                return (-1);
index 07465dd..877aafe 100644 (file)
@@ -859,6 +859,7 @@ static int csnmp_instance_list_add (csnmp_list_instances_t **head,
     char *ptr;
     size_t instance_len;
 
+    memset (il->instance, 0, sizeof (il->instance));
     instance_len = sizeof (il->instance) - 1;
     if (instance_len > vb->val_len)
       instance_len = vb->val_len;
@@ -866,7 +867,7 @@ static int csnmp_instance_list_add (csnmp_list_instances_t **head,
     sstrncpy (il->instance, (char *) ((vb->type == ASN_OCTET_STR)
          ? vb->val.string
          : vb->val.bitstring),
-       instance_len);
+       instance_len + 1);
 
     for (ptr = il->instance; *ptr != '\0'; ptr++)
     {
@@ -1145,10 +1146,6 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
     vb = res->variables;
     if (vb == NULL)
     {
-      if (res != NULL)
-       snmp_free_pdu (res);
-      res = NULL;
-
       status = -1;
       break;
     }
@@ -1157,10 +1154,7 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
      * subtree */
     if (csnmp_check_res_left_subtree (host, data, res) != 0)
     {
-      if (res != NULL)
-       snmp_free_pdu (res);
-      res = NULL;
-
+      status = 0;
       break;
     }
 
@@ -1182,11 +1176,7 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
          (vb != NULL) && (vb->next_variable != NULL);
          vb = vb->next_variable)
        /* do nothing */;
-      if (vb == NULL)
-      {
-       status = -1;
-       break;
-      }
+      assert (vb != NULL);
 
       /* Copy OID to oid_list[data->values_len] */
       memcpy (oid_list[data->values_len].oid, vb->name,
@@ -1249,6 +1239,10 @@ static int csnmp_read_table (host_definition_t *host, data_definition_t *data)
     res = NULL;
   } /* while (status == 0) */
 
+  if (res != NULL)
+    snmp_free_pdu (res);
+  res = NULL;
+
   if (status == 0)
     csnmp_dispatch_table (host, data, instance_list, value_table);
 
index f9debc1..8c09e35 100644 (file)
@@ -56,10 +56,10 @@ static kstat_t *ksp;
 /* No global variables */
 /* #endif defined(VM_SWAPUSAGE) */
 
-#elif HAVE_LIBKVM
+#elif HAVE_LIBKVM_GETSWAPINFO
 static kvm_t *kvm_obj = NULL;
 int kvm_pagesize;
-/* #endif HAVE_LIBKVM */
+/* #endif HAVE_LIBKVM_GETSWAPINFO */
 
 #elif HAVE_LIBSTATGRAB
 /* No global variables */
@@ -86,7 +86,7 @@ static int swap_init (void)
        /* No init stuff */
 /* #endif defined(VM_SWAPUSAGE) */
 
-#elif HAVE_LIBKVM
+#elif HAVE_LIBKVM_GETSWAPINFO
        if (kvm_obj != NULL)
        {
                kvm_close (kvm_obj);
@@ -105,7 +105,7 @@ static int swap_init (void)
                ERROR ("swap plugin: kvm_open failed.");
                return (-1);
        }
-/* #endif HAVE_LIBKVM */
+/* #endif HAVE_LIBKVM_GETSWAPINFO */
 
 #elif HAVE_LIBSTATGRAB
        /* No init stuff */
@@ -260,7 +260,7 @@ static int swap_read (void)
        swap_submit ("free", sw_usage.xsu_avail);
 /* #endif VM_SWAPUSAGE */
 
-#elif HAVE_LIBKVM
+#elif HAVE_LIBKVM_GETSWAPINFO
        struct kvm_swap data_s;
        int             status;
 
@@ -286,7 +286,7 @@ static int swap_read (void)
 
        swap_submit ("used", used);
        swap_submit ("free", free);
-/* #endif HAVE_LIBKVM */
+/* #endif HAVE_LIBKVM_GETSWAPINFO */
 
 #elif HAVE_LIBSTATGRAB
        sg_swap_stats *swap;
index 655c53e..d39a6c3 100644 (file)
  *   Florian octo Forster <octo at verplant.org>
  **/
 
+/**
+ * Code within `HAVE_LIBKVM_NLIST' blocks is provided under the following
+ * license:
+ *
+ * $collectd: parts of tcpconns.c, 2008/08/08 03:48:30 Michael Stapelberg $
+ * $OpenBSD: inet.c,v 1.100 2007/06/19 05:28:30 ray Exp $
+ * $NetBSD: inet.c,v 1.14 1995/10/03 21:42:37 thorpej Exp $
+ *
+ * Copyright (c) 1983, 1988, 1993
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
 
-#if !KERNEL_LINUX && !HAVE_SYSCTLBYNAME
+#if !KERNEL_LINUX && !HAVE_SYSCTLBYNAME && !HAVE_LIBKVM_NLIST
 # error "No applicable input method."
 #endif
 
 # include <netinet/tcpip.h>
 # include <netinet/tcp_seq.h>
 # include <netinet/tcp_var.h>
-#endif /* HAVE_SYSCTLBYNAME */
+/* #endif HAVE_SYSCTLBYNAME */
+
+/* This is for OpenBSD and possibly NetBSD. */
+#elif HAVE_LIBKVM_NLIST
+# include <sys/queue.h>
+# include <sys/socket.h>
+# include <net/route.h>
+# include <netinet/in.h>
+# include <netinet/in_systm.h>
+# include <netinet/ip.h>
+# include <netinet/in_pcb.h>
+# include <netinet/tcp.h>
+# include <netinet/tcp_timer.h>
+# include <netinet/tcp_var.h>
+# include <netdb.h>
+# include <arpa/inet.h>
+# include <nlist.h>
+# include <kvm.h>
+#endif /* HAVE_LIBKVM_NLIST */
 
 #if KERNEL_LINUX
 static const char *tcp_state[] =
@@ -100,7 +154,32 @@ static const char *tcp_state[] =
 # define TCP_STATE_LISTEN 1
 # define TCP_STATE_MIN 0
 # define TCP_STATE_MAX 10
-#endif /* HAVE_SYSCTLBYNAME */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif HAVE_LIBKVM_NLIST
+static const char *tcp_state[] =
+{
+  "CLOSED",
+  "LISTEN",
+  "SYN_SENT",
+  "SYN_RECV",
+  "ESTABLISHED",
+  "CLOSE_WAIT",
+  "FIN_WAIT1",
+  "CLOSING",
+  "LAST_ACK",
+  "FIN_WAIT2",
+  "TIME_WAIT"
+};
+
+static kvm_t *kvmd;
+static u_long      inpcbtable_off = 0;
+struct inpcbtable *inpcbtable_ptr = NULL;
+
+# define TCP_STATE_LISTEN 1
+# define TCP_STATE_MIN 1
+# define TCP_STATE_MAX 10
+#endif /* HAVE_LIBKVM_NLIST */
 
 #define PORT_COLLECT_LOCAL  0x01
 #define PORT_COLLECT_REMOTE 0x02
@@ -143,7 +222,7 @@ static void conn_submit_port_entry (port_entry_t *pe)
       || (pe->flags & PORT_COLLECT_LOCAL))
   {
     ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
-       "%hu-local", pe->port);
+       "%"PRIu16"-local", pe->port);
 
     for (i = 1; i <= TCP_STATE_MAX; i++)
     {
@@ -158,7 +237,7 @@ static void conn_submit_port_entry (port_entry_t *pe)
   if (pe->flags & PORT_COLLECT_REMOTE)
   {
     ssnprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
-       "%hu-remote", pe->port);
+       "%"PRIu16"-remote", pe->port);
 
     for (i = 1; i <= TCP_STATE_MAX; i++)
     {
@@ -224,7 +303,7 @@ static void conn_reset_port_entry (void)
       port_entry_t *next = pe->next;
 
       DEBUG ("tcpconns plugin: Removing temporary entry "
-         "for listening port %hu", pe->port);
+         "for listening port %"PRIu16, pe->port);
 
       if (prev == NULL)
        port_list_head = next;
@@ -255,8 +334,8 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t
 #endif
      )
   {
-    NOTICE ("tcpconns plugin: Ignoring connection with unknown state 0x%02x.",
-       state);
+    NOTICE ("tcpconns plugin: Ignoring connection with "
+       "unknown state 0x%02"PRIx8".", state);
     return (-1);
   }
 
@@ -268,7 +347,7 @@ static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t
       pe->flags |= PORT_IS_LISTENING;
   }
 
-  DEBUG ("tcpconns plugin: Connection %hu <-> %hu (%s)",
+  DEBUG ("tcpconns plugin: Connection %"PRIu16" <-> %"PRIu16" (%s)",
       port_local, port_remote, tcp_state[state]);
 
   pe = conn_get_port_entry (port_local, 0 /* no create */);
@@ -360,7 +439,10 @@ static int conn_read_file (const char *file)
 /* #endif KERNEL_LINUX */
 
 #elif HAVE_SYSCTLBYNAME
-#endif /* HAVE_SYSCTLBYNAME */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif HAVE_LIBKVM_NLIST
+#endif /* HAVE_LIBKVM_NLIST */
 
 static int conn_config (const char *key, const char *value)
 {
@@ -514,7 +596,107 @@ static int conn_read (void)
 
   return (0);
 } /* int conn_read */
-#endif /* HAVE_SYSCTLBYNAME */
+/* #endif HAVE_SYSCTLBYNAME */
+
+#elif HAVE_LIBKVM_NLIST
+static int kread (u_long addr, void *buf, int size)
+{
+  int status;
+
+  status = kvm_read (kvmd, addr, buf, size);
+  if (status != size)
+  {
+    ERROR ("tcpconns plugin: kvm_read failed (got %i, expected %i): %s\n",
+       status, size, kvm_geterr (kvmd));
+    return (-1);
+  }
+  return (0);
+} /* int kread */
+
+static int conn_init (void)
+{
+  char buf[_POSIX2_LINE_MAX];
+  struct nlist nl[] =
+  {
+#define N_TCBTABLE 0
+    { "_tcbtable" },
+    { "" }
+  };
+  int status;
+
+  kvmd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, buf);
+  if (kvmd == NULL)
+  {
+    ERROR ("tcpconns plugin: kvm_openfiles failed: %s", buf);
+    return (-1);
+  }
+
+  status = kvm_nlist (kvmd, nl);
+  if (status < 0)
+  {
+    ERROR ("tcpconns plugin: kvm_nlist failed with status %i.", status);
+    return (-1);
+  }
+
+  if (nl[N_TCBTABLE].n_type == 0)
+  {
+    ERROR ("tcpconns plugin: Error looking up kernel's namelist: "
+       "N_TCBTABLE is invalid.");
+    return (-1);
+  }
+
+  inpcbtable_off = (u_long) nl[N_TCBTABLE].n_value;
+  inpcbtable_ptr = (struct inpcbtable *) nl[N_TCBTABLE].n_value;
+
+  return (0);
+} /* int conn_init */
+
+static int conn_read (void)
+{
+  struct inpcbtable table;
+  struct inpcb *head;
+  struct inpcb *next;
+  struct inpcb inpcb;
+  struct tcpcb tcpcb;
+  int status;
+
+  conn_reset_port_entry ();
+
+  /* Read the pcbtable from the kernel */
+  status = kread (inpcbtable_off, &table, sizeof (table));
+  if (status != 0)
+    return (-1);
+
+  /* Get the `head' pcb */
+  head = (struct inpcb *) &(inpcbtable_ptr->inpt_queue);
+  /* Get the first pcb */
+  next = CIRCLEQ_FIRST (&table.inpt_queue);
+
+  while (next != head)
+  {
+    /* Read the pcb pointed to by `next' into `inpcb' */
+    kread ((u_long) next, &inpcb, sizeof (inpcb));
+
+    /* Advance `next' */
+    next = CIRCLEQ_NEXT (&inpcb, inp_queue);
+
+    /* Ignore sockets, that are not connected. */
+    if (!(inpcb.inp_flags & INP_IPV6)
+       && (inet_lnaof(inpcb.inp_laddr) == INADDR_ANY))
+      continue;
+    if ((inpcb.inp_flags & INP_IPV6)
+       && IN6_IS_ADDR_UNSPECIFIED (&inpcb.inp_laddr6))
+      continue;
+
+    kread ((u_long) inpcb.inp_ppcb, &tcpcb, sizeof (tcpcb));
+    conn_handle_ports (ntohs(inpcb.inp_lport), ntohs(inpcb.inp_fport), tcpcb.t_state);
+  } /* while (next != head) */
+
+  conn_submit_all ();
+
+  return (0);
+}
+#endif /* HAVE_LIBKVM_NLIST */
 
 void module_register (void)
 {
@@ -522,10 +704,14 @@ void module_register (void)
                        config_keys, config_keys_num);
 #if KERNEL_LINUX
        plugin_register_init ("tcpconns", conn_init);
+#elif HAVE_SYSCTLBYNAME
+       /* no initialization */
+#elif HAVE_LIBKVM_NLIST
+       plugin_register_init ("tcpconns", conn_init);
 #endif
        plugin_register_read ("tcpconns", conn_read);
 } /* void module_register */
 
 /*
- * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
+ * vim: set shiftwidth=2 softtabstop=2 tabstop=8 fdm=marker :
  */
index 9378132..b613648 100644 (file)
@@ -61,7 +61,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 *name)
+static int thermal_sysfs_device_read (const char *dir, const char *name,
+               void *user_data)
 {
        char filename[256];
        char data[1024];
@@ -112,7 +113,8 @@ static int thermal_sysfs_device_read (const char *name)
        return ok ? 0 : -1;
 }
 
-static int thermal_procfs_device_read (const char *name)
+static int thermal_procfs_device_read (const char *dir, const char *name,
+               void *user_data)
 {
        const char str_temp[] = "temperature:";
        char filename[256];
@@ -215,12 +217,14 @@ static int thermal_config (const char *key, const char *value)
 
 static int thermal_sysfs_read (void)
 {
-       return walk_directory (dirname_sysfs, thermal_sysfs_device_read);
+       return walk_directory (dirname_sysfs, thermal_sysfs_device_read,
+                       /* user_data = */ NULL);
 }
 
 static int thermal_procfs_read (void)
 {
-       return walk_directory (dirname_procfs, thermal_procfs_device_read);
+       return walk_directory (dirname_procfs, thermal_procfs_device_read,
+                       /* user_data = */ NULL);
 }
 
 static int thermal_init (void)
index 80d6cee..21d15d6 100644 (file)
@@ -3,6 +3,7 @@ apache_connections      count:GAUGE:0:65535
 apache_requests                count:COUNTER:0:134217728
 apache_scoreboard      count:GAUGE:0:65535
 bitrate                        value:GAUGE:0:4294967295
+bytes                  value:GAUGE:0:U
 cache_result           value:COUNTER:0:4294967295
 cache_size             value:GAUGE:0:4294967295
 charge                 value:GAUGE:0:U
@@ -28,6 +29,7 @@ email_count           value:GAUGE:0:U
 email_size             value:GAUGE:0:U
 entropy                        entropy:GAUGE:0:4294967295
 fanspeed               value:GAUGE:0:U
+files                  value:GAUGE:0:U
 frequency              frequency:GAUGE:0:U
 frequency_offset       ppm:GAUGE:-1000000:1000000
 gauge                  value:GAUGE:U:U
@@ -65,6 +67,7 @@ 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
 pg_n_tup_c             value:COUNTER:0:U
 pg_n_tup_g             value:GAUGE:0:U
 pg_numbackends         value:GAUGE:0:U
index d34b91f..d800906 100644 (file)
@@ -159,15 +159,12 @@ static void *us_handle_client (void *arg)
 {
        int fd;
        FILE *fhin, *fhout;
-       char buffer[1024];
-       char *fields[128];
-       int   fields_num;
 
        fd = *((int *) arg);
        free (arg);
        arg = NULL;
 
-       DEBUG ("Reading from fd #%i", fd);
+       DEBUG ("unixsock plugin: us_handle_client: Reading from fd #%i", fd);
 
        fhin  = fdopen (fd, "r");
        if (fhin == NULL)
@@ -202,7 +199,11 @@ static void *us_handle_client (void *arg)
 
        while (42)
        {
-               int len;
+               char buffer[1024];
+               char buffer_copy[1024];
+               char *fields[128];
+               int   fields_num;
+               int   len;
 
                errno = 0;
                if (fgets (buffer, sizeof (buffer), fhin) == NULL)
@@ -225,9 +226,9 @@ static void *us_handle_client (void *arg)
                if (len == 0)
                        continue;
 
-               DEBUG ("fgets -> buffer = %s; len = %i;", buffer, len);
+               sstrncpy (buffer_copy, buffer, sizeof (buffer_copy));
 
-               fields_num = strsplit (buffer, fields,
+               fields_num = strsplit (buffer_copy, fields,
                                sizeof (fields) / sizeof (fields[0]));
 
                if (fields_num < 1)
@@ -238,23 +239,23 @@ static void *us_handle_client (void *arg)
 
                if (strcasecmp (fields[0], "getval") == 0)
                {
-                       handle_getval (fhout, fields, fields_num);
+                       handle_getval (fhout, buffer);
                }
                else if (strcasecmp (fields[0], "putval") == 0)
                {
-                       handle_putval (fhout, fields, fields_num);
+                       handle_putval (fhout, buffer);
                }
                else if (strcasecmp (fields[0], "listval") == 0)
                {
-                       handle_listval (fhout, fields, fields_num);
+                       handle_listval (fhout, buffer);
                }
                else if (strcasecmp (fields[0], "putnotif") == 0)
                {
-                       handle_putnotif (fhout, fields, fields_num);
+                       handle_putnotif (fhout, buffer);
                }
                else if (strcasecmp (fields[0], "flush") == 0)
                {
-                       handle_flush (fhout, fields, fields_num);
+                       handle_flush (fhout, buffer);
                }
                else
                {
@@ -269,7 +270,7 @@ static void *us_handle_client (void *arg)
                }
        } /* while (fgets) */
 
-       DEBUG ("Exiting..");
+       DEBUG ("unixsock plugin: us_handle_client: Exiting..");
        fclose (fhin);
        fclose (fhout);
 
index 7feaac2..0e7b350 100644 (file)
@@ -24,6 +24,7 @@
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
+#include "utils_parse_option.h"
 
 #define print_to_socket(fh, ...) \
        if (fprintf (fh, __VA_ARGS__) < 0) { \
@@ -48,7 +49,7 @@ static int add_to_array (char ***array, int *array_num, char *value)
        return (0);
 } /* int add_to_array */
 
-int handle_flush (FILE *fh, char **fields, int fields_num)
+int handle_flush (FILE *fh, char *buffer)
 {
        int success = 0;
        int error   = 0;
@@ -61,47 +62,71 @@ int handle_flush (FILE *fh, char **fields, int fields_num)
 
        int i;
 
-       for (i = 1; i < fields_num; i++)
+       if ((fh == NULL) || (buffer == NULL))
+               return (-1);
+
+       DEBUG ("utils_cmd_flush: handle_flush (fh = %p, buffer = %s);",
+                       (void *) fh, buffer);
+
+       if (strncasecmp ("FLUSH", buffer, strlen ("FLUSH")) != 0)
        {
-               char *option = fields[i];
-               int   status = 0;
+               print_to_socket (fh, "-1 Cannot parse command.\n");
+               return (-1);
+       }
+       buffer += strlen ("FLUSH");
 
-               if (strncasecmp ("plugin=", option, strlen ("plugin=")) == 0)
+       while (*buffer != 0)
+       {
+               char *opt_key;
+               char *opt_value;
+               int status;
+
+               opt_key = NULL;
+               opt_value = NULL;
+               status = parse_option (&buffer, &opt_key, &opt_value);
+               if (status != 0)
                {
-                       char *plugin;
-                       
-                       plugin = option + strlen ("plugin=");
-                       add_to_array (&plugins, &plugins_num, plugin);
+                       print_to_socket (fh, "-1 Parsing options failed.\n");
+                       sfree (plugins);
+                       sfree (identifiers);
+                       return (-1);
                }
-               else if (strncasecmp ("identifier=", option, strlen ("identifier=")) == 0)
-               {
-                       char *identifier;
 
-                       identifier = option + strlen ("identifier=");
-                       add_to_array (&identifiers, &identifiers_num, identifier);
+               if (strcasecmp ("plugin", opt_key) == 0)
+               {
+                       add_to_array (&plugins, &plugins_num, opt_value);
                }
-               else if (strncasecmp ("timeout=", option, strlen ("timeout=")) == 0)
+               else if (strcasecmp ("identifier", opt_key) == 0)
                {
-                       char *endptr = NULL;
-                       char *value  = option + strlen ("timeout=");
-
+                       add_to_array (&identifiers, &identifiers_num, opt_value);
+               }
+               else if (strcasecmp ("timeout", opt_key) == 0)
+               {
+                       char *endptr;
+                       
                        errno = 0;
-                       timeout = strtol (value, &endptr, 0);
-
-                       if ((endptr == value) || (0 != errno))
-                               status = -1;
-                       else if (0 >= timeout)
+                       endptr = NULL;
+                       timeout = strtol (opt_value, &endptr, 0);
+
+                       if ((endptr == opt_value) || (errno != 0))
+                       {
+                               print_to_socket (fh, "-1 Invalid value for option `timeout': "
+                                               "%s\n", opt_value);
+                               sfree (plugins);
+                               sfree (identifiers);
+                               return (-1);
+                       }
+                       else if (timeout <= 0)
                                timeout = -1;
                }
                else
-                       status = -1;
-
-               if (status != 0)
                {
-                       print_to_socket (fh, "-1 Cannot parse option %s\n", option);
+                       print_to_socket (fh, "-1 Cannot parse option %s\n", opt_key);
+                       sfree (plugins);
+                       sfree (identifiers);
                        return (-1);
                }
-       }
+       } /* while (*buffer != 0) */
 
        /* Add NULL entries for `any plugin' and/or `any value' if nothing was
         * specified. */
@@ -139,10 +164,12 @@ int handle_flush (FILE *fh, char **fields, int fields_num)
        }
        else
        {
-               plugin_flush_all (timeout);
+               plugin_flush (NULL, timeout, NULL);
                print_to_socket (fh, "0 Done\n");
        }
 
+       sfree (plugins);
+       sfree (identifiers);
        return (0);
 } /* int handle_flush */
 
index 334f086..dccafd1 100644 (file)
@@ -22,7 +22,7 @@
 #ifndef UTILS_CMD_FLUSH_H
 #define UTILS_CMD_FLUSH_H 1
 
-int handle_flush (FILE *fh, char **fields, int fields_num);
+int handle_flush (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_FLUSH_H */
 
index 470d302..186ef9b 100644 (file)
@@ -24,6 +24,7 @@
 #include "plugin.h"
 
 #include "utils_cache.h"
+#include "utils_parse_option.h"
 
 #define print_to_socket(fh, ...) \
   if (fprintf (fh, __VA_ARGS__) < 0) { \
     return -1; \
   }
 
-int handle_getval (FILE *fh, char **fields, int fields_num)
+int handle_getval (FILE *fh, char *buffer)
 {
+  char *command;
+  char *identifier;
+  char *identifier_copy;
+
   char *hostname;
   char *plugin;
   char *plugin_instance;
@@ -43,39 +48,58 @@ int handle_getval (FILE *fh, char **fields, int fields_num)
   gauge_t *values;
   size_t values_num;
 
-  char *identifier_copy;
-
   const data_set_t *ds;
 
   int   status;
   int   i;
 
-  if (fields_num != 2)
+  if ((fh == NULL) || (buffer == NULL))
+    return (-1);
+
+  DEBUG ("utils_cmd_getval: handle_getval (fh = %p, buffer = %s);",
+      (void *) fh, buffer);
+
+  command = NULL;
+  status = parse_string (&buffer, &command);
+  if (status != 0)
   {
-    DEBUG ("unixsock plugin: Wrong number of fields: %i", fields_num);
-    print_to_socket (fh, "-1 Wrong number of fields: Got %i, expected 2.\n",
-       fields_num);
+    print_to_socket (fh, "-1 Cannot parse command.\n");
     return (-1);
   }
-  DEBUG ("unixsock plugin: Got query for `%s'", fields[1]);
+  assert (command != NULL);
 
-  if (strlen (fields[1]) < strlen ("h/p/t"))
+  if (strcasecmp ("GETVAL", command) != 0)
   {
-    print_to_socket (fh, "-1 Invalied identifier, %s\n", fields[1]);
+    print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
+    return (-1);
+  }
+
+  identifier = NULL;
+  status = parse_string (&buffer, &identifier);
+  if (status != 0)
+  {
+    print_to_socket (fh, "-1 Cannot parse identifier.\n");
+    return (-1);
+  }
+  assert (identifier != NULL);
+
+  if (*buffer != 0)
+  {
+    print_to_socket (fh, "-1 Garbage after end of command: %s\n", buffer);
     return (-1);
   }
 
   /* parse_identifier() modifies its first argument,
    * returning pointers into it */
-  identifier_copy = sstrdup (fields[1]);
+  identifier_copy = sstrdup (identifier);
 
   status = parse_identifier (identifier_copy, &hostname,
       &plugin, &plugin_instance,
       &type, &type_instance);
   if (status != 0)
   {
-    DEBUG ("unixsock plugin: Cannot parse `%s'", fields[1]);
-    print_to_socket (fh, "-1 Cannot parse identifier.\n");
+    DEBUG ("handle_getval: Cannot parse identifier `%s'.", identifier);
+    print_to_socket (fh, "-1 Cannot parse identifier `%s'.\n", identifier);
     sfree (identifier_copy);
     return (-1);
   }
@@ -83,7 +107,7 @@ int handle_getval (FILE *fh, char **fields, int fields_num)
   ds = plugin_get_ds (type);
   if (ds == NULL)
   {
-    DEBUG ("unixsock plugin: plugin_get_ds (%s) == NULL;", type);
+    DEBUG ("handle_getval: plugin_get_ds (%s) == NULL;", type);
     print_to_socket (fh, "-1 Type `%s' is unknown.\n", type);
     sfree (identifier_copy);
     return (-1);
@@ -91,7 +115,7 @@ int handle_getval (FILE *fh, char **fields, int fields_num)
 
   values = NULL;
   values_num = 0;
-  status = uc_get_rate_by_name (fields[1], &values, &values_num);
+  status = uc_get_rate_by_name (identifier, &values, &values_num);
   if (status != 0)
   {
     print_to_socket (fh, "-1 No such value\n");
index d7bd115..86134cd 100644 (file)
@@ -22,7 +22,7 @@
 #ifndef UTILS_CMD_GETVAL_H
 #define UTILS_CMD_GETVAL_H 1
 
-int handle_getval (FILE *fh, char **fields, int fields_num);
+int handle_getval (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_GETVAL_H */
 
index 6f03e75..bca83a9 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "utils_cmd_listval.h"
 #include "utils_cache.h"
+#include "utils_parse_option.h"
 
 #define print_to_socket(fh, ...) \
   if (fprintf (fh, __VA_ARGS__) < 0) { \
     return -1; \
   }
 
-int handle_listval (FILE *fh, char **fields, int fields_num)
+int handle_listval (FILE *fh, char *buffer)
 {
+  char *command;
   char **names = NULL;
   time_t *times = NULL;
   size_t number = 0;
   size_t i;
   int status;
 
-  if (fields_num != 1)
+  DEBUG ("utils_cmd_listval: handle_listval (fh = %p, buffer = %s);",
+      (void *) fh, buffer);
+
+  command = NULL;
+  status = parse_string (&buffer, &command);
+  if (status != 0)
+  {
+    print_to_socket (fh, "-1 Cannot parse command.\n");
+    return (-1);
+  }
+  assert (command != NULL);
+
+  if (strcasecmp ("LISTVAL", command) != 0)
+  {
+    print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
+    return (-1);
+  }
+
+  if (*buffer != 0)
   {
-    DEBUG ("command listval: us_handle_listval: Wrong number of fields: %i",
-       fields_num);
-    print_to_socket (fh, "-1 Wrong number of fields: Got %i, expected 1.\n",
-       fields_num);
+    print_to_socket (fh, "-1 Garbage after end of command: %s\n", buffer);
     return (-1);
   }
 
index c918796..73146e7 100644 (file)
@@ -22,7 +22,7 @@
 #ifndef UTILS_CMD_LISTVAL_H
 #define UTILS_CMD_LISTVAL_H 1
 
-int handle_listval (FILE *fh, char **fields, int fields_num);
+int handle_listval (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_LISTVAL_H */
 
index eb7d60b..5a9faff 100644 (file)
@@ -23,6 +23,8 @@
 #include "common.h"
 #include "plugin.h"
 
+#include "utils_parse_option.h"
+
 #define print_to_socket(fh, ...) \
   if (fprintf (fh, __VA_ARGS__) < 0) { \
     char errbuf[1024]; \
@@ -31,7 +33,7 @@
     return -1; \
   }
 
-static int parse_option_severity (notification_t *n, char *value)
+static int set_option_severity (notification_t *n, const char *value)
 {
   if (strcasecmp (value, "Failure") == 0)
     n->severity = NOTIF_FAILURE;
@@ -43,9 +45,9 @@ static int parse_option_severity (notification_t *n, char *value)
     return (-1);
 
   return (0);
-} /* int parse_option_severity */
+} /* int set_option_severity */
 
-static int parse_option_time (notification_t *n, char *value)
+static int set_option_time (notification_t *n, const char *value)
 {
   time_t tmp;
   
@@ -56,25 +58,22 @@ static int parse_option_time (notification_t *n, char *value)
   n->time = tmp;
 
   return (0);
-} /* int parse_option_time */
+} /* int set_option_time */
 
-static int parse_option (notification_t *n, char *buffer)
+static int set_option (notification_t *n, const char *option, const char *value)
 {
-  char *option = buffer;
-  char *value;
-
-  if ((n == NULL) || (option == NULL))
+  if ((n == NULL) || (option == NULL) || (value == NULL))
     return (-1);
 
-  value = strchr (option, '=');
-  if (value == NULL)
-    return (-1);
-  *value = '\0'; value++;
+  DEBUG ("utils_cmd_putnotif: set_option (option = %s, value = %s);",
+      option, value);
 
   if (strcasecmp ("severity", option) == 0)
-    return (parse_option_severity (n, value));
+    return (set_option_severity (n, value));
   else if (strcasecmp ("time", option) == 0)
-    return (parse_option_time (n, value));
+    return (set_option_time (n, value));
+  else if (strcasecmp ("message", option) == 0)
+    sstrncpy (n->message, value, sizeof (n->message));
   else if (strcasecmp ("host", option) == 0)
     sstrncpy (n->host, value, sizeof (n->host));
   else if (strcasecmp ("plugin", option) == 0)
@@ -89,61 +88,55 @@ static int parse_option (notification_t *n, char *buffer)
     return (1);
 
   return (0);
-} /* int parse_option */
+} /* int set_option */
 
-static int parse_message (notification_t *n, char **fields, int fields_num)
+int handle_putnotif (FILE *fh, char *buffer)
 {
+  char *command;
+  notification_t n;
   int status;
 
-  /* Strip off the leading `message=' */
-  fields[0] += strlen ("message=");
-
-  status = strjoin (n->message, sizeof (n->message), fields, fields_num, " ");
-  if (status < 0)
+  if ((fh == NULL) || (buffer == NULL))
     return (-1);
 
-  return (0);
-} /* int parse_message */
+  DEBUG ("utils_cmd_putnotif: handle_putnotif (fh = %p, buffer = %s);",
+      (void *) fh, buffer);
 
-int handle_putnotif (FILE *fh, char **fields, int fields_num)
-{
-  notification_t n;
-  int status;
-  int i;
+  command = NULL;
+  status = parse_string (&buffer, &command);
+  if (status != 0)
+  {
+    print_to_socket (fh, "-1 Cannot parse command.\n");
+    return (-1);
+  }
+  assert (command != NULL);
 
-  /* Required fields: `PUTNOTIF', severity, time, message */
-  if (fields_num < 4)
+  if (strcasecmp ("PUTNOTIF", command) != 0)
   {
-    DEBUG ("cmd putnotif: Wrong number of fields: %i", fields_num);
-    print_to_socket (fh, "-1 Wrong number of fields: Got %i, "
-       "expected at least 4.\n",
-       fields_num);
+    print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
     return (-1);
   }
 
   memset (&n, '\0', sizeof (n));
 
   status = 0;
-  for (i = 1; i < fields_num; i++)
+  while (*buffer != 0)
   {
-    if (strncasecmp (fields[i], "message=", strlen ("message=")) == 0)
+    char *key;
+    char *value;
+
+    status = parse_option (&buffer, &key, &value);
+    if (status != 0)
     {
-      status = parse_message (&n, fields + i, fields_num - i);
-      if (status != 0)
-      {
-       print_to_socket (fh, "-1 Error parsing the message. Have you hit the "
-           "limit of %u bytes?\n", (unsigned int) sizeof (n.message));
-      }
+      print_to_socket (fh, "-1 Malformed option.\n");
       break;
     }
-    else
+
+    status = set_option (&n, key, value);
+    if (status != 0)
     {
-      status = parse_option (&n, fields[i]);
-      if (status != 0)
-      {
-       print_to_socket (fh, "-1 Error parsing option `%s'\n", fields[i]);
-       break;
-      }
+      print_to_socket (fh, "-1 Error parsing option `%s'\n", key);
+      break;
     }
   } /* for (i) */
 
index 08b3bb3..8d5475b 100644 (file)
@@ -22,7 +22,7 @@
 #ifndef UTILS_CMD_PUTNOTIF_H
 #define UTILS_CMD_PUTNOTIF_H 1
 
-int handle_putnotif (FILE *fh, char **fields, int fields_num);
+int handle_putnotif (FILE *fh, char *buffer);
 
 /* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
 
index 9cbba12..91b7016 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/utils_cms_putval.c
- * Copyright (C) 2007  Florian octo Forster
+ * Copyright (C) 2007,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
@@ -23,6 +23,8 @@
 #include "common.h"
 #include "plugin.h"
 
+#include "utils_parse_option.h"
+
 #define print_to_socket(fh, ...) \
        if (fprintf (fh, __VA_ARGS__) < 0) { \
                char errbuf[1024]; \
@@ -93,24 +95,23 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
        return (0);
 } /* int parse_value */
 
-static int parse_option (value_list_t *vl, char *buffer)
+static int set_option (value_list_t *vl, const char *key, const char *value)
 {
-       char *option = buffer;
-       char *value;
-
-       if ((vl == NULL) || (option == NULL))
-               return (-1);
-
-       value = strchr (option, '=');
-       if (value == NULL)
+       if ((vl == NULL) || (key == NULL) || (value == NULL))
                return (-1);
-       *value = '\0'; value++;
 
-       if (strcasecmp ("interval", option) == 0)
+       if (strcasecmp ("interval", key) == 0)
        {
-               vl->interval = atoi (value);
-               if (vl->interval <= 0)
-                       vl->interval = interval_g;
+               int tmp;
+               char *endptr;
+
+               endptr = NULL;
+               errno = 0;
+               tmp = strtol (value, &endptr, 0);
+
+               if ((errno == 0) && (endptr != NULL)
+                               && (endptr != value) && (tmp > 0))
+                       vl->interval = tmp;
        }
        else
                return (1);
@@ -118,42 +119,63 @@ static int parse_option (value_list_t *vl, char *buffer)
        return (0);
 } /* int parse_option */
 
-int handle_putval (FILE *fh, char **fields, int fields_num)
+int handle_putval (FILE *fh, char *buffer)
 {
+       char *command;
+       char *identifier;
        char *hostname;
        char *plugin;
        char *plugin_instance;
        char *type;
        char *type_instance;
        int   status;
-       int   i;
+       int   values_submitted;
 
        char *identifier_copy;
 
        const data_set_t *ds;
        value_list_t vl = VALUE_LIST_INIT;
 
-       if (fields_num < 3)
+       DEBUG ("utils_cmd_putval: handle_putval (fh = %p, buffer = %s);",
+                       (void *) fh, buffer);
+
+       command = NULL;
+       status = parse_string (&buffer, &command);
+       if (status != 0)
        {
-               DEBUG ("cmd putval: Wrong number of fields: %i",
-                               fields_num);
-               print_to_socket (fh, "-1 Wrong number of fields: Got %i, "
-                               "expected at least 3.\n",
-                               fields_num);
+               print_to_socket (fh, "-1 Cannot parse command.\n");
                return (-1);
        }
+       assert (command != NULL);
+
+       if (strcasecmp ("PUTVAL", command) != 0)
+       {
+               print_to_socket (fh, "-1 Unexpected command: `%s'.\n", command);
+               return (-1);
+       }
+
+       identifier = NULL;
+       status = parse_string (&buffer, &identifier);
+       if (status != 0)
+       {
+               print_to_socket (fh, "-1 Cannot parse identifier.\n");
+               return (-1);
+       }
+       assert (identifier != NULL);
 
        /* parse_identifier() modifies its first argument,
         * returning pointers into it */
-       identifier_copy = sstrdup (fields[1]);
+       identifier_copy = sstrdup (identifier);
 
        status = parse_identifier (identifier_copy, &hostname,
                        &plugin, &plugin_instance,
                        &type, &type_instance);
        if (status != 0)
        {
-               DEBUG ("cmd putval: Cannot parse `%s'", fields[1]);
-               print_to_socket (fh, "-1 Cannot parse identifier.\n");
+               DEBUG ("handle_putval: Cannot parse identifier `%s'.",
+                               identifier);
+               print_to_socket (fh, "-1 Cannot parse identifier `%s'.\n",
+                               identifier);
                sfree (identifier_copy);
                return (-1);
        }
@@ -180,54 +202,73 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
 
        ds = plugin_get_ds (type);
        if (ds == NULL) {
+               print_to_socket (fh, "-1 Type `%s' isn't defined.\n", type);
                sfree (identifier_copy);
                return (-1);
        }
 
+       /* Free identifier_copy */
+       hostname = NULL;
+       plugin = NULL; plugin_instance = NULL;
+       type = NULL;   type_instance = NULL;
+       sfree (identifier_copy);
+
        vl.values_len = ds->ds_num;
        vl.values = (value_t *) malloc (vl.values_len * sizeof (value_t));
        if (vl.values == NULL)
        {
                print_to_socket (fh, "-1 malloc failed.\n");
-               sfree (identifier_copy);
                return (-1);
        }
 
        /* All the remaining fields are part of the optionlist. */
-       for (i = 2; i < fields_num; i++)
+       values_submitted = 0;
+       while (*buffer != 0)
        {
-               if (strchr (fields[i], ':') != NULL)
+               char *string = NULL;
+               char *value  = NULL;
+
+               status = parse_option (&buffer, &string, &value);
+               if (status < 0)
                {
-                       /* It's parse_value's job to write an error to `fh'.
-                        * This is not the case with `parse_option below.
-                        * Neither will write an success message. */
-                       if (parse_value (ds, &vl, fh, fields[i]) != 0)
-                               break;
+                       /* parse_option failed, buffer has been modified.
+                        * => we need to abort */
+                       print_to_socket (fh, "-1 Misformatted option.\n");
+                       return (-1);
                }
-               else if (strchr (fields[i], '=') != NULL)
+               else if (status == 0)
                {
-                       if (parse_option (&vl, fields[i]) != 0)
-                       {
-                               print_to_socket (fh, "-1 Error parsing option `%s'\n",
-                                               fields[i]);
-                               break;
-                       }
+                       assert (string != NULL);
+                       assert (value != NULL);
+                       set_option (&vl, string, value);
+                       continue;
                }
-               else
+               /* else: parse_option but buffer has not been modified. This is
+                * the default if no `=' is found.. */
+
+               status = parse_string (&buffer, &string);
+               if (status != 0)
                {
-                       WARNING ("cmd putval: handle_putval: "
-                                       "Cannot parse field #%i `%s'; "
-                                       "Ignoring it.\n",
-                                       i, fields[i]);
+                       print_to_socket (fh, "-1 Misformatted value.\n");
+                       return (-1);
                }
-       }
+               assert (string != NULL);
+
+               status = parse_value (ds, &vl, fh, string);
+               if (status != 0)
+               {
+                       /* An error has already been printed. */
+                       return (-1);
+               }
+               values_submitted++;
+       } /* while (*buffer != 0) */
        /* Done parsing the options. */
 
-       if (i == fields_num)
-               print_to_socket (fh, "0 Success\n");
+       print_to_socket (fh, "0 Success: %i %s been dispatched.\n",
+                       values_submitted,
+                       (values_submitted == 1) ? "value has" : "values have");
 
        sfree (vl.values); 
-       sfree (identifier_copy);
 
        return (0);
 } /* int handle_putval */
index 2ae4532..9ba52cc 100644 (file)
@@ -22,6 +22,6 @@
 #ifndef UTILS_CMD_PUTVAL_H
 #define UTILS_CMD_PUTVAL_H 1
 
-int handle_putval (FILE *fh, char **fields, int fields_num);
+int handle_putval (FILE *fh, char *buffer);
 
 #endif /* UTILS_CMD_PUTVAL_H */
diff --git a/src/utils_parse_option.c b/src/utils_parse_option.c
new file mode 100644 (file)
index 0000000..d94c140
--- /dev/null
@@ -0,0 +1,153 @@
+/**
+ * collectd - src/utils_parse_option.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
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_parse_option.h"
+
+int parse_string (char **ret_buffer, char **ret_string)
+{
+  char *buffer;
+  char *string;
+
+  buffer = *ret_buffer;
+
+  /* Eat up leading spaces. */
+  string = buffer;
+  while (isspace ((int) *string))
+    string++;
+  if (*string == 0)
+    return (1);
+
+  /* A quoted string */
+  if (*string == '"')
+  {
+    char *dst;
+
+    string++;
+    if (*string == 0)
+      return (1);
+
+    dst = string;
+    buffer = string;
+    while ((*buffer != '"') && (*buffer != 0))
+    {
+      /* Un-escape backslashes */
+      if (*buffer == '\\')
+      {
+        buffer++;
+        /* Catch a backslash at the end of buffer */
+        if (*buffer == 0)
+          return (-1);
+      }
+      *dst = *buffer;
+      buffer++;
+      dst++;
+    }
+    /* No quote sign has been found */
+    if (*buffer == 0)
+      return (-1);
+
+    *dst = 0;
+    dst++;
+    *buffer = 0;
+    buffer++;
+
+    /* Check for trailing spaces. */
+    if ((*buffer != 0) && !isspace ((int) *buffer))
+      return (-1);
+  }
+  else /* an unquoted string */
+  {
+    buffer = string;
+    while ((*buffer != 0) && !isspace ((int) *buffer))
+      buffer++;
+    if (*buffer != 0)
+    {
+      *buffer = 0;
+      buffer++;
+    }
+  }
+  
+  /* Eat up trailing spaces */
+  while (isspace ((int) *buffer))
+    buffer++;
+
+  *ret_buffer = buffer;
+  *ret_string = string;
+
+  return (0);
+} /* int parse_string */
+
+/*
+ * parse_option
+ * ------------
+ *  Parses an ``option'' as used with the unixsock and exec commands. An
+ *  option is of the form:
+ *    name0="value"
+ *    name1="value with \"quotes\""
+ *    name2="value \\ backslash"
+ *  However, if the value does *not* contain a space character, you can skip
+ *  the quotes.
+ */
+int parse_option (char **ret_buffer, char **ret_key, char **ret_value)
+{
+  char *buffer;
+  char *key;
+  char *value;
+  int status;
+
+  buffer = *ret_buffer;
+
+  /* Eat up leading spaces */
+  key = buffer;
+  while (isspace ((int) *key))
+    key++;
+  if (*key == 0)
+    return (1);
+
+  /* Look for the equal sign */
+  buffer = key;
+  while (isalnum ((int) *buffer))
+    buffer++;
+  if ((*buffer != '=') || (buffer == key))
+    return (1);
+  *buffer = 0;
+  buffer++;
+  /* Empty values must be written as "" */
+  if (isspace ((int) *buffer) || (*buffer == 0))
+    return (-1);
+
+  status = parse_string (&buffer, &value);
+  if (status != 0)
+    return (-1);
+
+  /* NB: parse_string will have eaten up all trailing spaces. */
+
+  *ret_buffer = buffer;
+  *ret_key = key;
+  *ret_value = value;
+
+  return (0);
+} /* int parse_option */
+
+/* vim: set sw=2 ts=8 tw=78 et : */
diff --git a/src/utils_parse_option.h b/src/utils_parse_option.h
new file mode 100644 (file)
index 0000000..cb7f6d3
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * collectd - src/utils_parse_option.h
+ * 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
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_PARSE_OPTION
+#define UTILS_PARSE_OPTION 1
+
+int parse_string (char **ret_buffer, char **ret_string);
+int parse_option (char **ret_buffer, char **ret_key, char **ret_value);
+
+#endif /* UTILS_PARSE_OPTION */
+
+/* vim: set sw=2 ts=8 tw=78 et : */
index bea5bfb..68b85a3 100644 (file)
@@ -862,6 +862,7 @@ int ut_check_interesting (const char *name)
   if (status != 0)
   {
     ERROR ("ut_check_interesting: parse_identifier failed.");
+    sfree (name_copy);
     return (-1);
   }
 
index 0049f7d..246c7a8 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEFAULT_VERSION="4.4.2.git"
+DEFAULT_VERSION="4.5.0.git"
 
 VERSION="$( git describe 2> /dev/null | sed -e 's/^collectd-//' )"