Merge branch 'collectd-4.2' into collectd-4.3
authorFlorian Forster <sifnfors@informatik.stud.uni-erlangen.de>
Tue, 22 Apr 2008 11:17:05 +0000 (13:17 +0200)
committerFlorian Forster <sifnfors@informatik.stud.uni-erlangen.de>
Tue, 22 Apr 2008 11:17:05 +0000 (13:17 +0200)
83 files changed:
.gitignore
AUTHORS
ChangeLog
Makefile.am
README
TODO
bindings/Makefile.am
bindings/perl/Collectd.pm
bindings/perl/Collectd/Unixsock.pm
configure.in
contrib/README
contrib/examples/MyPlugin.pm
contrib/examples/myplugin.c
contrib/exec-munin.px
contrib/exec-nagios.conf [new file with mode: 0644]
contrib/exec-nagios.px [new file with mode: 0755]
contrib/extractDS.px [deleted file]
contrib/migrate-3-4.px
contrib/network-proxy.py [new file with mode: 0644]
contrib/redhat/apache.conf [new file with mode: 0644]
contrib/redhat/collectd.conf [new file with mode: 0644]
contrib/redhat/collectd.spec [new file with mode: 0644]
contrib/redhat/email.conf [new file with mode: 0644]
contrib/redhat/init.d-collectd [new file with mode: 0644]
contrib/redhat/mysql.conf [new file with mode: 0644]
contrib/redhat/nginx.conf [new file with mode: 0644]
contrib/redhat/sensors.conf [new file with mode: 0644]
contrib/redhat/snmp.conf [new file with mode: 0644]
contrib/rrd_filter.px [new file with mode: 0755]
src/Makefile.am
src/apcups.c
src/battery.c
src/collectd-exec.pod
src/collectd-perl.pod
src/collectd-snmp.pod
src/collectd-unixsock.pod
src/collectd.c
src/collectd.conf.in
src/collectd.conf.pod
src/collectd.pod
src/collectdmon.c [new file with mode: 0644]
src/collectdmon.pod [new file with mode: 0644]
src/common.c
src/common.h
src/configfile.c
src/cpu.c
src/cpufreq.c
src/csv.c
src/disk.c
src/exec.c
src/hddtemp.c
src/iptables.c
src/irq.c
src/liboconfig/parser.y
src/libvirt.c [new file with mode: 0644]
src/logfile.c
src/mbmon.c
src/network.c
src/network.h
src/ntpd.c
src/perl.c
src/plugin.c
src/plugin.h
src/types.db
src/types.db.pod [new file with mode: 0644]
src/types_list.c
src/types_list.h
src/unixsock.c
src/utils_avltree.c
src/utils_avltree.h
src/utils_cache.c [new file with mode: 0644]
src/utils_cache.h [new file with mode: 0644]
src/utils_cmd_putnotif.c [new file with mode: 0644]
src/utils_cmd_putnotif.h [new file with mode: 0644]
src/utils_cmd_putval.c
src/utils_dns.c
src/utils_ignorelist.c
src/utils_llist.c
src/utils_llist.h
src/utils_threshold.c [new file with mode: 0644]
src/utils_threshold.h [new file with mode: 0644]
src/uuid.c [new file with mode: 0644]
version-gen.sh

index 855e36b..5eb8974 100644 (file)
@@ -46,6 +46,7 @@ src/*.o
 src/collectd
 src/collectd*.1
 src/collectd*.5
+src/types.db.5
 src/config.h.in~
 src/liboconfig/.libs
 src/liboconfig/*.la
diff --git a/AUTHORS b/AUTHORS
index ee7e392..bd5daa4 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,52 +4,59 @@ This package was written by:
 apcups plugin by:
   Anthony Gialluca <tonyabg at charter.net>
 
-cpufreq, multimeter and irq module by:
+cpufreq, multimeter and irq plugin by:
   Peter Holik <peter at holik.at>
 
-hddtemp module by:
+hddtemp plugin by:
   Vincent Stehlé <vincent.stehle at free.fr>
 
-iptables module by:
+iptables plugin by:
   Sjoerd van der Berg <harekiet at gmail.com>
 
+libvirt plugin by:
+  Richard W. M. Jones <rjones at redhat.com>
+
 mbmon plugin by:
   Flavio Stanchina <flavio at stanchina.net>
 
 memcached plugin by:
   Antony Dovgal <tony at daylessday.org>
 
-nfs module by:
+nfs plugin by:
   Jason Pepas <cell at ices.utexas.edu>
 
-perl module by:
+perl plugin by:
   Sebastian Harl <sh at tokkee.org>
 
-processes module by:
+processes plugin by:
   Lyonel Vincent <lyonel at ezix.org>
 
 sensors plugin has been improved by:
   Luboš Staněk <kolektor at atlas.cz>
 
-serial module by:
+serial plugin by:
   David Bacher <drbacher at gmail.com>
 
-tape module by:
+tape plugin by:
   Scott Garrett <sgarrett at technomancer.com>
 
-users module by:
+users plugin by:
   Sebastian Harl <sh at tokkee.org>
 
-vserver module by:
+uuid plugin by:
+  Dan Berrange <berrange@redhat.com>
+  Richard W.M. Jones <rjones@redhat.com>
+
+vserver plugin by:
   Sebastian Harl <sh at tokkee.org>
 
 PID-file patch by:
   Tommie Gannert <d00-tga at d.kth.se>
 
-don't-fork-patch by:
+Don't-fork-patch by:
   Alvaro Barcellos <alvaro.barcellos at gmail.com>
 
-many autotools related fixes, libltdl code, getmnt-wizardry and much help has
+Many autotools related fixes, libltdl code, getmnt-wizardry and much help has
 contributed:
   Niki W. Waibel <niki.waibel at newlogic.com>
 
index d4f311a..da00f0b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,64 @@
+2008-03-29, Version 4.3.2
+       * collectd: Fix configuration of the `FailureMax', `WarningMax', and
+         `Persist' threshold options.
+       * collectd: Fix handling of missing values in the global value cache.
+       * collectd: Improved error messages when parsing the configuration.
+       * sensors plugin: Fix temperature collection with libsensors4.
+       * unixsock plugin: Fix mixed input and output operation on streams.
+       * wireless plugin: Fix reading noise value.
+
+2008-03-05, Version 4.3.1
+       * exec plugin: Set supplementary group IDs.
+       * network plugin:
+         + Use `memcpy' when constructing/parsing a package to avoid
+           alignment problems on weird architectures, such as Sparc.
+         + Translate doubles to/from the x86 byte representation to ensure
+           cross-platform compatibility.
+       * ping plugin: Correct the handling of the `TTL' setting.
+       * swap plugin: Reapply a patch for Solaris.
+       * tcpconns plugin: Portability improvements.
+
+2008-02-18, Version 4.3.0
+       * collectd: Notifications have been added to the daemon. Notifications
+         are status messages that may be associated with a data instance.
+       * collectd: Threshold checking has been added to the daemon. This
+         means that you can configure threshold values for each data
+         instance. If this threshold is exceeded a notification will be
+         created.
+       * collectd: The new `FQDNLookup' option tells the daemon to use the
+         full qualified domain name as the hostname, not just the host part
+         es returned by `gethostname(2)'.
+       * collectd: Support for more than one `TypesDB' file has been added.
+         This is useful when one such file is included in a package but one
+         wants to add custom type definitions.
+       * collectd: The `Include' config option has been expanded to handle
+         entire directories and shell wildcards.
+       * collectdmon: The new `collectdmon' binary detects when collectd
+         terminates and automatically restarts it again.
+       * csv plugin: The CSV plugin is now able to store counter values as a
+         rate, using the `StoreRates' configuration option.
+       * exec plugin: Handling of notifications has been added and the
+         ability to pass arguments to the executed programs has been added.
+       * hddtemp plugin: The new `TranslateDevicename' option lets you
+         disable the translation from device names to major-minor-numbers.
+       * logfile plugin: Handling of notifications has been added.
+       * ntpd plugin: The new `ReverseLookups' can be used to disable reverse
+         domain name lookups in this plugin.
+       * perl plugin: Many internal changes added support for handling multiple
+         threads making the plugin reasonably usable inside collectd. The API has
+         been extended to support notifications and export global variables to
+         Perl plugins; callbacks now have to be identified by name rather than a
+         pointer to a subroutine. The plugin is no longer experimental.
+       * uuid plugin: The new UUID plugin sets the hostname to an unique
+         identifier for this host. This is meant for setups where each client
+         may migrate to another physical host, possibly going through one or
+         more name changes in the process. Thanks to Richard Jones from
+         Red Hat's Emerging Technology group for this plugin.
+       * libvirt: The new libvirt plugin uses the `libvirt' library to query
+         CPU, disk and network statistics about guest systems on the same
+         physical server. Thanks to Richard Jones from Red Hat's Emerging
+         Technology group for this plugin.
+
 2008-04-22, Version 4.2.7
        * build system: Improved detection of several libraries, especially if
          they are in non-standard paths.
index 9a96eb2..b52b0e9 100644 (file)
@@ -4,9 +4,7 @@ INCLUDES = $(LTDLINCL)
 
 EXTRA_DIST = contrib version-gen.sh
 
-dist-hook:
-       find $(distdir) -type d -name '.svn' | xargs rm -rf
-
 install-exec-hook:
        $(mkinstalldirs) $(DESTDIR)$(localstatedir)/run
        $(mkinstalldirs) $(DESTDIR)$(localstatedir)/lib/$(PACKAGE_NAME)
+       $(mkinstalldirs) $(DESTDIR)$(localstatedir)/log
diff --git a/README b/README
index 2406154..8c0be1d 100644 (file)
--- a/README
+++ b/README
@@ -5,8 +5,9 @@ http://collectd.org/
 About
 -----
 
-  collectd is a small daemon which collects statistics about a computer's
-  usage and writes then into RRD files.
+  collectd is a small daemon which collects system information periodically
+  and provides mechanisms to store and monitor the values in a variety of
+  ways.
 
 
 Features
@@ -80,6 +81,9 @@ Features
     - load
       System load average over the last 1, 5 and 15 minutes.
 
+    - libvirt
+      CPU, disk and network I/O statistics from virtual machines.
+
     - mbmon
       Motherboard sensors: temperature, fanspeed and voltage information,
       using mbmon(1).
@@ -118,7 +122,7 @@ Features
       Collects statistics from `nginx' (speak: engine X), a HTTP and mail
       server/proxy.
 
-    - ntp
+    - ntpd
       NTP daemon statistics: Local clock drift, offset to peers, etc.
 
     - nut
@@ -130,9 +134,6 @@ Features
       write your own plugins in Perl and return arbitrary values using this
       API. See collectd-perl(5).
 
-      This plugin is still considered to be experimental and subject to change
-      between minor releases.
-
     - ping
       Network latency: Time to reach the default gateway or another given
       host.
@@ -209,9 +210,36 @@ Features
     - logfile
       Writes logmessages to a file or STDOUT/STDERR.
 
+    - perl
+      Log messages are propagated to plugins written in Perl as well.
+      See collectd-perl(5).
+
     - syslog
       Logs to the standard UNIX logging mechanism, syslog.
 
+  * Notifications can be handled by the following plugins:
+
+    - exec
+      Execute a program or script to handle the notification.
+      See collectd-exec(5).
+
+    - logfile
+      Writes the notification message to a file or STDOUT/STDERR.
+
+    - network
+      Send the notification to a remote host to handle it somehow.
+
+    - perl
+      Notifications are propagated to plugins written in Perl as well.
+      See collectd-perl(5).
+
+  * Miscellaneous plugins:
+
+    - uuid
+      Sets the hostname to an unique identifier. This is meant for setups
+      where each client may migrate to another physical host, possibly going
+      through one or more name changes in the process.
+
   * Performance: Since collectd is running as a daemon it doesn't spend much
     time starting up again and again. With the exception of the exec plugin no
     processes are forked. Caching in output plugins, such as the rrdtool and
@@ -279,6 +307,9 @@ Prerequisites
   * libcurl (optional)
     If you want to use the `apache' and/or `nginx' plugins.
 
+  * libhal (optional)
+    If present, the uuid plugin will check for UUID from HAL.
+
   * libiptc (optional)
     For querying iptables counters.
 
@@ -298,6 +329,10 @@ Prerequisites
   * libpcap (optional)
     Used to capture packets by the `dns' plugin.
 
+  * libperl (optional)
+    Obviously used by the `perl' plugin. The library has to be compiled with
+    ithread support (introduced in Perl 5.6.0).
+
   * librrd (optional; headers and library; rrdtool 1.0 and 1.2 both work fine)
     If built without `librrd' the resulting binary will be `client only', i.e.
     will send its values via multicast and not create any RRD files itself.
@@ -324,6 +359,12 @@ Prerequisites
     For compiling on Darwin in general and the `apple_sensors' plugin in
     particular.
 
+  * libvirt (optional)
+    Collect statistics from virtual machines.
+
+  * libxml2 (optional)
+    Parse XML data provided by libvirt.
+
 
 Configuring / Compiling / Installing
 ------------------------------------
diff --git a/TODO b/TODO
index 9cec87b..2a5b7ca 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,15 +1,20 @@
+For version 4.3:
+* unixsock plugin: Remove the custom cache if possible.
+
 src/battery.c: commend not working code.
-general: build Darwin package
 
-Near future:
+Wishlist:
 * Update the RPM specfile to
   - build `collectd-apache'
-  - be free of syntax erros.
-
-For version 3.*:
+  - be free of syntax errors.
 * Port nfs module to solaris
 * Port tape module to Linux
 * Maybe look into porting the serial module
+* Build Darwin package
+* Maybe let the network plugin configure whether or not notifications should be
+  sent/received.
+* Maybe find a way for processes connected to the unixsock plugin to receive
+  notifications, too.
 
 http://developer.apple.com/documentation/DeviceDrivers/Conceptual/AccessingHardware/AH_IOKitLib_API/chapter_5_section_1.html
 http://developer.apple.com/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/index.html#//apple_ref/doc/uid/TP0000011
index c724725..620389f 100644 (file)
@@ -1,4 +1,5 @@
-EXTRA_DIST = perl/Collectd.pm perl/Makefile.PL perl/Collectd/Makefile.PL perl/Collectd/Unixsock.pm
+EXTRA_DIST = perl/Collectd.pm perl/Makefile.PL perl/Collectd/Makefile.PL \
+               perl/Collectd/Unixsock.pm
 
 all-local: @PERL_BINDINGS@
 
@@ -11,7 +12,8 @@ clean-local:
 perl: perl/Makefile
        cd perl && $(MAKE)
 
-perl/Makefile: .perl-directory-stamp perl/Makefile.PL perl/Collectd/Makefile.PL
+perl/Makefile: .perl-directory-stamp perl/Makefile.PL \
+       perl/Collectd/Makefile.PL $(top_builddir)/config.status
        cd perl && @PERL@ Makefile.PL PREFIX=$(prefix) @PERL_BINDINGS_OPTIONS@
 
 .perl-directory-stamp:
index fd5632a..4377570 100644 (file)
@@ -22,6 +22,17 @@ package Collectd;
 use strict;
 use warnings;
 
+use Config;
+
+use threads;
+use threads::shared;
+
+BEGIN {
+       if (! $Config{'useithreads'}) {
+               die "Perl does not support ithreads!";
+       }
+}
+
 require Exporter;
 
 our @ISA = qw( Exporter );
@@ -31,6 +42,7 @@ our %EXPORT_TAGS = (
                        plugin_register
                        plugin_unregister
                        plugin_dispatch_values
+                       plugin_dispatch_notification
                        plugin_log
        ) ],
        'types' => [ qw(
@@ -39,6 +51,7 @@ our %EXPORT_TAGS = (
                        TYPE_WRITE
                        TYPE_SHUTDOWN
                        TYPE_LOG
+                       TYPE_NOTIF
                        TYPE_DATASET
        ) ],
        'ds_types' => [ qw(
@@ -57,6 +70,15 @@ our %EXPORT_TAGS = (
                        LOG_INFO
                        LOG_DEBUG
        ) ],
+       'notif' => [ qw(
+                       NOTIF_FAILURE
+                       NOTIF_WARNING
+                       NOTIF_OKAY
+       ) ],
+       'globals' => [ qw(
+                       $hostname_g
+                       $interval_g
+       ) ],
 );
 
 {
@@ -65,21 +87,25 @@ our %EXPORT_TAGS = (
                foreach keys %EXPORT_TAGS;
 }
 
+# global variables
+our $hostname_g;
+our $interval_g;
+
 Exporter::export_ok_tags ('all');
 
-my @plugins  = ();
-my @datasets = ();
+my @plugins : shared = ();
 
 my %types = (
        TYPE_INIT,     "init",
        TYPE_READ,     "read",
        TYPE_WRITE,    "write",
        TYPE_SHUTDOWN, "shutdown",
-       TYPE_LOG,      "log"
+       TYPE_LOG,      "log",
+       TYPE_NOTIF,    "notify"
 );
 
 foreach my $type (keys %types) {
-       $plugins[$type] = {};
+       $plugins[$type] = &share ({});
 }
 
 sub _log {
@@ -102,6 +128,8 @@ sub DEBUG   { _log (scalar caller, LOG_DEBUG,   shift); }
 sub plugin_call_all {
        my $type = shift;
 
+       our $cb_name = undef;
+
        if (! defined $type) {
                return;
        }
@@ -115,21 +143,47 @@ sub plugin_call_all {
                return;
        }
 
+       lock @plugins;
        foreach my $plugin (keys %{$plugins[$type]}) {
                my $p = $plugins[$type]->{$plugin};
 
+               my $status = 0;
+
                if ($p->{'wait_left'} > 0) {
-                       # TODO: use interval_g
-                       $p->{'wait_left'} -= 10;
+                       $p->{'wait_left'} -= $interval_g;
                }
 
                next if ($p->{'wait_left'} > 0);
 
-               if (my $status = $p->{'code'}->(@_)) {
+               $cb_name = $p->{'cb_name'};
+               $status = call_by_name (@_);
+
+               if (! $status) {
+                       my $err = undef;
+
+                       if ($@) {
+                               $err = $@;
+                       }
+                       else {
+                               $err = "callback returned false";
+                       }
+
+                       if (TYPE_LOG != $type) {
+                               ERROR ("Execution of callback \"$cb_name\" failed: $err");
+                       }
+
+                       $status = 0;
+               }
+
+               if ($status) {
                        $p->{'wait_left'} = 0;
-                       $p->{'wait_time'} = 10;
+                       $p->{'wait_time'} = $interval_g;
                }
                elsif (TYPE_READ == $type) {
+                       if ($p->{'wait_time'} < $interval_g) {
+                               $p->{'wait_time'} = $interval_g;
+                       }
+
                        $p->{'wait_left'} = $p->{'wait_time'};
                        $p->{'wait_time'} *= 2;
 
@@ -141,12 +195,12 @@ sub plugin_call_all {
                                . "Will suspend it for $p->{'wait_left'} seconds.");
                }
                elsif (TYPE_INIT == $type) {
+                       ERROR ("${plugin}->init() failed with status $status. "
+                               . "Plugin will be disabled.");
+
                        foreach my $type (keys %types) {
                                plugin_unregister ($type, $plugin);
                        }
-
-                       ERROR ("${plugin}->init() failed with status $status. "
-                               . "Plugin will be disabled.");
                }
                elsif (TYPE_LOG != $type) {
                        WARNING ("${plugin}->$types{$type}() failed with status $status.");
@@ -187,13 +241,23 @@ sub plugin_register {
        if ((TYPE_DATASET == $type) && ("ARRAY" eq ref $data)) {
                return plugin_register_data_set ($name, $data);
        }
-       elsif ("CODE" eq ref $data) {
-               # TODO: make interval_g available at configuration time
-               $plugins[$type]->{$name} = {
-                               wait_time => 10,
-                               wait_left => 0,
-                               code      => $data,
-               };
+       elsif ((TYPE_DATASET != $type) && (! ref $data)) {
+               my $pkg = scalar caller;
+
+               my %p : shared;
+
+               if ($data !~ m/^$pkg/) {
+                       $data = $pkg . "::" . $data;
+               }
+
+               %p = (
+                       wait_time => $interval_g,
+                       wait_left => 0,
+                       cb_name   => $data,
+               );
+
+               lock @plugins;
+               $plugins[$type]->{$name} = \%p;
        }
        else {
                ERROR ("Collectd::plugin_register: Invalid data.");
@@ -217,6 +281,7 @@ sub plugin_unregister {
                return plugin_unregister_data_set ($name);
        }
        elsif (defined $plugins[$type]) {
+               lock @plugins;
                delete $plugins[$type]->{$name};
        }
        else {
index 3b8a91c..8749c1a 100644 (file)
@@ -1,8 +1,30 @@
+#
+# collectd - Collectd::Unixsock
+# 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
+# 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>
+#
+
 package Collectd::Unixsock;
 
 =head1 NAME
 
-Collectd::Unixsock - Abstraction layer for accessing the functionality by collectd's unixsock plugin.
+Collectd::Unixsock - Abstraction layer for accessing the functionality by
+collectd's unixsock plugin.
 
 =head1 SYNOPSIS
 
@@ -29,6 +51,8 @@ programmers to interact with the daemon.
 use strict;
 use warnings;
 
+#use constant { NOTIF_FAILURE => 1, NOTIF_WARNING => 2, NOTIF_OKAY => 4 };
+
 use Carp (qw(cluck confess));
 use IO::Socket::UNIX;
 use Regexp::Common (qw(number));
@@ -49,7 +73,7 @@ sub _create_socket
 
 =head1 VALUE IDENTIFIER
 
-The values in the collectd are identified using an five-tupel (host, plugin,
+The values in the collectd are identified using an five-tuple (host, plugin,
 plugin-instance, type, type-instance) where only plugin-instance and
 type-instance may be NULL (or undefined). Many functions expect an
 I<%identifier> hash that has at least the members B<host>, B<plugin>, and
@@ -190,10 +214,10 @@ sub getval
        return ($ret);
 } # getval
 
-=item I<$obj>-E<gt>B<putval> (I<%identifier>, B<time> => I<$time>, B<values> => [...]);
+=item I<$obj>-E<gt>B<putval> (I<%identifier>, B<time> =E<gt> I<$time>, B<values> =E<gt> [...]);
 
 Submits a value-list to the daemon. If the B<time> argument is omitted
-C<time()> is used. The requierd argument B<values> is a reference to an array
+C<time()> is used. The required argument B<values> is a reference to an array
 of values that is to be submitted. The number of values must match the number
 of values expected for the given B<type> (see L<VALUE IDENTIFIER>), though this
 is checked by the daemon, not the Perl module. Also, gauge data-sources
@@ -290,6 +314,92 @@ sub listval
        return (@ret);
 } # listval
 
+=item I<$res> = I<$obj>-E<gt>B<putnotif> (B<severity> =E<gt> I<$severity>, B<message> =E<gt> I<$message>, ...);
+
+Submits a notification to the daemon.
+
+Valid options are:
+
+=over 4
+
+=item B<severity>
+
+Sets the severity of the notification. The value must be one of the following
+strings: C<failure>, C<warning>, or C<okay>. Case does not matter. This option
+is mandatory.
+
+=item B<message>
+
+Sets the message of the notification. This option is mandatory.
+
+=item B<time>
+
+Sets the time. If omitted, C<time()> is used.
+
+=item I<Value identifier>
+
+All the other fields of the value identifiers, B<host>, B<plugin>,
+B<plugin_instance>, B<type>, and B<type_instance>, are optional. When given,
+the notification is associated with the performance data of that identifier.
+For more details, please see L<collectd-unixsock(5)>.
+
+=back
+
+=cut
+
+sub putnotif
+{
+       my $obj = shift;
+       my %args = @_;
+
+       my $status;
+       my $fh = $obj->{'sock'} or confess;
+
+       my $msg; # message sent to the socket
+       my $opt_msg; # message of the notification
+       
+       if (!$args{'message'})
+       {
+               cluck ("Need argument `message'");
+               return;
+       }
+       if (!$args{'severity'})
+       {
+               cluck ("Need argument `severity'");
+               return;
+       }
+       $args{'severity'} = lc ($args{'severity'});
+       if (($args{'severity'} ne 'failure')
+               && ($args{'severity'} ne 'warning')
+               && ($args{'severity'} ne 'okay'))
+       {
+               cluck ("Invalid `severity: " . $args{'severity'});
+               return;
+       }
+
+       if (!$args{'time'})
+       {
+               $args{'time'} = time ();
+       }
+       
+       $opt_msg = $args{'message'};
+       delete ($args{'message'});
+
+       $msg = 'PUTNOTIF '
+       . join (' ', map { $_ . '=' . $args{$_} } (keys %args))
+       . " message=$opt_msg\n";
+
+       send ($fh, $msg, 0) or confess ("send: $!");
+       $msg = undef;
+       recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
+
+       ($status, $msg) = split (' ', $msg, 2);
+       return (1) if ($status == 0);
+
+       $obj->{'error'} = $msg;
+       return;
+} # putnotif
+
 =item I<$obj>-E<gt>destroy ();
 
 Closes the socket before the object is destroyed. This function is also
@@ -315,6 +425,12 @@ sub DESTROY
        $obj->destroy ();
 }
 
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<collectd-unixsock(5)>
+
 =head1 AUTHOR
 
 Florian octo Forster E<lt>octo@verplant.orgE<gt>
index 046b174..cd2110f 100644 (file)
@@ -17,6 +17,7 @@ AC_PROG_CPP
 AC_PROG_INSTALL
 AC_PROG_LN_S
 AC_PROG_MAKE_SET
+AM_PROG_CC_C_O
 AM_CONDITIONAL(COMPILER_IS_GCC, test "x$GCC" = "xyes")
 
 dnl configure libtool
@@ -310,7 +311,7 @@ AC_CHECK_HEADERS(linux/un.h, [], [],
 #endif
 ])
 
-AC_CHECK_HEADERS(pwd.h grp.h sys/un.h ctype.h limits.h sys/quota.h xfs/xqm.h fs_info.h fshelp.h paths.h mntent.h mnttab.h sys/fstyp.h sys/fs_types.h sys/mntent.h sys/mnttab.h sys/statfs.h sys/statvfs.h sys/vfs.h sys/vfstab.h kvm.h)
+AC_CHECK_HEADERS(pwd.h grp.h sys/un.h ctype.h limits.h sys/quota.h xfs/xqm.h fs_info.h fshelp.h paths.h mntent.h mnttab.h sys/fstyp.h sys/fs_types.h sys/mntent.h sys/mnttab.h sys/statfs.h sys/statvfs.h sys/vfs.h sys/vfstab.h kvm.h wordexp.h)
 
 # For the dns plugin
 AC_CHECK_HEADERS(arpa/nameser.h)
@@ -880,6 +881,20 @@ AC_CHECK_LIB(resolv, res_search,
 [with_libresolv="no"])
 AM_CONDITIONAL(BUILD_WITH_LIBRESOLV, test "x$with_libresolv" = "xyes")
 
+dnl Check for HAL (hardware abstraction library)
+with_libhal="yes"
+AC_CHECK_LIB(hal,libhal_device_property_exists,
+            [AC_DEFINE(HAVE_LIBHAL, 1, [Define to 1 if you have 'hal' library])],
+            [with_libhal="no"])
+if test "x$with_libhal" = "xyes"; then
+       PKG_PROG_PKG_CONFIG
+       if test "x$PKG_CONFIG" != "x"; then
+               BUILD_WITH_LIBHAL_CFLAGS="`pkg-config --cflags hal`"
+               BUILD_WITH_LIBHAL_LIBS="`pkg-config --libs hal`"
+               AC_SUBST(BUILD_WITH_LIBHAL_CFLAGS)
+               AC_SUBST(BUILD_WITH_LIBHAL_LIBS)
+       fi
+fi
 
 m4_divert_once([HELP_WITH], [
 collectd additional packages:])
@@ -894,6 +909,8 @@ AC_ARG_WITH(rrdtool, [AS_HELP_STRING([--with-rrdtool@<:@=PREFIX@:>@], [Path to r
                librrd_cflags="-I$withval/include"
                librrd_ldflags="-L$withval/lib"
                with_rrdtool="yes"
+       else
+               with_rrdtool="$withval"
        fi
 ], [with_rrdtool="yes"])
 if test "x$with_rrdtool" = "xyes"
@@ -1079,7 +1096,7 @@ AC_CHECK_LIB(IOKit, IOServiceGetMatchingServices,
 [
        with_libiokit="yes"
        collectd_libiokit=1
-], 
+],
 [
        with_libiokit="no"
        collectd_libiokit=0
@@ -1094,6 +1111,8 @@ AC_ARG_WITH(libstatgrab, [AS_HELP_STRING([--with-libstatgrab@<:@=PREFIX@:>@], [P
                LDFLAGS="$LDFLAGS -L$withval/lib"
                CPPFLAGS="$CPPFLAGS -I$withval/include"
                with_libstatgrab="yes"
+       else
+               with_libstatgrab="$withval"
        fi
 ],
 [
@@ -1332,10 +1351,12 @@ AC_ARG_WITH(liboping, [AS_HELP_STRING([--with-liboping@<:@=PREFIX@:>@], [Path to
        then
                with_liboping="no"
                with_own_liboping="no"
-       fi
+       else if test "x$withval" = "xyes"
+       then
+               with_liboping="yes"
+       fi; fi
 ],
 [
-       #753
        with_liboping="yes"
 ])
 
@@ -1367,6 +1388,8 @@ AC_ARG_WITH(libpcap, [AS_HELP_STRING([--with-libpcap@<:@=PREFIX@:>@], [Path to l
                LDFLAGS="$LDFLAGS -L$withval/lib"
                CPPFLAGS="$CPPFLAGS -I$withval/include"
                with_libpcap="yes"
+       else
+               with_libpcap="$withval"
        fi
 ],
 [
@@ -1405,6 +1428,8 @@ AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to l
                CPPFLAGS="$CPPFLAGS -I$withval/include"
                perl_interpreter="$withval/bin/perl"
                with_libperl="yes"
+       else
+               with_libperl="$withval"
        fi
 ],
 [
@@ -1469,6 +1494,41 @@ else if test -z "$perl_interpreter"; then
 fi; fi
 AM_CONDITIONAL(BUILD_WITH_LIBPERL, test "x$with_libperl" = "xyes")
 
+if test "x$with_libperl" = "xyes"
+then
+       SAVE_CFLAGS=$CFLAGS
+       SAVE_LDFLAGS=$LDFLAGS
+       CFLAGS="$CFLAGS $PERL_CFLAGS"
+       LDFLAGS="$LDFLAGS $PERL_LDFLAGS"
+
+       AC_CACHE_CHECK([if perl supports ithreads],
+               [have_perl_ithreads],
+               AC_LINK_IFELSE(
+                       AC_LANG_PROGRAM(
+                       [[
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+
+#if !defined(USE_ITHREADS)
+# error "Perl does not support ithreads!"
+#endif /* !defined(USE_ITHREADS) */
+                       ]],
+                       [[ ]]),
+                       [have_perl_ithreads="yes"],
+                       [have_perl_ithreads="no"]
+               )
+       )
+
+       if test "x$have_perl_ithreads" = "xyes"
+       then
+               AC_DEFINE(HAVE_PERL_ITHREADS, 1, [Define if Perl supports ithreads.])
+       fi
+
+       CFLAGS=$SAVE_CFLAGS
+       LDFLAGS=$SAVE_LDFLAGS
+fi
+
 AC_ARG_WITH(libiptc, [AS_HELP_STRING([--with-libiptc@<:@=PREFIX@:>@], [Path to libiptc.])],
 [
        if test "x$withval" != "xno" && test "x$withval" != "xyes"
@@ -1476,10 +1536,12 @@ AC_ARG_WITH(libiptc, [AS_HELP_STRING([--with-libiptc@<:@=PREFIX@:>@], [Path to l
                LDFLAGS="$LDFLAGS -L$withval/lib"
                CPPFLAGS="$CPPFLAGS -I$withval/include"
                with_libiptc="yes"
+       else
+               with_libiptc="$withval"
        fi
 ],
 [
-       if test "x$ac_system" = "xLinux"
+       if test "x$ac_system" = "xLinux"
        then
                with_libiptc="yes"
        else
@@ -1513,14 +1575,14 @@ with_snmp_cflags=""
 with_snmp_libs=""
 AC_ARG_WITH(libnetsnmp, [AS_HELP_STRING([--with-libnetsnmp@<:@=PREFIX@:>@], [Path to the Net-SNMPD library.])],
 [
-       if test "x$withval" = "xno"
+       if test "x$withval" = "xno"
        then
                with_libnetsnmp="no"
        else if test "x$withval" = "xyes"
        then
                with_libnetsnmp="yes"
        else
-               if test -x "$withval"
+               if test -x "$withval"
                then
                        with_snmp_config="$withval"
                        with_libnetsnmp="yes"
@@ -1643,7 +1705,7 @@ then
        SAVE_CPPFLAGS="$CPPFLAGS"
        CPPFLAGS="$CPPFLAGS $with_upsclient_cflags"
 
-       AC_CHECK_TYPES([UPSCONN_t, UPSCONN], [], [], 
+       AC_CHECK_TYPES([UPSCONN_t, UPSCONN], [], [],
 [#include <stdlib.h>
 #include <stdio.h>
 #include <upsclient.h>])
@@ -1825,6 +1887,125 @@ then
 fi
 AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
 
+dnl Check for libvirt and libxml2 libraries.
+with_libxml2="no (pkg-config isn't available)"
+with_libxml2_cflags=""
+with_libxml2_ldflags=""
+with_libvirt="no (pkg-config isn't available)"
+with_libvirt_cflags=""
+with_libvirt_ldflags=""
+PKG_PROG_PKG_CONFIG
+if test "x$PKG_CONFIG" != "x"
+then
+       pkg-config --exists 'libxml-2.0' 2>/dev/null
+       if test "$?" = "0"
+       then
+               with_libxml2="yes"
+       else
+               with_libxml2="no (pkg-config doesn't know library)"
+       fi
+
+       pkg-config --exists libvirt 2>/dev/null
+       if test "$?" = "0"
+       then
+               with_libvirt="yes"
+       else
+               with_libvirt="no (pkg-config doesn't know library)"
+       fi
+fi
+if test "x$with_libxml2" = "xyes"
+then
+       with_libxml2_cflags="`pkg-config --cflags libxml-2.0`"
+       if test $? -ne 0
+       then
+               with_libxml2="no"
+       fi
+       with_libxml2_ldflags="`pkg-config --libs libxml-2.0`"
+       if test $? -ne 0
+       then
+               with_libxml2="no"
+       fi
+fi
+if test "x$with_libxml2" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libxml2_cflags"
+
+       AC_CHECK_HEADERS(libxml/parser.h, [],
+                     [with_libxml2="no (libxml/parser.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libxml2" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       SAVE_LD_FLAGS="$LDFLAGS"
+
+       CFLAGS="$CFLAGS $with_libxml2_cflags"
+       LDFLAGS="$LDFLAGS $with_libxml2_ldflags"
+
+       AC_CHECK_LIB(xml2, xmlXPathEval,
+                    [with_libxml2="yes"],
+                    [with_libxml2="no (symbol xmlXPathEval not found)"])
+
+       CFLAGS="$SAVE_CFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+dnl Add the right compiler flags and libraries.
+if test "x$with_libxml2" = "xyes"; then
+       BUILD_WITH_LIBXML2_CFLAGS="$with_libxml2_cflags"
+       BUILD_WITH_LIBXML2_LIBS="$with_libxml2_ldflags"
+       AC_SUBST(BUILD_WITH_LIBXML2_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBXML2_LIBS)
+fi
+if test "x$with_libvirt" = "xyes"
+then
+       with_libvirt_cflags="`pkg-config --cflags libvirt`"
+       if test $? -ne 0
+       then
+               with_libvirt="no"
+       fi
+       with_libvirt_ldflags="`pkg-config --libs libvirt`"
+       if test $? -ne 0
+       then
+               with_libvirt="no"
+       fi
+fi
+if test "x$with_libvirt" = "xyes"
+then
+       SAVE_CPPFLAGS="$CPPFLAGS"
+       CPPFLAGS="$CPPFLAGS $with_libvirt_cflags"
+
+       AC_CHECK_HEADERS(libvirt/libvirt.h, [],
+                     [with_libvirt="no (libvirt/libvirt.h not found)"])
+
+       CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+if test "x$with_libvirt" = "xyes"
+then
+       SAVE_CFLAGS="$CFLAGS"
+       SAVE_LD_FLAGS="$LDFLAGS"
+
+       CFLAGS="$CFLAGS $with_libvirt_cflags"
+       LDFLAGS="$LDFLAGS $with_libvirt_ldflags"
+
+       AC_CHECK_LIB(virt, virDomainBlockStats,
+                    [with_libvirt="yes"],
+                    [with_libvirt="no (symbol virDomainBlockStats not found)"])
+
+       CFLAGS="$SAVE_CFLAGS"
+       LDFLAGS="$SAVE_LDFLAGS"
+fi
+dnl Add the right compiler flags and libraries.
+if test "x$with_libvirt" = "xyes"; then
+       BUILD_WITH_LIBVIRT_CFLAGS="$with_libvirt_cflags"
+       BUILD_WITH_LIBVIRT_LIBS="$with_libvirt_ldflags"
+       AC_SUBST(BUILD_WITH_LIBVIRT_CFLAGS)
+       AC_SUBST(BUILD_WITH_LIBVIRT_LIBS)
+fi
+
+dnl End of check for libvirt and libxml2 libraries.
+
 # Check for enabled/disabled features
 #
 
@@ -1937,10 +2118,12 @@ plugin_entropy="no"
 plugin_interface="no"
 plugin_ipvs="no"
 plugin_irq="no"
+plugin_libvirt="no"
 plugin_load="no"
 plugin_memory="no"
 plugin_multimeter="no"
 plugin_nfs="no"
+plugin_perl="no"
 plugin_processes="no"
 plugin_serial="no"
 plugin_swap="no"
@@ -2028,11 +2211,21 @@ then
        plugin_interface="yes"
 fi
 
+if test "x$with_libxml2" = "xyes" && test "x$with_libvirt" = "xyes"
+then
+       plugin_libvirt="yes"
+fi
+
 if test "x$have_getloadavg" = "xyes"
 then
        plugin_load="yes"
 fi
 
+if test "x$have_libperl$have_perl_ithreads" = "xyesyes"
+then
+       plugin_perl="yes"
+fi
+
 # Mac OS X memory interface
 if test "x$have_host_statistics" = "xyes"
 then
@@ -2086,6 +2279,7 @@ AC_PLUGIN([interface],   [$plugin_interface],  [Interface traffic statistics])
 AC_PLUGIN([iptables],    [$with_libiptc],      [IPTables rule counters])
 AC_PLUGIN([ipvs],        [$plugin_ipvs],       [IPVS connection statistics])
 AC_PLUGIN([irq],         [$plugin_irq],        [IRQ statistics])
+AC_PLUGIN([libvirt],     [$plugin_libvirt],    [Virtual machine statistics])
 AC_PLUGIN([load],        [$plugin_load],       [System load])
 AC_PLUGIN([logfile],     [yes],                [File logging plugin])
 AC_PLUGIN([mbmon],       [yes],                [Query mbmond])
@@ -2099,7 +2293,7 @@ AC_PLUGIN([nfs],         [$plugin_nfs],        [NFS statistics])
 AC_PLUGIN([nginx],       [$with_libcurl],      [nginx statistics])
 AC_PLUGIN([ntpd],        [yes],                [NTPd statistics])
 AC_PLUGIN([nut],         [$with_libupsclient], [Network UPS tools statistics])
-AC_PLUGIN([perl],        [$with_libperl],      [Embed a Perl interpreter])
+AC_PLUGIN([perl],        [$plugin_perl],       [Embed a Perl interpreter])
 AC_PLUGIN([ping],        [$with_liboping],     [Network latency statistics])
 AC_PLUGIN([processes],   [$plugin_processes],  [Process statistics])
 AC_PLUGIN([rrdtool],     [$with_rrdtool],      [RRDTool output plugin])
@@ -2112,6 +2306,7 @@ AC_PLUGIN([tape],        [$plugin_tape],       [Tape drive statistics])
 AC_PLUGIN([tcpconns],    [$plugin_tcpconns],   [TCP connection statistics])
 AC_PLUGIN([unixsock],    [yes],                [Unixsock communication plugin])
 AC_PLUGIN([users],       [$plugin_users],      [User statistics])
+AC_PLUGIN([uuid],        [yes],                [UUID as hostname plugin])
 AC_PLUGIN([vserver],     [$plugin_vserver],    [Linux VServer statistics])
 AC_PLUGIN([wireless],    [$plugin_wireless],   [Wireless statistics])
 AC_PLUGIN([xmms],        [$with_libxmms],      [XMMS statistics])
@@ -2154,6 +2349,11 @@ AC_SUBST(PERL_BINDINGS_OPTIONS)
 
 AC_OUTPUT(Makefile src/Makefile src/collectd.conf src/liboconfig/Makefile src/liboping/Makefile bindings/Makefile)
 
+if test "x$with_rrdtool" = "xyes" -a "x$librrd_threadsafe" != "xyes"
+then
+       with_rrdtool="yes (warning: librrd is not thread-safe)"
+fi
+
 if test "x$with_liboping" = "xyes" -a "x$with_own_liboping" = "xyes"
 then
        with_liboping="yes (shipped version)"
@@ -2192,6 +2392,8 @@ Configuration:
     libsensors  . . . . $with_lm_sensors
     libstatgrab . . . . $with_libstatgrab
     libupsclient  . . . $with_libupsclient
+    libvirt . . . . . . $with_libvirt
+    libxml2 . . . . . . $with_libxml2
     libxmms . . . . . . $with_libxmms
 
   Features:
@@ -2220,6 +2422,7 @@ Configuration:
     iptables  . . . . . $enable_iptables
     ipvs  . . . . . . . $enable_ipvs
     irq . . . . . . . . $enable_irq
+    libvirt . . . . . . $enable_libvirt
     load  . . . . . . . $enable_load
     logfile . . . . . . $enable_logfile
     mbmon . . . . . . . $enable_mbmon
@@ -2246,6 +2449,7 @@ Configuration:
     tcpconns  . . . . . $enable_tcpconns
     unixsock  . . . . . $enable_unixsock
     users . . . . . . . $enable_users
+    uuid  . . . . . . . $enable_uuid
     vserver . . . . . . $enable_vserver
     wireless  . . . . . $enable_wireless
     xmms  . . . . . . . $enable_xmms
index 398d13b..1c34812 100644 (file)
@@ -2,11 +2,6 @@ The files in this directory may be used to perform common tasks that aren't
 exactly `collectd's job. They may or may not require in-depth knowledge of RRD
 files and/or `collectd's inner workings. Use at your own risk.
 
-PerlLib/
---------
-  Perl modules to be used in conjunction with collectd. See the perldoc
-documentation of the .pm-files to find out what they're good for.
-
 add_rra.sh
 ----------
   Before version 3.9.0 collectd used to create a different set of RRAs. The
@@ -73,6 +68,10 @@ prints a bash-script to STDOUT which should do most of the work for you. You
 may still need to do some things by hand, read `README.migration' for more
 details.
 
+redhat/
+-------
+  Spec-file and affiliated files to build an RedHat RPM package of collectd.
+
 snmp-data.conf
 --------------
   Sample configuration for the SNMP plugin. This config includes a few standard
index 1b98d5b..1a0247f 100644 (file)
@@ -17,25 +17,29 @@ package Collectd::Plugin::MyPlugin;
 use strict;
 use warnings;
 
+use Collectd qw( :all );
+
 # data set definition:
 # see section "DATA TYPES" in collectd-perl(5) for details
+# (take a look at the types.db file for a large list of predefined data-sets)
 my $dataset =
 [
        {
                name => 'my_ds',
-               type => Collectd::DS_TYPE_GAUGE,
+               type => DS_TYPE_GAUGE,
                min  => 0,
                max  => 65535,
        },
 ];
 
 # This code is executed after loading the plugin to register it with collectd.
-Collectd::plugin_register (Collectd::TYPE_LOG, 'myplugin', \&my_log);
-Collectd::plugin_register (Collectd::TYPE_DATASET, 'myplugin', $dataset);
-Collectd::plugin_register (Collectd::TYPE_INIT, 'myplugin', \&my_init);
-Collectd::plugin_register (Collectd::TYPE_READ, 'myplugin', \&my_read);
-Collectd::plugin_register (Collectd::TYPE_WRITE, 'myplugin', \&my_write);
-Collectd::plugin_register (Collectd::TYPE_SHUTDOWN, 'myplugin', \&my_shutdown);
+plugin_register (TYPE_LOG, 'myplugin', 'my_log');
+plugin_register (TYPE_NOTIF, 'myplugin', 'my_notify');
+plugin_register (TYPE_DATASET, 'myplugin', $dataset);
+plugin_register (TYPE_INIT, 'myplugin', 'my_init');
+plugin_register (TYPE_READ, 'myplugin', 'my_read');
+plugin_register (TYPE_WRITE, 'myplugin', 'my_write');
+plugin_register (TYPE_SHUTDOWN, 'myplugin', 'my_shutdown');
 
 # For each of the functions below see collectd-perl(5) for details about
 # arguments and the like.
@@ -67,7 +71,7 @@ sub my_read
        # dispatch the values to collectd which passes them on to all registered
        # write functions - the first argument is used to lookup the data set
        # definition
-       Collectd::plugin_dispatch_values ('myplugin', $vl);
+       plugin_dispatch_values ('myplugin', $vl);
 
        # A false return value indicates an error and the plugin will be skipped
        # for an increasing amount of time.
@@ -82,8 +86,7 @@ sub my_write
        my $vl   = shift;
 
        if (scalar (@$ds) != scalar (@{$vl->{'values'}})) {
-               Collectd::plugin_log (Collectd::LOG_WARNING,
-                       "DS number does not match values length");
+               plugin_log (LOG_WARNING, "DS number does not match values length");
                return;
        }
 
@@ -123,3 +126,38 @@ sub my_log
        return 1;
 } # my_log ()
 
+# This function is called when plugin_dispatch_notification () has been used
+sub my_notify
+{
+       my $notif = shift;
+
+       my ($sec, $min, $hour, $mday, $mon, $year) = localtime ($notif->{'time'});
+
+       printf "NOTIF (%04d-%02d-%02d %02d:%02d:%02d): %d - ",
+                       $year + 1900, $mon + 1, $mday, $hour, $min, $sec,
+                       $notif->{'severity'};
+
+       if (defined $notif->{'host'}) {
+               print "$notif->{'host'}: ";
+       }
+
+       if (defined $notif->{'plugin'}) {
+               print "$notif->{'plugin'}: ";
+       }
+
+       if (defined $notif->{'plugin_instance'}) {
+               print "$notif->{'plugin_instance'}: ";
+       }
+
+       if (defined $notif->{'type'}) {
+               print "$notif->{'type'}: ";
+       }
+
+       if (defined $notif->{'type_instance'}) {
+               print "$notif->{'type_instance'}: ";
+       }
+
+       print "$notif->{'message'}\n";
+       return 1;
+} # my_notify ()
+
index fc7856b..cdd537a 100644 (file)
@@ -18,7 +18,8 @@
  *   is optional
  */
 
-#include <stdio.h>
+#if ! HAVE_CONFIG_H
+
 #include <stdlib.h>
 
 #include <string.h>
 # undef __USE_ISOC99
 #endif /* DISABLE_ISOC99 */
 
+#include <time.h>
+
+#endif /* ! HAVE_CONFIG */
+
 #include <collectd/collectd.h>
 #include <collectd/common.h>
 #include <collectd/plugin.h>
@@ -146,6 +151,44 @@ static void my_log (int severity, const char *msg)
 } /* static void my_log (int, const char *) */
 
 /*
+ * This function is called when plugin_dispatch_notification () has been used.
+ */
+static int my_notify (const notification_t *notif)
+{
+       char time_str[32] = "";
+       struct tm *tm = NULL;
+
+       int n = 0;
+
+       if (NULL == (tm = localtime (&notif->time)))
+               time_str[0] = '\0';
+
+       n = strftime (time_str, 32, "%F %T", tm);
+       if (n >= 32) n = 31;
+       time_str[n] = '\0';
+
+       printf ("NOTIF (%s): %i - ", time_str, notif->severity);
+
+       if ('\0' != *notif->host)
+               printf ("%s: ", notif->host);
+
+       if ('\0' != *notif->plugin)
+               printf ("%s: ", notif->plugin);
+
+       if ('\0' != *notif->plugin_instance)
+               printf ("%s: ", notif->plugin_instance);
+
+       if ('\0' != *notif->type)
+               printf ("%s: ", notif->type);
+
+       if ('\0' != *notif->type_instance)
+               printf ("%s: ", notif->type_instance);
+
+       printf ("%s\n", notif->message);
+       return 0;
+} /* static int my_notify (notification_t *) */
+
+/*
  * This function is called before shutting down collectd.
  */
 static int my_shutdown (void)
@@ -161,6 +204,7 @@ static int my_shutdown (void)
 void module_register (void)
 {
        plugin_register_log ("myplugin", my_log);
+       plugin_register_notification ("myplugin", my_notify);
        plugin_register_data_set (&ds);
        plugin_register_read ("myplugin", my_read);
        plugin_register_init ("myplugin", my_init);
index e6c8128..2460956 100755 (executable)
@@ -143,7 +143,7 @@ sub handle_config
     }
     elsif (ref ($config->{'script'}) eq '')
     {
-      handle_config_addtype ([$config->{'script'}]);
+      handle_config_script ([$config->{'script'}]);
     }
     else
     {
diff --git a/contrib/exec-nagios.conf b/contrib/exec-nagios.conf
new file mode 100644 (file)
index 0000000..26dd621
--- /dev/null
@@ -0,0 +1,13 @@
+# Run `perldoc exec-nagios.px' for details on this config file.
+
+Interval 300
+
+<Script /usr/lib/nagios/check_tcp>
+       Arguments -H alice -p 22
+       Type delay
+</Script>
+
+<Script /usr/lib/nagios/check_dns>
+       Arguments -H alice
+       Type delay
+</Script>
diff --git a/contrib/exec-nagios.px b/contrib/exec-nagios.px
new file mode 100755 (executable)
index 0000000..3a84724
--- /dev/null
@@ -0,0 +1,395 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+exec-nagios.px
+
+=head1 DESCRIPTION
+
+This script allows you to use plugins that were written for Nagios with
+collectd's C<exec-plugin>. If the plugin checks some kind of threshold, please
+consider configuring the threshold using collectd's own facilities instead of
+using this transition layer.
+
+=cut
+
+use Sys::Hostname ('hostname');
+use File::Basename ('basename');
+use Config::General ('ParseConfig');
+use Regexp::Common ('number');
+
+our $ConfigFile = '/etc/exec-nagios.conf';
+our $TypeMap = {};
+our $Scripts = [];
+our $Interval = 300;
+
+main ();
+exit (0);
+
+# Configuration
+# {{{
+
+=head1 CONFIGURATION
+
+This script reads it's configuration from F</etc/exec-nagios.conf>. The
+configuration is read using C<Config::General> which understands a Apache-like
+config syntax, so it's very similar to the F<collectd.conf> syntax, too.
+
+Here's a short sample config:
+
+  Interval 300
+  <Script /usr/lib/nagios/check_tcp>
+    Arguments -H alice -p 22
+    Type delay
+  </Script>
+  <Script /usr/lib/nagios/check_dns>
+    Arguments -H alice
+    Type delay
+  </Script>
+
+The options have the following semantic (i.E<nbsp>e. meaning):
+
+=over 4
+
+=item B<Interval> I<Seconds>
+
+Sets the interval in which the plugins are executed. This doesn't need to match
+the interval setting of the collectd daemon. Usually, you want to execute the
+Nagios plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
+seconds.
+
+=item E<lt>B<Script> I<File>E<gt>
+
+Adds a script to the list of scripts to be executed once per I<Interval>
+seconds. You can use the following optional arguments to specify the operation
+further:
+
+=over 4
+
+=item B<Arguments> I<Arguments>
+
+Pass the arguments I<Arguments> to the script. This is often needed with Nagios
+plugins, because much of the logic is implemented in the plugins, not in the
+daemon. If you need to specify a warning and/or critical range here, please
+consider using collectd's own threshold mechanism, which is by far the more
+elegant solution than this transition layer.
+
+=item B<Type> I<Type>
+
+If the plugin provides "performance data" the performance data is dispatched to
+collectd with this type. If no type is configured the data is ignored. Please
+note that this is limited to types that take exactly one value, such as the
+type C<delay> in the example above. If you need more complex performance data,
+rewrite the plugin as a collectd plugin (or at least port it do run directly
+with the C<exec-plugin>).
+
+=back
+
+=cut
+
+sub handle_config_addtype
+{
+  my $list = shift;
+
+  for (my $i = 0; $i < @$list; $i++)
+  {
+    my ($to, @from) = split (' ', $list->[$i]);
+    for (my $j = 0; $j < @from; $j++)
+    {
+      $TypeMap->{$from[$j]} = $to;
+    }
+  }
+} # handle_config_addtype
+
+sub handle_config_script
+{
+  my $scripts = shift;
+
+  for (keys %$scripts)
+  {
+    my $script = $_;
+    my $opts = $scripts->{$script};
+
+    if (!-e $script)
+    {
+      print STDERR "Script `$script' doesn't exist.\n";
+    }
+    elsif (!-x $script)
+    {
+      print STDERR "Script `$script' exists but is not executable.\n";
+    }
+    else
+    {
+      $opts->{'script'} = $script;
+      push (@$Scripts, $opts);
+    }
+  } # for (keys %$scripts)
+} # handle_config_script
+
+sub handle_config
+{
+  my $config = shift;
+
+  if (defined ($config->{'addtype'}))
+  {
+    if (ref ($config->{'addtype'}) eq 'ARRAY')
+    {
+      handle_config_addtype ($config->{'addtype'});
+    }
+    elsif (ref ($config->{'addtype'}) eq '')
+    {
+      handle_config_addtype ([$config->{'addtype'}]);
+    }
+    else
+    {
+      print STDERR "Cannot handle ref type '"
+      . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
+    }
+  }
+
+  if (defined ($config->{'script'}))
+  {
+    if (ref ($config->{'script'}) eq 'HASH')
+    {
+      handle_config_script ($config->{'script'});
+    }
+    else
+    {
+      print STDERR "Cannot handle ref type '"
+      . ref ($config->{'script'}) . "' for option 'Script'.\n";
+    }
+  }
+
+  if (defined ($config->{'interval'})
+    && (ref ($config->{'interval'}) eq ''))
+  {
+    my $num = int ($config->{'interval'});
+    if ($num > 0)
+    {
+      $Interval = $num;
+    }
+  }
+} # handle_config }}}
+
+sub scale_value
+{
+  my $value = shift;
+  my $unit = shift;
+
+  if (!$unit)
+  {
+    return ($value);
+  }
+
+  if (($unit =~ m/^mb(yte)?$/i) || ($unit eq 'M'))
+  {
+    return ($value * 1000000);
+  }
+  elsif ($unit =~ m/^k(b(yte)?)?$/i)
+  {
+    return ($value * 1000);
+  }
+
+  return ($value);
+}
+
+sub sanitize_instance
+{
+  my $inst = shift;
+
+  if ($inst eq '/')
+  {
+    return ('root');
+  }
+
+  $inst =~ s/[^A-Za-z_-]/_/g;
+  $inst =~ s/__+/_/g;
+  $inst =~ s/^_//;
+  $inst =~ s/_$//;
+
+  return ($inst);
+}
+
+sub handle_performance_data
+{
+  my $host = shift;
+  my $plugin = shift;
+  my $pinst = shift;
+  my $type = shift;
+  my $time = shift;
+  my $line = shift;
+
+  my $tinst;
+  my $value;
+  my $unit;
+
+  if ($line =~ m/^([^=]+)=($RE{num}{real})([^;]*)/)
+  {
+    $tinst = sanitize_instance ($1);
+    $value = scale_value ($2, $3);
+  }
+  else
+  {
+    return;
+  }
+
+  print "PUTVAL $host/$plugin-$pinst/$type-$tinst interval=$Interval ${time}:$value\n";
+}
+
+sub execute_script
+{
+  my $fh;
+  my $pinst;
+  my $time = time ();
+  my $script = shift;
+  my @args = ();
+  my $host = hostname () || 'localhost';
+
+  my $state = 0;
+  my $serviceoutput;
+  my @serviceperfdata;
+  my @longserviceoutput;
+
+  my $script_name = $script->{'script'};
+  
+  if ($script->{'arguments'})
+  {
+    @args = split (' ', $script->{'arguments'});
+  }
+
+  if (!open ($fh, '-|', $script_name, @args))
+  {
+    print STDERR "Cannot execute $script_name: $!";
+    return;
+  }
+
+  $pinst = sanitize_instance (basename ($script_name));
+
+  # Parse the output of the plugin. The format is seriously fucked up, because
+  # it got extended way beyond what it could handle.
+  while (my $line = <$fh>)
+  {
+    chomp ($line);
+
+    if ($state == 0)
+    {
+      my $perfdata;
+      ($serviceoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
+      
+      if ($perfdata)
+      {
+       push (@serviceperfdata, split (' ', $perfdata));
+      }
+
+      $state = 1;
+    }
+    elsif ($state == 1)
+    {
+      my $longoutput;
+      my $perfdata;
+      ($longoutput, $perfdata) = split (m/\s*\|\s*/, $line, 2);
+
+      push (@longserviceoutput, $longoutput);
+
+      if ($perfdata)
+      {
+       push (@serviceperfdata, split (' ', $perfdata));
+       $state = 2;
+      }
+    }
+    else # ($state == 2)
+    {
+      push (@serviceperfdata, split (' ', $line));
+    }
+  }
+
+  close ($fh);
+  # Save the exit status of the check in $state
+  $state = $?;
+
+  if ($state == 0)
+  {
+    $state = 'okay';
+  }
+  elsif ($state == 1)
+  {
+    $state = 'warning';
+  }
+  else
+  {
+    $state = 'failure';
+  }
+
+  {
+    my $type = $script->{'type'} || 'nagios_check';
+
+    print "PUTNOTIF time=$time severity=$state host=$host plugin=nagios "
+    . "plugin_instance=$pinst type=$type message=$serviceoutput\n";
+  }
+
+  if ($script->{'type'})
+  {
+    for (@serviceperfdata)
+    {
+      handle_performance_data ($host, 'nagios', $pinst, $script->{'type'},
+       $time, $_);
+    }
+  }
+} # execute_script
+
+sub main
+{
+  my $last_run;
+  my $next_run;
+
+  my %config = ParseConfig (-ConfigFile => $ConfigFile,
+    -AutoTrue => 1,
+    -LowerCaseNames => 1);
+  handle_config (\%config);
+
+  while (42)
+  {
+    $last_run = time ();
+    $next_run = $last_run + $Interval;
+
+    for (@$Scripts)
+    {
+      execute_script ($_);
+    }
+
+    while ((my $timeleft = ($next_run - time ())) > 0)
+    {
+      sleep ($timeleft);
+    }
+  }
+} # main
+
+=head1 REQUIREMENTS
+
+This script requires the following Perl modules to be installed:
+
+=over 4
+
+=item C<Config::General>
+
+=item C<Regexp::Common>
+
+=back
+
+=head1 SEE ALSO
+
+L<http://www.nagios.org/>,
+L<http://nagiosplugins.org/>,
+L<http://collectd.org/>,
+L<collectd-exec(5)>
+
+=head1 AUTHOR
+
+Florian octo Forster E<lt>octo at verplant.orgE<gt>
+
+=cut
+
+# vim: set sw=2 sts=2 ts=8 fdm=marker :
diff --git a/contrib/extractDS.px b/contrib/extractDS.px
deleted file mode 100755 (executable)
index 80d873b..0000000
+++ /dev/null
@@ -1,621 +0,0 @@
-#!/usr/bin/perl
-
-# collectd - contrib/rrd_filter.px
-# 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
-# Free Software Foundation; only version 2 of the License is applicable.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
-#
-# Authors:
-#   Florian octo Forster <octo at verplant.org>
-
-use strict;
-use warnings;
-
-=head1 NAME
-
-rrd_filter.px - Perform same advanced non-standard operations on an RRD file.
-
-=head1 SYNOPSYS
-
-  rrd_filter.px -i input.rrd -o output.rrd [options]
-
-=head1 DEPENDENCIES
-
-extractDS.px requires the RRDTool binary, Perl and the included L<Getopt::Long>
-module.
-
-=cut
-
-use Getopt::Long ('GetOptions');
-use Data::Dumper ();
-
-our $InFile;
-our $InDS = [];
-our $OutFile;
-our $OutDS = [];
-
-our $NewRRAs = [];
-
-our $Step = 0;
-
-=head1 OPTIONS
-
-The following options can be passed on the command line:
-
-=over 4
-
-=item B<--infile> I<file>
-
-=item B<-i> I<file>
-
-Reads from I<file>. If I<file> ends in C<.rrd>, then C<rrdtool dump> is invoked
-to create an XML dump of the RRD file. Otherwise the XML dump is expected
-directly. The special filename C<-> can be used to read from STDIN.
-
-=item B<--outfile> I<file>
-
-=item B<-o> I<file>
-
-Writes output to I<file>. If I<file> ends in C<.rrd>, then C<rrdtool restore>
-is invoked to create a binary RRD file. Otherwise an XML output is written. The
-special filename C<-> can be used to write to STDOUT.
-
-=item B<--map> I<in_ds>:I<out_ds>
-
-=item B<-m> I<in_ds>:I<out_ds>
-
-Writes the datasource I<in_ds> to the output and renames it to I<out_ds>. This
-is useful to extract one DS from an RRD file.
-
-=item B<--step> I<seconds>
-
-=item B<-s> I<seconds>
-
-Changes the step of the output RRD file to be I<seconds>. The new stepsize must
-be a multiple of the old stepsize of the other way around. When increasing the
-stepsize the number of PDPs in each RRA must be dividable by the factor by
-which the stepsize is increased. The length of CDPs and the absolute length of
-RRAs (and thus the data itself) is not altered.
-
-Examples:
-
-  step =  10, rra_steps = 12   =>   step = 60, rra_steps =  2
-  step = 300, rra_steps =  1   =>   step = 10, rra_steps = 30
-
-=item B<--rra> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
-
-=item B<-a> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
-
-Inserts a new RRA in the generated RRD file. This is done B<after> the step has
-been adjusted, take that into account when specifying I<steps> and I<rows>. For
-an explanation of the format please see L<rrdcreate(1)>.
-
-=back
-
-=cut
-
-GetOptions ("infile|i=s" => \$InFile,
-       "outfile|o=s" => \$OutFile,
-       'map|m=s' => sub
-       {
-               my ($in_ds, $out_ds) = split (':', $_[1]);
-               if (!defined ($in_ds) || !defined ($out_ds))
-               {
-                       print STDERR "Argument for `map' incorrect! The format is `--map in_ds:out_ds'\n";
-                       exit (1);
-               }
-               push (@$InDS, $in_ds);
-               push (@$OutDS, $out_ds);
-       },
-       'step|s=i' => \$Step,
-       'rra|a=s' => sub
-       {
-               my ($rra, $cf, $xff, $steps, $rows) = split (':', $_[1]);
-               if (($rra ne 'RRA') || !defined ($rows))
-               {
-                       print STDERR "Please use the standard RRDTool syntax when adding RRAs. I. e. RRA:<cf><xff>:<steps>:<rows>.\n";
-                       exit (1);
-               }
-               push (@$NewRRAs, {cf => $cf, xff => $xff, steps => $steps, rows => $rows});
-       }
-) or exit (1);
-
-if (!$InFile || !$OutFile)
-{
-       print STDERR "Usage: $0 -i <infile> -m <in_ds>:<out_ds> -s <step>\n";
-       exit (1);
-}
-if ((1 + @$InDS) != (1 + @$OutDS))
-{
-       print STDERR "You need the same amount of in- and out-DSes\n";
-       exit (1);
-}
-
-main ($InFile, $OutFile);
-exit (0);
-
-{
-my $ds_index;
-my $current_index;
-# state 0 == searching for DS index
-# state 1 == parse RRA header
-# state 2 == parse values
-my $state;
-my $out_cache;
-sub handle_line_dsmap
-{
-       my $line = shift;
-       my $index = shift;
-       my $ret = '';
-
-       if ((@$InDS == 0) || (@$OutDS == 0))
-       {
-               post_line ($line, $index + 1);
-               return;
-       }
-
-       if (!defined ($state))
-       {
-               $current_index = -1;
-               $state = 0;
-               $out_cache = [];
-
-               # $ds_index->[new_index] = old_index
-               $ds_index = [];
-               for (my $i = 0; $i < @$InDS; $i++)
-               {
-                       $ds_index->[$i] = -1;
-               }
-       }
-
-       if ($state == 0)
-       {
-               if ($line =~ m/<ds>/)
-               {
-                       $current_index++;
-                       $out_cache->[$current_index] = $line;
-               }
-               elsif ($line =~ m#<name>\s*([^<\s]+)\s*</name>#)
-               {
-                       # old_index == $current_index
-                       # new_index == $i
-                       for (my $i = 0; $i < @$InDS; $i++)
-                       {
-                               next if ($ds_index->[$i] >= 0);
-
-                               if ($1 eq $InDS->[$i])
-                               {
-                                       $line =~ s#<name>\s*([^<\s]+)\s*</name>#<name> $OutDS->[$i] </name>#;
-                                       $ds_index->[$i] = $current_index;
-                                       last;
-                               }
-                       }
-
-                       $out_cache->[$current_index] .= $line;
-               }
-               elsif ($line =~ m#</ds>#)
-               {
-                       $out_cache->[$current_index] .= $line;
-               }
-               elsif ($line =~ m#<rra>#)
-               {
-                       # Print out all the DS definitions we need
-                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
-                       {
-                               my $old_index = $ds_index->[$new_index];
-                               while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
-                               {
-                                       post_line ("$1\n", $index + 1);
-                               }
-                       }
-
-                       # Clear the cache - it's used in state1, too.
-                       for (my $i = 0; $i <= $current_index; $i++)
-                       {
-                               $out_cache->[$i] = '';
-                       }
-
-                       $ret .= $line;
-                       $current_index = -1;
-                       $state = 1;
-               }
-               elsif ($current_index == -1)
-               {
-                       # Print all the lines before the first DS definition
-                       $ret .= $line;
-               }
-               else
-               {
-                       # Something belonging to a DS-definition
-                       $out_cache->[$current_index] .= $line;
-               }
-       }
-       elsif ($state == 1)
-       {
-               if ($line =~ m#<ds>#)
-               {
-                       $current_index++;
-                       $out_cache->[$current_index] .= $line;
-               }
-               elsif ($line =~ m#</cdp_prep>#)
-               {
-                       # Print out all the DS definitions we need
-                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
-                       {
-                               my $old_index = $ds_index->[$new_index];
-                               while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
-                               {
-                                       post_line ("$1\n", $index + 1);
-                               }
-                       }
-
-                       # Clear the cache
-                       for (my $i = 0; $i <= $current_index; $i++)
-                       {
-                               $out_cache->[$i] = '';
-                       }
-
-                       $ret .= $line;
-                       $current_index = -1;
-               }
-               elsif ($line =~ m#<database>#)
-               {
-                       $ret .= $line;
-                       $state = 2;
-               }
-               elsif ($current_index == -1)
-               {
-                       # Print all the lines before the first DS definition
-                       # and after cdp_prep
-                       $ret .= $line;
-               }
-               else
-               {
-                       # Something belonging to a DS-definition
-                       $out_cache->[$current_index] .= $line;
-               }
-       }
-       elsif ($state == 2)
-       {
-               if ($line =~ m#</database>#)
-               {
-                       $ret .= $line;
-                       $current_index = -1;
-                       $state = 1;
-               }
-               else
-               {
-                       my @values = ();
-                       my $i;
-                       
-                       $ret .= "\t\t";
-
-                       if ($line =~ m#(<!-- .*? -->)#)
-                       {
-                               $ret .= "$1 ";
-                       }
-                       $ret .= "<row> ";
-
-                       $i = 0;
-                       while ($line =~ m#<v>\s*([^<\s]+)\s*</v>#g)
-                       {
-                               $values[$i] = $1;
-                               $i++;
-                       }
-
-                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
-                       {
-                               my $old_index = $ds_index->[$new_index];
-                               $ret .= '<v> ' . $values[$old_index] . ' </v> ';
-                       }
-                       $ret .= "</row>\n";
-               }
-       }
-       else
-       {
-               die;
-       }
-
-       if ($ret)
-       {
-               post_line ($ret, $index + 1);
-       }
-}} # handle_line_dsmap
-
-#
-# The _step_ handler
-#
-{
-my $step_factor_up;
-my $step_factor_down;
-sub handle_line_step
-{
-       my $line = shift;
-       my $index = shift;
-
-       if (!$Step)
-       {
-               post_line ($line, $index + 1);
-               return;
-       }
-
-       $step_factor_up ||= 0;
-       $step_factor_down ||= 0;
-
-       if (($step_factor_up == 0) && ($step_factor_down == 0))
-       {
-               if ($line =~ m#<step>\s*(\d+)\s*</step>#i)
-               {
-                       my $old_step = 0 + $1;
-                       if ($Step < $old_step)
-                       {
-                               $step_factor_down = int ($old_step / $Step);
-                               if (($step_factor_down * $Step) != $old_step)
-                               {
-                                       print STDERR "The old step ($old_step seconds) "
-                                       . "is not a multiple of the new step "
-                                       . "($Step seconds).\n";
-                                       exit (1);
-                               }
-                               $line = "<step> $Step </step>\n";
-                       }
-                       elsif ($Step > $old_step)
-                       {
-                               $step_factor_up = int ($Step / $old_step);
-                               if (($step_factor_up * $old_step) != $Step)
-                               {
-                                       print STDERR "The new step ($Step seconds) "
-                                       . "is not a multiple of the old step "
-                                       . "($old_step seconds).\n";
-                                       exit (1);
-                               }
-                               $line = "<step> $Step </step>\n";
-                       }
-                       else
-                       {
-                               $Step = 0;
-                       }
-               }
-       }
-       elsif ($line =~ m#<pdp_per_row>\s*(\d+)\s*</pdp_per_row>#i)
-       {
-               my $old_val = 0 + $1;
-               my $new_val;
-               if ($step_factor_up)
-               {
-                       $new_val = int ($old_val / $step_factor_up);
-                       if (($new_val * $step_factor_up) != $old_val)
-                       {
-                               print STDERR "Can't divide number of PDPs per row ($old_val) by step-factor ($step_factor_up).\n";
-                               exit (1);
-                       }
-               }
-               else
-               {
-                       $new_val = $step_factor_down * $old_val;
-               }
-               $line = "<pdp_per_row> $new_val </pdp_per_row>\n";
-       }
-
-       post_line ($line, $index + 1);
-}} # handle_line_step
-
-#
-# The _add RRA_ handler
-#
-{
-my $add_rra_done;
-my $num_ds;
-sub handle_line_add_rra
-{
-  my $line = shift;
-  my $index = shift;
-
-  my $post = sub { for (@_) { post_line ($_, $index + 1); } };
-
-  $num_ds ||= 0;
-
-  if (!@$NewRRAs || $add_rra_done)
-  {
-    $post->($line);
-    return;
-  }
-
-  if ($line =~ m#<ds>#i)
-  {
-    $num_ds++;
-  }
-  elsif ($line =~ m#<rra>#i)
-  {
-    for (my $i = 0; $i < @$NewRRAs; $i++)
-    {
-      my $rra = $NewRRAs->[$i];
-      my $temp;
-      $post->("\t<rra>\n",
-      "\t\t<cf> $rra->{'cf'} </cf>\n",
-      "\t\t<pdp_per_row> $rra->{'steps'} </pdp_per_row>\n",
-      "\t\t<params>\n",
-      "\t\t\t<xff> $rra->{'xff'} </xff>\n",
-      "\t\t</params>\n",
-      "\t\t<cdp_prep>\n");
-
-      for (my $j = 0; $j < $num_ds; $j++)
-      {
-       $post->("\t\t\t<ds>\n",
-       "\t\t\t\t<primary_value> NaN </primary_value>\n",
-       "\t\t\t\t<secondary_value> NaN </secondary_value>\n",
-       "\t\t\t\t<value> NaN </value>\n",
-       "\t\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n",
-       "\t\t\t</ds>\n");
-      }
-
-      $post->("\t\t</cdp_prep>\n", "\t\t<database>\n");
-      $temp = "\t\t\t<row>" . join ('', map { "<v> NaN </v>" } (1 .. $num_ds)) . "</row>\n";
-      for (my $j = 0; $j < $rra->{'rows'}; $j++)
-      {
-       $post->($temp);
-      }
-      $post->("\t\t</database>\n");
-    }
-  }
-
-  $post->($line);
-}} # handle_line_add_rra
-
-#
-# The _output_ handler
-#
-{
-my $fh;
-sub set_output
-{
-       $fh = shift;
-}
-
-sub handle_line_output
-{
-       my $line = shift;
-       my $index = shift;
-
-       if (!defined ($fh))
-       {
-               post_line ($line, $index + 1);
-               return;
-       }
-       
-       print $fh $line;
-}} # handle_line_output
-
-#
-# Dispatching logic
-#
-{
-my @handlers = ();
-sub add_handler
-{
-       my $handler = shift;
-
-       die unless (ref ($handler) eq 'CODE');
-       push (@handlers, $handler);
-} # add_handler
-
-sub post_line
-{
-       my $line = shift;
-       my $index = shift;
-
-       if (0)
-       {
-               my $copy = $line;
-               chomp ($copy);
-               print "DEBUG: post_line ($copy, $index);\n";
-       }
-
-       if ($index > $#handlers)
-       {
-               return;
-       }
-       $handlers[$index]->($line, $index);
-}} # post_line
-
-sub handle_fh
-{
-       my $in_fh = shift;
-       my $out_fh = shift;
-
-       set_output ($out_fh);
-
-       if (@$InDS)
-       {
-         add_handler (\&handle_line_dsmap);
-       }
-
-       if ($Step)
-       {
-         add_handler (\&handle_line_step);
-       }
-
-       if (@$NewRRAs)
-       {
-         add_handler (\&handle_line_add_rra);
-       }
-
-       add_handler (\&handle_line_output);
-
-       while (my $line = <$in_fh>)
-       {
-               post_line ($line, 0);
-       }
-} # handle_fh
-
-sub main
-{
-       my $in_file = shift;
-       my $out_file = shift;
-
-       my $in_fh;
-       my $out_fh;
-
-       my $in_needs_close = 1;
-       my $out_needs_close = 1;
-
-       if ($in_file =~ m/\.rrd$/i)
-       {
-               open ($in_fh,  '-|', 'rrdtool', 'dump', $in_file) or die ("open (rrdtool): $!");
-       }
-       elsif ($in_file eq '-')
-       {
-               $in_fh = \*STDIN;
-               $in_needs_close = 0;
-       }
-       else
-       {
-               open ($in_fh, '<', $in_file) or die ("open ($in_file): $!");
-       }
-
-       if ($out_file =~ m/\.rrd$/i)
-       {
-               open ($out_fh, '|-', 'rrdtool', 'restore', '-', $out_file) or die ("open (rrdtool): $!");
-       }
-       elsif ($out_file eq '-')
-       {
-               $out_fh = \*STDOUT;
-               $out_needs_close = 0;
-       }
-       else
-       {
-               open ($out_fh, '>', $out_file) or die ("open ($out_file): $!");
-       }
-
-       handle_fh ($in_fh, $out_fh);
-
-       if ($in_needs_close)
-       {
-               close ($in_fh);
-       }
-       if ($out_needs_close)
-       {
-               close ($out_fh);
-       }
-} # main
-
-=head1 LICENSE
-
-This script is licensed under the GNU general public license, versionE<nbsp>2
-(GPLv2).
-
-=head1 AUTHOR
-
-Florian octo Forster E<lt>octo at verplant.orgE<gt>
-
index 699a471..ed41827 100755 (executable)
@@ -167,7 +167,7 @@ for (@Files)
                        my $src_ds = $src_dses->[$i];
                        $dest->{'type_instance'} = $type_instances->[$i];
                        $dest_filename = get_filename ($dest);
-                       print "./extractDS.px -i '$InDir/$orig_filename' -s '$src_ds' -o '$OutDir/$dest_filename' -d '$dst_ds'\n";
+                       print "./rrd_filter.px -i '$InDir/$orig_filename' -m '${src_ds}:${dst_ds}' -o '$OutDir/$dest_filename'\n";
                }
        }
        elsif (exists ($TypeRename{$orig->{'type'}}))
@@ -362,19 +362,19 @@ sub special_disk
                $OutDirs{$dest_directory} = 1;
        }
 
-       print "./extractDS.px -i '$InDir/$orig_filename' -s 'rmerged' -s 'wmerged' -o '$OutDir/$dest_filename' -d 'read' -d 'write'\n";
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rmerged:read' -m 'wmerged:write' -o '$OutDir/$dest_filename'\n";
 
        $dest->{'type'} = 'disk_octets';
        $dest_filename = get_filename ($dest);
-       print "./extractDS.px -i '$InDir/$orig_filename' -s 'rbytes' -s 'wbytes' -o '$OutDir/$dest_filename' -d 'read' -d 'write'\n";
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rbytes:read' -m 'wbytes:write' -o '$OutDir/$dest_filename'\n";
 
        $dest->{'type'} = 'disk_ops';
        $dest_filename = get_filename ($dest);
-       print "./extractDS.px -i '$InDir/$orig_filename' -s 'rcount' -s 'wcount' -o '$OutDir/$dest_filename' -d 'read' -d 'write'\n";
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rcount:read' -m 'wcount:write' -o '$OutDir/$dest_filename'\n";
 
        $dest->{'type'} = 'disk_time';
        $dest_filename = get_filename ($dest);
-       print "./extractDS.px -i '$InDir/$orig_filename' -s 'rtime' -s 'wtime' -o '$OutDir/$dest_filename' -d 'read' -d 'write'\n";
+       print "./rrd_filter.px -i '$InDir/$orig_filename' -m 'rtime:read' -m 'wtime:write' -o '$OutDir/$dest_filename'\n";
 }
 
 sub exit_usage
diff --git a/contrib/network-proxy.py b/contrib/network-proxy.py
new file mode 100644 (file)
index 0000000..98a4ad8
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+# vim: sts=4 sw=4 et
+
+# Simple unicast proxy to send collectd traffic to another host/port.
+# Copyright (C) 2007  Pavel Shramov <shramov at mexmat.net>
+#
+# 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., 59 Temple
+# Place, Suite 330, Boston, MA  02111-1307  USA
+
+"""
+Simple unicast proxy for collectd (>= 4.0).
+Binds to 'local' address and forwards all traffic to 'remote'.
+"""
+
+import socket
+import struct
+
+""" Local multicast group/port"""
+local  = ("239.192.74.66", 25826)
+""" Address to send packets """
+remote = ("grid.pp.ru", 35826)
+
+sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+mreq = struct.pack("4sl", socket.inet_aton(local[0]), socket.INADDR_ANY)
+
+sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
+sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
+sock.bind(local)
+
+out = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+
+if __name__ == "__main__":
+    while True:
+        (buf, addr) = sock.recvfrom(2048)
+        sock.sendto(buf, remote)
diff --git a/contrib/redhat/apache.conf b/contrib/redhat/apache.conf
new file mode 100644 (file)
index 0000000..e9c767a
--- /dev/null
@@ -0,0 +1,8 @@
+LoadPlugin apache
+#<Plugin apache>
+#      URL "http://localhost/status?auto"
+#      User "www-user"
+#      Password "secret"
+#      CACert "/etc/ssl/ca.crt"
+#</Plugin>
+
diff --git a/contrib/redhat/collectd.conf b/contrib/redhat/collectd.conf
new file mode 100644 (file)
index 0000000..7a026fa
--- /dev/null
@@ -0,0 +1,209 @@
+#
+# Config file for collectd(1).
+# Please read collectd.conf(5) for a list of options.
+# http://collectd.org/
+#
+
+#Hostname    "localhost"
+FQDNLookup   true
+BaseDir     "/var/lib/collectd"
+PIDFile     "/var/run/collectd.pid"
+PluginDir   "/usr/lib/collectd"
+TypesDB     "/usr/lib/collectd/types.db"
+Interval     10
+ReadThreads  5
+
+LoadPlugin apcups
+#LoadPlugin apple_sensors
+LoadPlugin battery
+LoadPlugin cpu
+LoadPlugin cpufreq
+LoadPlugin csv
+LoadPlugin df
+LoadPlugin disk
+LoadPlugin dns
+LoadPlugin entropy
+LoadPlugin exec
+LoadPlugin hddtemp
+LoadPlugin interface
+#LoadPlugin iptables
+#LoadPlugin ipvs
+LoadPlugin irq
+#LoadPlugin libvirt
+LoadPlugin load
+LoadPlugin logfile
+LoadPlugin mbmon
+LoadPlugin memcached
+LoadPlugin memory
+LoadPlugin multimeter
+#LoadPlugin netlink
+LoadPlugin network
+LoadPlugin nfs
+LoadPlugin ntpd
+#LoadPlugin nut
+LoadPlugin perl
+LoadPlugin ping
+LoadPlugin processes
+LoadPlugin rrdtool
+LoadPlugin serial
+LoadPlugin swap
+LoadPlugin syslog
+#LoadPlugin tape
+LoadPlugin tcpconns
+LoadPlugin unixsock
+LoadPlugin users
+LoadPlugin uuid
+LoadPlugin vserver
+LoadPlugin wireless
+#LoadPlugin xmms
+
+
+#<Plugin apcups>
+#      Host "localhost"
+#      Port "3551"
+#</Plugin>
+
+#<Plugin csv>
+#      DataDir "/usr/var/lib/collectd/csv"
+#      StoreRates false
+#</Plugin>
+
+#<Plugin df>
+#      Device "/dev/hda1"
+#      Device "192.168.0.2:/mnt/nfs"
+#      MountPoint "/home"
+#      FSType "ext3"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin dns>
+#      Interface "eth0"
+#      IgnoreSource "192.168.0.1"
+#</Plugin>
+
+#<Plugin exec>
+#      Exec "user:group" "/path/to/exec"
+#      NotificationExec "/path/to/exec"
+#</Plugin>
+
+#<Plugin hddtemp>
+#      Host "127.0.0.1"
+#      Port "7634"
+#      TranslateDevicename false
+#</Plugin>
+
+#<Plugin interface>
+#      Interface "eth0"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin iptables>
+#      Chain table chain
+#</Plugin>
+
+#<Plugin irq>
+#      Irq 7
+#      Irq 8
+#      Irq 9
+#      IgnoreSelected true
+#</Plugin>
+
+#<Plugin libvirt>
+#      Connection "xen:///"
+#      RefreshInterval 60
+#      Domain "name"
+#      BlockDevice "name:device"
+#      InterfaceDevice "name:device"
+#      IgnoreSelected false
+#      HostnameFormat name
+#</Plugin>
+
+#<Plugin logfile>
+#      LogLevel info
+#      File STDOUT
+#      Timestamp true
+#</Plugin>
+
+#<Plugin mbmon>
+#      Host "127.0.0.1"
+#      Port "411"
+#</Plugin>
+
+#<Plugin memcached>
+#      Host "127.0.0.1"
+#      Port "11211"
+#</Plugin>
+
+#<Plugin netlink>
+#      Interface "All"
+#      VerboseInterface "All"
+#      QDisc "eth0" "pfifo_fast-1:0"
+#      Class "ppp0" "htb-1:10"
+#      Filter "ppp0" "u32-1:0"
+#      IgnoreSelected false
+#</Plugin>
+
+#<Plugin network>
+#      Server "ff18::efc0:4a42" "25826"
+#      Server "239.192.74.66" "25826"
+#      Listen "ff18::efc0:4a42" "25826"
+#      Listen "239.192.74.66" "25826"
+#      TimeToLive "128"
+#      Forward false
+#      CacheFlush 1800
+#</Plugin>
+
+#<Plugin ntpd>
+#      Host "localhost"
+#      Port 123
+#      ReverseLookups false
+#</Plugin>
+
+#<Plugin nut>
+#      UPS "upsname@hostname:port"
+#</Plugin>
+
+#<Plugin perl>
+#      IncludeDir "/my/include/path"
+#      BaseName "Collectd::Plugin"
+#      EnableDebugger ""
+#      LoadPlugin foo
+#</Plugin>
+
+#<Plugin ping>
+#      Host "host.foo.bar"
+#      TTL 255
+#</Plugin>
+
+#<Plugin processes>
+#      Process "name"
+#</Plugin>
+
+#<Plugin rrdtool>
+#      DataDir "/usr/var/lib/collectd/rrd"
+#      CacheTimeout 120
+#      CacheFlush   900
+#</Plugin>
+
+#<Plugin syslog>
+#      LogLevel info
+#</Plugin>
+
+#<Plugin tcpconns>
+#      ListeningPorts false
+#      LocalPort "25"
+#      RemotePort "25"
+#</Plugin>
+
+#<Plugin unixsock>
+#      SocketFile "/usr/var/run/collectd-unixsock"
+#      SocketGroup "collectd"
+#      SocketPerms "0660"
+#</Plugin>
+
+#<Plugin uuid>
+#      UUIDFile "/etc/uuid"
+#</Plugin>
+
+Include "/etc/collectd.d"
+
diff --git a/contrib/redhat/collectd.spec b/contrib/redhat/collectd.spec
new file mode 100644 (file)
index 0000000..bce7647
--- /dev/null
@@ -0,0 +1,410 @@
+Summary:       Statistics collection daemon for filling RRD files.
+Name:          collectd
+Version:       4.3.1
+Release:       0.centos5
+Source:                http://collectd.org/files/%{name}-%{version}.tar.gz
+License:       GPL
+Group:         System Environment/Daemons
+BuildRoot:     %{_tmppath}/%{name}-%{version}-root
+BuildPrereq:   lm_sensors-devel, mysql-devel, rrdtool-devel, curl-devel, libpcap-devel, net-snmp-devel, libstatgrab-devel, mysql-devel, libxml2-devel, libiptcdata-devel
+Requires:      rrdtool, perl-Regexp-Common, libstatgrab
+Packager:      RightScale <support@rightscale.com>
+Vendor:                collectd development team <collectd@verplant.org>
+
+%description
+collectd is a small daemon which collects system information periodically and
+provides mechanisms to monitor and store the values in a variety of ways. It
+is written in C for performance. Since the daemon doesn't need to startup
+every time it wants to update the values it's very fast and easy on the
+system. Also, the statistics are very fine grained since the files are updated
+every 10 seconds.
+
+%package apache
+Summary:       apache-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, curl
+%description apache
+This plugin collects data provided by Apache's `mod_status'.
+
+%package email
+Summary:       email-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, spamassassin
+%description email
+This plugin collects data provided by spamassassin.
+
+%package mysql
+Summary:       mysql-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, mysql
+%description mysql
+MySQL querying plugin. This plugins provides data of issued commands, called
+handlers and database traffic.
+
+%package nginx
+Summary:       nginx-plugin for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, curl
+%description nginx
+This plugin gets data provided by nginx.
+
+%package sensors
+Summary:       libsensors-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, lm_sensors
+%description sensors
+This plugin for collectd provides querying of sensors supported by lm_sensors.
+
+%package snmp
+Summary:       snmp-module for collectd.
+Group:         System Environment/Daemons
+Requires:      collectd = %{version}, net-snmp
+%description snmp
+This plugin for collectd allows querying of network equipment using SNMP.
+
+%prep
+rm -rf $RPM_BUILD_ROOT
+%setup
+
+%build
+./configure CFLAGS=-"DLT_LAZY_OR_NOW='RTLD_LAZY|RTLD_GLOBAL'" --prefix=%{_prefix} --sbindir=%{_sbindir} --mandir=%{_mandir} --libdir=%{_libdir} --sysconfdir=%{_sysconfdir} --enable-apache --enable-email --enable-mysql --enable-dns
+make
+
+%install
+make install DESTDIR=$RPM_BUILD_ROOT
+mkdir -p $RPM_BUILD_ROOT/etc/rc.d/init.d
+mkdir -p $RPM_BUILD_ROOT/var/www/cgi-bin
+cp contrib/redhat/init.d-collectd $RPM_BUILD_ROOT/etc/rc.d/init.d/collectd
+cp contrib/collection.cgi $RPM_BUILD_ROOT/var/www/cgi-bin
+mkdir -p $RPM_BUILD_ROOT/etc/collectd.d
+mkdir -p $RPM_BUILD_ROOT/var/lib/collectd
+### Clean up docs
+find contrib/ -type f -exec %{__chmod} a-x {} \;
+###Modify Config for Redhat Based Distros
+cp contrib/redhat/collectd.conf $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#BaseDir     "/usr/var/lib/collectd":BaseDir     "/var/lib/collectd":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#PIDFile     "/usr/var/run/collectd.pid":PIDFile     "/var/run/collectd.pid":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#PluginDir   "/usr/lib/collectd":PluginDir   "/usr/lib/collectd":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#TypesDB     "/usr/lib/collectd/types.db":TypesDB     "/usr/lib/collectd/types.db":' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#Interval     10:Interval     10:' $RPM_BUILD_ROOT/etc/collectd.conf
+sed -i 's:#ReadThreads  5:ReadThreads  5:' $RPM_BUILD_ROOT/etc/collectd.conf
+##Move config contribs
+cp contrib/redhat/apache.conf $RPM_BUILD_ROOT/etc/collectd.d/apache.conf
+cp contrib/redhat/email.conf $RPM_BUILD_ROOT/etc/collectd.d/email.conf
+cp contrib/redhat/sensors.conf $RPM_BUILD_ROOT/etc/collectd.d/sensors.conf
+cp contrib/redhat/mysql.conf $RPM_BUILD_ROOT/etc/collectd.d/mysql.conf
+cp contrib/redhat/nginx.conf $RPM_BUILD_ROOT/etc/collectd.d/nginx.conf
+cp contrib/redhat/snmp.conf $RPM_BUILD_ROOT/etc/collectd.d/snmp.conf
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+/sbin/chkconfig --add collectd
+/sbin/chkconfig collectd on
+
+%preun
+if [ "$1" = 0 ]; then
+   /sbin/chkconfig collectd off
+   /etc/init.d/collectd stop
+   /sbin/chkconfig --del collectd
+fi
+exit 0
+
+%postun
+if [ "$1" -ge 1 ]; then
+    /etc/init.d/collectd restart
+fi
+exit 0
+
+%files
+%defattr(-,root,root)
+%doc AUTHORS COPYING ChangeLog INSTALL NEWS README contrib/
+%attr(0644,root,root) %config(noreplace) /etc/collectd.conf
+%attr(0755,root,root) /etc/rc.d/init.d/collectd
+%attr(0755,root,root) /var/www/cgi-bin/collection.cgi
+%attr(0755,root,root) %{_sbindir}/collectd
+%attr(0755,root,root) %{_bindir}/collectd-nagios
+%attr(0755,root,root) %{_sbindir}/collectdmon
+%attr(0644,root,root) %{_mandir}/man1/*
+%attr(0644,root,root) %{_mandir}/man5/*
+%dir /etc/collectd.d
+
+%attr(0644,root,root) %{_libdir}/%{name}/apcups.so*
+%attr(0644,root,root) %{_libdir}/%{name}/apcups.la
+
+#%attr(0644,root,root) %{_libdir}/%{name}/apple_sensors.so*
+#%attr(0644,root,root) %{_libdir}/%{name}/apple_sensors.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/battery.so*
+%attr(0644,root,root) %{_libdir}/%{name}/battery.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/cpufreq.so*
+%attr(0644,root,root) %{_libdir}/%{name}/cpufreq.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/cpu.so*
+%attr(0644,root,root) %{_libdir}/%{name}/cpu.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/csv.so*
+%attr(0644,root,root) %{_libdir}/%{name}/csv.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/df.so*
+%attr(0644,root,root) %{_libdir}/%{name}/df.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/disk.so*
+%attr(0644,root,root) %{_libdir}/%{name}/disk.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/dns.so*
+%attr(0644,root,root) %{_libdir}/%{name}/dns.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/entropy.so*
+%attr(0644,root,root) %{_libdir}/%{name}/entropy.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/exec.so*
+%attr(0644,root,root) %{_libdir}/%{name}/exec.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/hddtemp.so*
+%attr(0644,root,root) %{_libdir}/%{name}/hddtemp.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/interface.so*
+%attr(0644,root,root) %{_libdir}/%{name}/interface.la
+
+#%attr(0644,root,root) %{_libdir}/%{name}/iptables.so*
+#%attr(0644,root,root) %{_libdir}/%{name}/iptables.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/irq.so*
+%attr(0644,root,root) %{_libdir}/%{name}/irq.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/load.so*
+%attr(0644,root,root) %{_libdir}/%{name}/load.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/logfile.so*
+%attr(0644,root,root) %{_libdir}/%{name}/logfile.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/mbmon.so
+%attr(0644,root,root) %{_libdir}/%{name}/mbmon.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/memcached.so*
+%attr(0644,root,root) %{_libdir}/%{name}/memcached.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/memory.so*
+%attr(0644,root,root) %{_libdir}/%{name}/memory.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/multimeter.so*
+%attr(0644,root,root) %{_libdir}/%{name}/multimeter.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/network.so*
+%attr(0644,root,root) %{_libdir}/%{name}/network.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/nfs.so*
+%attr(0644,root,root) %{_libdir}/%{name}/nfs.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/ntpd.so*
+%attr(0644,root,root) %{_libdir}/%{name}/ntpd.la
+
+#%attr(0644,root,root) %{_libdir}/%{name}/nut.so*
+#%attr(0644,root,root) %{_libdir}/%{name}/nut.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/perl.so*
+%attr(0644,root,root) %{_libdir}/%{name}/perl.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/ping.so*
+%attr(0644,root,root) %{_libdir}/%{name}/ping.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/processes.so*
+%attr(0644,root,root) %{_libdir}/%{name}/processes.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/rrdtool.so*
+%attr(0644,root,root) %{_libdir}/%{name}/rrdtool.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/serial.so*
+%attr(0644,root,root) %{_libdir}/%{name}/serial.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/swap.so*
+%attr(0644,root,root) %{_libdir}/%{name}/swap.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/syslog.so*
+%attr(0644,root,root) %{_libdir}/%{name}/syslog.la
+
+#%attr(0644,root,root) %{_libdir}/%{name}/tape.so*
+#%attr(0644,root,root) %{_libdir}/%{name}/tape.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/tcpconns.so*
+%attr(0644,root,root) %{_libdir}/%{name}/tcpconns.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/unixsock.so*
+%attr(0644,root,root) %{_libdir}/%{name}/unixsock.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/users.so*
+%attr(0644,root,root) %{_libdir}/%{name}/users.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/uuid.so*
+%attr(0644,root,root) %{_libdir}/%{name}/uuid.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/vserver.so*
+%attr(0644,root,root) %{_libdir}/%{name}/vserver.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/wireless.so*
+%attr(0644,root,root) %{_libdir}/%{name}/wireless.la
+
+%attr(0644,root,root) %{_libdir}/%{name}/types.db
+
+%exclude %{_libdir}/perl5/5.8.8/%{_arch}-linux-thread-multi/perllocal.pod
+%attr(0644,root,root) %{_libdir}/perl5/site_perl/5.8.8/Collectd.pm
+%attr(0644,root,root) %{_libdir}/perl5/site_perl/5.8.8/Collectd/Unixsock.pm
+%attr(0644,root,root) %{_libdir}/perl5/site_perl/5.8.8/%{_arch}-linux-thread-multi/auto/Collectd/.packlist
+%attr(0644,root,root) /usr/share/man/man3/Collectd::Unixsock.3pm.gz
+
+%dir /var/lib/collectd
+
+%files apache
+%attr(0644,root,root) %{_libdir}/%{name}/apache.so*
+%attr(0644,root,root) %{_libdir}/%{name}/apache.la
+%attr(0644,root,root) /etc/collectd.d/apache.conf
+
+%files email
+%attr(0644,root,root) %{_libdir}/%{name}/email.so*
+%attr(0644,root,root) %{_libdir}/%{name}/email.la
+%attr(0644,root,root) /etc/collectd.d/email.conf
+
+%files mysql
+%attr(0644,root,root) %{_libdir}/%{name}/mysql.so*
+%attr(0644,root,root) %{_libdir}/%{name}/mysql.la
+%attr(0644,root,root) /etc/collectd.d/mysql.conf
+
+%files nginx
+%attr(0644,root,root) %{_libdir}/%{name}/nginx.so*
+%attr(0644,root,root) %{_libdir}/%{name}/nginx.la
+%attr(0644,root,root) /etc/collectd.d/nginx.conf
+
+%files sensors
+%attr(0644,root,root) %{_libdir}/%{name}/sensors.so*
+%attr(0644,root,root) %{_libdir}/%{name}/sensors.la
+%attr(0644,root,root) /etc/collectd.d/sensors.conf
+
+%files snmp
+%attr(0644,root,root) %{_libdir}/%{name}/snmp.so*
+%attr(0644,root,root) %{_libdir}/%{name}/snmp.la
+%attr(0644,root,root) /etc/collectd.d/snmp.conf
+
+%changelog
+* Mon Mar 17 2008 RightScale <support@rightscale.com> 4.3.1
+- New upstream version
+- Changes to support 4.3.1
+- Added More Prereqs to support more plugins
+- Added support for perl plugin
+
+* Mon Aug 06 2007 Kjell Randa <Kjell.Randa@broadpark.no> 4.0.6
+- New upstream version
+
+* Wed Jul 25 2007 Kjell Randa <Kjell.Randa@broadpark.no> 4.0.5
+- New major releas
+- Changes to support 4.0.5 
+
+* Wed Jan 11 2007 Iain Lea <iain@bricbrac.de> 3.11.0-0
+- fixed spec file to build correctly on fedora core
+- added improved init.d script to work with chkconfig
+- added %post and %postun to call chkconfig automatically
+
+* Sun Jul 09 2006 Florian octo Forster <octo@verplant.org> 3.10.0-1
+- New upstream version
+
+* Tue Jun 25 2006 Florian octo Forster <octo@verplant.org> 3.9.4-1
+- New upstream version
+
+* Tue Jun 01 2006 Florian octo Forster <octo@verplant.org> 3.9.3-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.9.2-1
+- New upstream version
+
+* Tue May 09 2006 Florian octo Forster <octo@verplant.org> 3.8.5-1
+- New upstream version
+
+* Fri Apr 21 2006 Florian octo Forster <octo@verplant.org> 3.9.1-1
+- New upstream version
+
+* Fri Apr 14 2006 Florian octo Forster <octo@verplant.org> 3.9.0-1
+- New upstream version
+- Added the `apache' package.
+
+* Thu Mar 14 2006 Florian octo Forster <octo@verplant.org> 3.8.2-1
+- New upstream version
+
+* Thu Mar 13 2006 Florian octo Forster <octo@verplant.org> 3.8.1-1
+- New upstream version
+
+* Thu Mar 09 2006 Florian octo Forster <octo@verplant.org> 3.8.0-1
+- New upstream version
+
+* Sat Feb 18 2006 Florian octo Forster <octo@verplant.org> 3.7.2-1
+- Include `tape.so' so the build doesn't terminate because of missing files..
+- New upstream version
+
+* Sat Feb 04 2006 Florian octo Forster <octo@verplant.org> 3.7.1-1
+- New upstream version
+
+* Mon Jan 30 2006 Florian octo Forster <octo@verplant.org> 3.7.0-1
+- New upstream version
+- Removed the extra `hddtemp' package
+
+* Tue Jan 24 2006 Florian octo Forster <octo@verplant.org> 3.6.2-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.1-1
+- New upstream version
+
+* Fri Jan 20 2006 Florian octo Forster <octo@verplant.org> 3.6.0-1
+- New upstream version
+- Added config file, `collectd.conf(5)', `df.so'
+- Added package `collectd-mysql', dependency on `mysqlclient10 | mysql'
+
+* Wed Dec 07 2005 Florian octo Forster <octo@verplant.org> 3.5.0-1
+- New upstream version
+
+* Sat Nov 26 2005 Florian octo Forster <octo@verplant.org> 3.4.0-1
+- New upstream version
+
+* Sat Nov 05 2005 Florian octo Forster <octo@verplant.org> 3.3.0-1
+- New upstream version
+
+* Tue Oct 26 2005 Florian octo Forster <octo@verplant.org> 3.2.0-1
+- New upstream version
+- Added statement to remove the `*.la' files. This fixes a problem when
+  `Unpackaged files terminate build' is in effect.
+- Added `processes.so*' to the main package
+
+* Fri Oct 14 2005 Florian octo Forster <octo@verplant.org> 3.1.0-1
+- New upstream version
+- Added package `collectd-hddtemp'
+
+* Fri Sep 30 2005 Florian octo Forster <octo@verplant.org> 3.0.0-1
+- New upstream version
+- Split the package into `collectd' and `collectd-sensors'
+
+* Fri Sep 16 2005 Florian octo Forster <octo@verplant.org> 2.1.0-1
+- New upstream version
+
+* Mon Sep 10 2005 Florian octo Forster <octo@verplant.org> 2.0.0-1
+- New upstream version
+
+* Mon Aug 29 2005 Florian octo Forster <octo@verplant.org> 1.8.0-1
+- New upstream version
+
+* Sun Aug 25 2005 Florian octo Forster <octo@verplant.org> 1.7.0-1
+- New upstream version
+
+* Sun Aug 21 2005 Florian octo Forster <octo@verplant.org> 1.6.0-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5.1-1
+- New upstream version
+
+* Sun Jul 17 2005 Florian octo Forster <octo@verplant.org> 1.5-1
+- New upstream version
+
+* Mon Jul 11 2005 Florian octo Forster <octo@verplant.org> 1.4.2-1
+- New upstream version
+
+* Sat Jul 09 2005 Florian octo Forster <octo@verplant.org> 1.4-1
+- Built on RedHat 7.3
diff --git a/contrib/redhat/email.conf b/contrib/redhat/email.conf
new file mode 100644 (file)
index 0000000..6f2caba
--- /dev/null
@@ -0,0 +1,8 @@
+LoadPlugin email
+#<Plugin email>
+#      SocketFile "/usr/var/run/collectd-email"
+#      SocketGroup "collectd"
+#      SocketPerms "0770"
+#      MaxConns 5
+#</Plugin>
+
diff --git a/contrib/redhat/init.d-collectd b/contrib/redhat/init.d-collectd
new file mode 100644 (file)
index 0000000..b7c085c
--- /dev/null
@@ -0,0 +1,68 @@
+#!/bin/bash
+#
+# collectd    Startup script for the Collectd statistics gathering daemon
+# chkconfig: - 86 15
+# description: Collectd is a statistics gathering daemon used to collect \
+#   system information ie. cpu, memory, disk, network
+# processname: collectd
+# config: /etc/collectd.conf
+# config: /etc/sysconfig/collectd
+# pidfile: /var/run/collectd.pid
+
+# Source function library.
+. /etc/init.d/functions
+
+RETVAL=0
+ARGS=""
+prog="collectdmon"
+CONFIG=/etc/collectd.conf
+COLLECTD=/usr/sbin/collectd
+COLLECTDMONPID=/var/run/collectdmon.pid
+
+if [ -r /etc/default/$prog ]; then
+       . /etc/default/$prog
+fi
+
+start () {
+       echo -n $"Starting collectd: "
+       if [ -r "$CONFIG" ]
+       then
+               daemon $prog -P $COLLECTDMONPID -c $COLLECTD -- -C "$CONFIG"
+               RETVAL=$?
+               echo
+               [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
+       fi
+}
+stop () {
+       echo -n $"Stopping collectd: "
+       killproc $prog
+       RETVAL=$?
+       echo
+       [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
+}
+# See how we were called.
+case "$1" in
+  start)
+       start
+       ;;
+  stop)
+       stop
+       ;;
+  status)
+       status $prog
+       ;;
+  restart|reload)
+       stop
+       start
+       ;;
+  condrestart)
+       [ -f /var/lock/subsys/$prog ] && restart || :
+       ;;
+  *)
+       echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
+       exit 1
+esac
+
+exit $?
+
+# vim:syntax=sh
diff --git a/contrib/redhat/mysql.conf b/contrib/redhat/mysql.conf
new file mode 100644 (file)
index 0000000..ad87557
--- /dev/null
@@ -0,0 +1,9 @@
+LoadPlugin mysql
+
+#<Plugin mysql>
+#      Host "database.serv.er"
+#      User "db_user"
+#      Password "secret"
+#      Database "db_name"
+#</Plugin>
+
diff --git a/contrib/redhat/nginx.conf b/contrib/redhat/nginx.conf
new file mode 100644 (file)
index 0000000..56ce35d
--- /dev/null
@@ -0,0 +1,8 @@
+LoadPlugin nginx
+
+#<Plugin nginx>
+#      URL "http://localhost/status?auto"
+#      User "www-user"
+#      Password "secret"
+#      CACert "/etc/ssl/ca.crt"
+#</Plugin>
diff --git a/contrib/redhat/sensors.conf b/contrib/redhat/sensors.conf
new file mode 100644 (file)
index 0000000..82455f8
--- /dev/null
@@ -0,0 +1,9 @@
+LoadPlugin sensors
+
+#<Plugin sensors>
+#      Sensor "it8712-isa-0290/temperature-temp1"
+#      Sensor "it8712-isa-0290/fanspeed-fan3"
+#      Sensor "it8712-isa-0290/voltage-in8"
+#      IgnoreSelected false
+#</Plugin>
+
diff --git a/contrib/redhat/snmp.conf b/contrib/redhat/snmp.conf
new file mode 100644 (file)
index 0000000..e13833c
--- /dev/null
@@ -0,0 +1,44 @@
+LoadPlugin snmp
+
+#<Plugin snmp>
+#   <Data "powerplus_voltge_input">
+#       Type "voltage"
+#       Table false
+#       Instance "input_line1"
+#       Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
+#   </Data>
+#   <Data "hr_users">
+#       Type "users"
+#       Table false
+#       Instance ""
+#       Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
+#   </Data>
+#   <Data "std_traffic">
+#       Type "if_octets"
+#       Table true
+#       Instance "IF-MIB::ifDescr"
+#       Values "IF-MIB::ifInOctets" "IF-MIB::ifOutOctets"
+#   </Data>
+#   
+#   <Host "some.switch.mydomain.org">
+#       Address "192.168.0.2"
+#       Version 1
+#       Community "community_string"
+#       Collect "std_traffic"
+#       Inverval 120
+#   </Host>
+#   <Host "some.server.mydomain.org">
+#       Address "192.168.0.42"
+#       Version 2
+#       Community "another_string"
+#       Collect "std_traffic" "hr_users"
+#   </Host>
+#   <Host "some.ups.mydomain.org">
+#       Address "192.168.0.3"
+#       Version 1
+#       Community "more_communities"
+#       Collect "powerplus_voltge_input"
+#       Interval 300
+#   </Host>
+#</Plugin>
+
diff --git a/contrib/rrd_filter.px b/contrib/rrd_filter.px
new file mode 100755 (executable)
index 0000000..d28f9f2
--- /dev/null
@@ -0,0 +1,874 @@
+#!/usr/bin/perl
+
+# collectd - contrib/rrd_filter.px
+# 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
+# Free Software Foundation; only version 2 of the License is applicable.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+# Authors:
+#   Florian octo Forster <octo at verplant.org>
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+rrd_filter.px - Perform same advanced non-standard operations on an RRD file.
+
+=head1 SYNOPSYS
+
+  rrd_filter.px -i input.rrd -o output.rrd [options]
+
+=head1 DEPENDENCIES
+
+rrd_filter.px requires the RRDTool binary, Perl and the included
+L<Getopt::Long> module.
+
+=cut
+
+use Getopt::Long ('GetOptions');
+
+our $InFile;
+our $InDS = [];
+our $OutFile;
+our $OutDS = [];
+
+our $NewDSes = [];
+our $NewRRAs = [];
+
+our $Step = 0;
+
+our $Scale = 1.0;
+our $Shift = 0.0;
+
+our $Debug = 0;
+
+=head1 OPTIONS
+
+The following options can be passed on the command line:
+
+=over 4
+
+=item B<--infile> I<file>
+
+=item B<-i> I<file>
+
+Reads from I<file>. If I<file> ends in C<.rrd>, then C<rrdtool dump> is invoked
+to create an XML dump of the RRD file. Otherwise the XML dump is expected
+directly. The special filename C<-> can be used to read from STDIN.
+
+=item B<--outfile> I<file>
+
+=item B<-o> I<file>
+
+Writes output to I<file>. If I<file> ends in C<.rrd>, then C<rrdtool restore>
+is invoked to create a binary RRD file. Otherwise an XML output is written. The
+special filename C<-> can be used to write to STDOUT.
+
+=item B<--map> I<in_ds>:I<out_ds>
+
+=item B<-m> I<in_ds>:I<out_ds>
+
+Writes the datasource I<in_ds> to the output and renames it to I<out_ds>. This
+is useful to extract one DS from an RRD file.
+
+=item B<--step> I<seconds>
+
+=item B<-s> I<seconds>
+
+Changes the step of the output RRD file to be I<seconds>. The new stepsize must
+be a multiple of the old stepsize of the other way around. When increasing the
+stepsize the number of PDPs in each RRA must be dividable by the factor by
+which the stepsize is increased. The length of CDPs and the absolute length of
+RRAs (and thus the data itself) is not altered.
+
+Examples:
+
+  step =  10, rra_steps = 12   =>   step = 60, rra_steps =  2
+  step = 300, rra_steps =  1   =>   step = 10, rra_steps = 30
+
+=item B<--rra> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
+
+=item B<-a> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
+
+Inserts a new RRA in the generated RRD file. This is done B<after> the step has
+been adjusted, take that into account when specifying I<steps> and I<rows>. For
+an explanation of the format please see L<rrdcreate(1)>.
+
+=item B<--scale> I<factor>
+
+Scales the values by the factor I<factor>, i.E<nbsp>e. all values are
+multiplied by I<factor>.
+
+=item B<--shift> I<offset>
+
+Shifts all values by I<offset>, i.E<nbsp>e. I<offset> is added to all values.
+
+=back
+
+=cut
+
+GetOptions ("infile|i=s" => \$InFile,
+       "outfile|o=s" => \$OutFile,
+       'map|m=s' => sub
+       {
+               my ($in_ds, $out_ds) = split (':', $_[1]);
+               if (!defined ($in_ds) || !defined ($out_ds))
+               {
+                       print STDERR "Argument for `map' incorrect! The format is `--map in_ds:out_ds'\n";
+                       exit (1);
+               }
+               push (@$InDS, $in_ds);
+               push (@$OutDS, $out_ds);
+       },
+       'step|s=i' => \$Step,
+       'ds|d=s' => sub
+       {
+               #DS:ds-name:GAUGE | COUNTER | DERIVE | ABSOLUTE:heartbeat:min:max
+               my ($ds, $name, $type, $hb, $min, $max) = split (':', $_[1]);
+               if (($ds ne 'DS') || !defined ($max))
+               {
+                       print STDERR "Please use the standard RRDTool syntax when adding DSes. I. e. DS:<name>:<type>:<heartbeat>:<min>:<max>.\n";
+                       exit (1);
+               }
+               push (@$NewDSes, {name => $name, type => $type, heartbeat => $hb, min => $min, max => $max});
+       },
+       'rra|a=s' => sub
+       {
+               my ($rra, $cf, $xff, $steps, $rows) = split (':', $_[1]);
+               if (($rra ne 'RRA') || !defined ($rows))
+               {
+                       print STDERR "Please use the standard RRDTool syntax when adding RRAs. I. e. RRA:<cf><xff>:<steps>:<rows>.\n";
+                       exit (1);
+               }
+               push (@$NewRRAs, {cf => $cf, xff => $xff, steps => $steps, rows => $rows});
+       },
+       'scale=f' => \$Scale,
+       'shift=f' => \$Shift
+) or exit (1);
+
+if (!$InFile || !$OutFile)
+{
+       print STDERR "Usage: $0 -i <infile> -m <in_ds>:<out_ds> -s <step>\n";
+       exit (1);
+}
+if ((1 + @$InDS) != (1 + @$OutDS))
+{
+       print STDERR "You need the same amount of in- and out-DSes\n";
+       exit (1);
+}
+main ($InFile, $OutFile);
+exit (0);
+
+{
+my $ds_index;
+my $current_index;
+# state 0 == searching for DS index
+# state 1 == parse RRA header
+# state 2 == parse values
+my $state;
+my $out_cache;
+sub handle_line_dsmap
+{
+       my $line = shift;
+       my $index = shift;
+       my $ret = '';
+
+       if ((@$InDS == 0) || (@$OutDS == 0))
+       {
+               post_line ($line, $index + 1);
+               return;
+       }
+
+       if (!defined ($state))
+       {
+               $current_index = -1;
+               $state = 0;
+               $out_cache = [];
+
+               # $ds_index->[new_index] = old_index
+               $ds_index = [];
+               for (my $i = 0; $i < @$InDS; $i++)
+               {
+                       print STDOUT "DS map $i: $InDS->[$i] -> $OutDS->[$i]\n" if ($Debug);
+                       $ds_index->[$i] = -1;
+               }
+       }
+
+       if ($state == 0)
+       {
+               if ($line =~ m/<ds>/)
+               {
+                       $current_index++;
+                       $out_cache->[$current_index] = $line;
+               }
+               elsif ($line =~ m#<name>\s*([^<\s]+)\s*</name>#)
+               {
+                       # old_index == $current_index
+                       # new_index == $i
+                       for (my $i = 0; $i < @$InDS; $i++)
+                       {
+                               next if ($ds_index->[$i] >= 0);
+
+                               if ($1 eq $InDS->[$i])
+                               {
+                                       $line =~ s#<name>\s*([^<\s]+)\s*</name>#<name> $OutDS->[$i] </name>#;
+                                       $ds_index->[$i] = $current_index;
+                                       last;
+                               }
+                       }
+
+                       $out_cache->[$current_index] .= $line;
+               }
+               elsif ($line =~ m#<last_ds>\s*([^\s>]+)\s*</last_ds>#i)
+               {
+                       $out_cache->[$current_index] .= "\t\t<last_ds> NaN </last_ds>\n";
+               }
+               elsif ($line =~ m#<value>\s*([^\s>]+)\s*</value>#i)
+               {
+                       $out_cache->[$current_index] .= "\t\t<value> NaN </value>\n";
+               }
+               elsif ($line =~ m#</ds>#)
+               {
+                       $out_cache->[$current_index] .= $line;
+               }
+               elsif ($line =~ m#<rra>#)
+               {
+                       # Print out all the DS definitions we need
+                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
+                       {
+                               my $old_index = $ds_index->[$new_index];
+                               while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
+                               {
+                                       post_line ("$1\n", $index + 1);
+                               }
+                       }
+
+                       # Clear the cache - it's used in state1, too.
+                       for (my $i = 0; $i <= $current_index; $i++)
+                       {
+                               $out_cache->[$i] = '';
+                       }
+
+                       $ret .= $line;
+                       $current_index = -1;
+                       $state = 1;
+               }
+               elsif ($current_index == -1)
+               {
+                       # Print all the lines before the first DS definition
+                       $ret .= $line;
+               }
+               else
+               {
+                       # Something belonging to a DS-definition
+                       $out_cache->[$current_index] .= $line;
+               }
+       }
+       elsif ($state == 1)
+       {
+               if ($line =~ m#<ds>#)
+               {
+                       $current_index++;
+                       $out_cache->[$current_index] .= $line;
+               }
+               elsif ($line =~ m#<value>\s*([^\s>]+)\s*</value>#i)
+               {
+                       $out_cache->[$current_index] .= "\t\t\t<value> NaN </value>\n";
+               }
+               elsif ($line =~ m#</cdp_prep>#)
+               {
+                       # Print out all the DS definitions we need
+                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
+                       {
+                               my $old_index = $ds_index->[$new_index];
+                               while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
+                               {
+                                       post_line ("$1\n", $index + 1);
+                               }
+                       }
+
+                       # Clear the cache
+                       for (my $i = 0; $i <= $current_index; $i++)
+                       {
+                               $out_cache->[$i] = '';
+                       }
+
+                       $ret .= $line;
+                       $current_index = -1;
+               }
+               elsif ($line =~ m#<database>#)
+               {
+                       $ret .= $line;
+                       $state = 2;
+               }
+               elsif ($current_index == -1)
+               {
+                       # Print all the lines before the first DS definition
+                       # and after cdp_prep
+                       $ret .= $line;
+               }
+               else
+               {
+                       # Something belonging to a DS-definition
+                       $out_cache->[$current_index] .= $line;
+               }
+       }
+       elsif ($state == 2)
+       {
+               if ($line =~ m#</database>#)
+               {
+                       $ret .= $line;
+                       $current_index = -1;
+                       $state = 1;
+               }
+               else
+               {
+                       my @values = ();
+                       my $i;
+                       
+                       $ret .= "\t\t";
+
+                       if ($line =~ m#(<!-- .*? -->)#)
+                       {
+                               $ret .= "$1 ";
+                       }
+                       $ret .= "<row> ";
+
+                       $i = 0;
+                       while ($line =~ m#<v>\s*([^<\s]+)\s*</v>#g)
+                       {
+                               $values[$i] = $1;
+                               $i++;
+                       }
+
+                       for (my $new_index = 0; $new_index < @$InDS; $new_index++)
+                       {
+                               my $old_index = $ds_index->[$new_index];
+                               $ret .= '<v> ' . $values[$old_index] . ' </v> ';
+                       }
+                       $ret .= "</row>\n";
+               }
+       }
+       else
+       {
+               die;
+       }
+
+       if ($ret)
+       {
+               post_line ($ret, $index + 1);
+       }
+}} # handle_line_dsmap
+
+#
+# The _step_ handler
+#
+{
+my $step_factor_up;
+my $step_factor_down;
+sub handle_line_step
+{
+       my $line = shift;
+       my $index = shift;
+
+       if (!$Step)
+       {
+               post_line ($line, $index + 1);
+               return;
+       }
+
+       if ($Debug && !defined ($step_factor_up))
+       {
+               print STDOUT "New step: $Step\n";
+       }
+
+       $step_factor_up ||= 0;
+       $step_factor_down ||= 0;
+
+       if (($step_factor_up == 0) && ($step_factor_down == 0))
+       {
+               if ($line =~ m#<step>\s*(\d+)\s*</step>#i)
+               {
+                       my $old_step = 0 + $1;
+                       if ($Step < $old_step)
+                       {
+                               $step_factor_down = int ($old_step / $Step);
+                               if (($step_factor_down * $Step) != $old_step)
+                               {
+                                       print STDERR "The old step ($old_step seconds) "
+                                       . "is not a multiple of the new step "
+                                       . "($Step seconds).\n";
+                                       exit (1);
+                               }
+                               $line = "<step> $Step </step>\n";
+                       }
+                       elsif ($Step > $old_step)
+                       {
+                               $step_factor_up = int ($Step / $old_step);
+                               if (($step_factor_up * $old_step) != $Step)
+                               {
+                                       print STDERR "The new step ($Step seconds) "
+                                       . "is not a multiple of the old step "
+                                       . "($old_step seconds).\n";
+                                       exit (1);
+                               }
+                               $line = "<step> $Step </step>\n";
+                       }
+                       else
+                       {
+                               $Step = 0;
+                       }
+               }
+       }
+       elsif ($line =~ m#<pdp_per_row>\s*(\d+)\s*</pdp_per_row>#i)
+       {
+               my $old_val = 0 + $1;
+               my $new_val;
+               if ($step_factor_up)
+               {
+                       $new_val = int ($old_val / $step_factor_up);
+                       if (($new_val * $step_factor_up) != $old_val)
+                       {
+                               print STDERR "Can't divide number of PDPs per row ($old_val) by step-factor ($step_factor_up).\n";
+                               exit (1);
+                       }
+               }
+               else
+               {
+                       $new_val = $step_factor_down * $old_val;
+               }
+               $line = "<pdp_per_row> $new_val </pdp_per_row>\n";
+       }
+
+       post_line ($line, $index + 1);
+}} # handle_line_step
+
+#
+# The _add DS_ handler
+#
+{
+my $add_ds_done;
+sub handle_line_add_ds
+{
+  my $line = shift;
+  my $index = shift;
+
+  my $post = sub { for (@_) { post_line ($_, $index + 1); } };
+
+  if (!@$NewDSes)
+  {
+    $post->($line);
+    return;
+  }
+
+  if (!$add_ds_done && ($line =~ m#<rra>#i))
+  {
+    for (my $i = 0; $i < @$NewDSes; $i++)
+    {
+      my $ds = $NewDSes->[$i];
+      my $temp;
+
+      my $min;
+      my $max;
+
+      if ($Debug)
+      {
+       print STDOUT "Adding DS: name = $ds->{'name'}, type = $ds->{'type'}, heartbeat = $ds->{'heartbeat'}, min = $ds->{'min'}, max = $ds->{'max'}\n";
+      }
+
+      $min = 'NaN';
+      if (defined ($ds->{'min'}) && ($ds->{'min'} ne 'U'))
+      {
+       $min = sprintf ('%.10e', $ds->{'min'});
+      }
+      
+      $max = 'NaN';
+      if (defined ($ds->{'max'}) && ($ds->{'max'} ne 'U'))
+      {
+       $max = sprintf ('%.10e', $ds->{'max'});
+      }
+      
+
+      $post->("\t<ds>\n",
+      "\t\t<name> $ds->{'name'} </name>\n",
+      "\t\t<type> $ds->{'type'} </type>\n",
+      "\t\t<minimal_heartbeat> $ds->{'heartbeat'} </minimal_heartbeat>\n",
+      "\t\t<min> $min </min>\n",
+      "\t\t<max> $max </max>\n",
+      "\n",
+      "\t\t<!-- PDP Status -->\n",
+      "\t\t<last_ds> UNKN </last_ds>\n",
+      "\t\t<value> NaN </value>\n",
+      "\t\t<unknown_sec> 0 </unknown_sec>\n",
+      "\t</ds>\n",
+      "\n");
+    }
+
+    $add_ds_done = 1;
+  }
+  elsif ($add_ds_done && ($line =~ m#</ds>#i)) # inside a cdp_prep block
+  {
+    $post->("\t\t\t</ds>\n",
+       "\t\t\t<ds>\n",
+       "\t\t\t<primary_value> NaN </primary_value>\n",
+       "\t\t\t<secondary_value> NaN </secondary_value>\n",
+       "\t\t\t<value> NaN </value>\n",
+       "\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n");
+  }
+  elsif ($line =~ m#<row>#i)
+  {
+         my $insert = '<v> NaN </v>' x (0 + @$NewDSes);
+         $line =~ s#</row>#$insert</row>#i;
+  }
+
+  $post->($line);
+}} # handle_line_add_ds
+
+#
+# The _add RRA_ handler
+#
+{
+my $add_rra_done;
+my $num_ds;
+sub handle_line_add_rra
+{
+  my $line = shift;
+  my $index = shift;
+
+  my $post = sub { for (@_) { post_line ($_, $index + 1); } };
+
+  $num_ds ||= 0;
+
+  if (!@$NewRRAs || $add_rra_done)
+  {
+    $post->($line);
+    return;
+  }
+
+  if ($line =~ m#<ds>#i)
+  {
+    $num_ds++;
+  }
+  elsif ($line =~ m#<rra>#i)
+  {
+    for (my $i = 0; $i < @$NewRRAs; $i++)
+    {
+      my $rra = $NewRRAs->[$i];
+      my $temp;
+
+      if ($Debug)
+      {
+       print STDOUT "Adding RRA: CF = $rra->{'cf'}, xff = $rra->{'xff'}, steps = $rra->{'steps'}, rows = $rra->{'rows'}, num_ds = $num_ds\n";
+      }
+
+      $post->("\t<rra>\n",
+      "\t\t<cf> $rra->{'cf'} </cf>\n",
+      "\t\t<pdp_per_row> $rra->{'steps'} </pdp_per_row>\n",
+      "\t\t<params>\n",
+      "\t\t\t<xff> $rra->{'xff'} </xff>\n",
+      "\t\t</params>\n",
+      "\t\t<cdp_prep>\n");
+
+      for (my $j = 0; $j < $num_ds; $j++)
+      {
+       $post->("\t\t\t<ds>\n",
+       "\t\t\t\t<primary_value> NaN </primary_value>\n",
+       "\t\t\t\t<secondary_value> NaN </secondary_value>\n",
+       "\t\t\t\t<value> NaN </value>\n",
+       "\t\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n",
+       "\t\t\t</ds>\n");
+      }
+
+      $post->("\t\t</cdp_prep>\n", "\t\t<database>\n");
+      $temp = "\t\t\t<row>" . join ('', map { "<v> NaN </v>" } (1 .. $num_ds)) . "</row>\n";
+      for (my $j = 0; $j < $rra->{'rows'}; $j++)
+      {
+       $post->($temp);
+      }
+      $post->("\t\t</database>\n", "\t</rra>\n");
+    }
+
+    $add_rra_done = 1;
+  }
+
+  $post->($line);
+}} # handle_line_add_rra
+
+#
+# The _scale/shift_ handler
+#
+sub calculate_scale_shift 
+{
+  my $value = shift;
+  my $tag = shift;
+  my $scale = shift;
+  my $shift = shift;
+
+  if (lc ("$value") eq 'nan')
+  {
+    $value = 'NaN';
+    return ("<$tag> NaN </$tag>");
+  }
+
+  $value = ($scale * (0.0 + $value)) + $shift;
+  return (sprintf ("<%s> %1.10e </%s>", $tag, $value, $tag));
+}
+
+sub handle_line_scale_shift
+{
+  my $line = shift;
+  my $index = shift;
+
+  if (($Scale != 1.0) || ($Shift != 0.0))
+  {
+    $line =~ s#<(min|max|last_ds|value|primary_value|secondary_value|v)>\s*([^\s<]+)\s*</[^>]+>#calculate_scale_shift ($2, $1, $Scale, $Shift)#eg;
+  }
+
+  post_line ($line, $index + 1);
+}
+
+#
+# The _output_ handler
+#
+# This filter is unfinished!
+#
+{
+my $fh;
+sub set_output
+{
+       $fh = shift;
+}
+
+{
+my $previous_values;
+my $previous_differences;
+my $pdp_per_row;
+sub handle_line_peak_detect
+{
+  my $line = shift;
+  my $index = shift;
+
+  if (!$previous_values)
+  {
+    $previous_values = [];
+    $previous_differences = [];
+  }
+
+  if ($line =~ m#</database>#i)
+  {
+    $previous_values = [];
+    $previous_differences = [];
+    print STDERR "==============================================================================\n";
+  }
+  elsif ($line =~ m#<pdp_per_row>\s*([1-9][0-9]*)\s*</pdp_per_row>#)
+  {
+    $pdp_per_row = int ($1);
+    print STDERR "pdp_per_row = $pdp_per_row;\n";
+  }
+  elsif ($line =~ m#<row>#)
+  {
+    my @values = ();
+    while ($line =~ m#<v>\s*([^\s>]+)\s*</v>#ig)
+    {
+      if ($1 eq 'NaN')
+      {
+       push (@values, undef);
+      }
+      else
+      {
+       push (@values, 0.0 + $1);
+      }
+    }
+
+    for (my $i = 0; $i < @values; $i++)
+    {
+      if (!defined ($values[$i]))
+      {
+       $previous_values->[$i] = undef;
+      }
+      elsif (!defined ($previous_values->[$i]))
+      {
+       $previous_values->[$i] = $values[$i];
+      }
+      elsif (!defined ($previous_differences->[$i]))
+      {
+       $previous_differences->[$i] = abs ($previous_values->[$i] - $values[$i]);
+      }
+      else
+      {
+       my $divisor = ($previous_differences->[$i] < 1.0) ? 1.0 : $previous_differences->[$i];
+       my $difference = abs ($previous_values->[$i] - $values[$i]);
+       my $change = $pdp_per_row * $difference / $divisor;
+       if (($divisor > 10.0) &&  ($change > 10e5))
+       {
+         print STDERR "i = $i; average difference = " . $previous_differences->[$i]. "; current difference = " . $difference. "; change = $change;\n";
+       }
+       $previous_values->[$i] = $values[$i];
+       $previous_differences->[$i] = (0.95 * $previous_differences->[$i]) + (0.05 * $difference);
+      }
+    }
+  }
+
+  post_line ($line, $index + 1);
+}} # handle_line_peak_detect
+
+sub handle_line_output
+{
+       my $line = shift;
+       my $index = shift;
+
+       if (!defined ($fh))
+       {
+               post_line ($line, $index + 1);
+               return;
+       }
+       
+       print $fh $line;
+}} # handle_line_output
+
+#
+# Dispatching logic
+#
+{
+my @handlers = ();
+sub add_handler
+{
+       my $handler = shift;
+
+       die unless (ref ($handler) eq 'CODE');
+       push (@handlers, $handler);
+} # add_handler
+
+sub post_line
+{
+       my $line = shift;
+       my $index = shift;
+
+       if (0)
+       {
+               my $copy = $line;
+               chomp ($copy);
+               print "DEBUG: post_line ($copy, $index);\n";
+       }
+
+       if ($index > $#handlers)
+       {
+               return;
+       }
+       $handlers[$index]->($line, $index);
+}} # post_line
+
+sub handle_fh
+{
+  my $in_fh = shift;
+  my $out_fh = shift;
+
+  set_output ($out_fh);
+
+  if (@$InDS)
+  {
+    add_handler (\&handle_line_dsmap);
+  }
+
+  if ($Step)
+  {
+    add_handler (\&handle_line_step);
+  }
+
+  if (($Scale != 1.0) || ($Shift != 0.0))
+  {
+    add_handler (\&handle_line_scale_shift);
+  }
+
+  #add_handler (\&handle_line_peak_detect);
+
+  if (@$NewDSes)
+  {
+    add_handler (\&handle_line_add_ds);
+  }
+
+  if (@$NewRRAs)
+  {
+    add_handler (\&handle_line_add_rra);
+  }
+
+  add_handler (\&handle_line_output);
+
+  while (my $line = <$in_fh>)
+  {
+    post_line ($line, 0);
+  }
+} # handle_fh
+
+sub main
+{
+       my $in_file = shift;
+       my $out_file = shift;
+
+       my $in_fh;
+       my $out_fh;
+
+       my $in_needs_close = 1;
+       my $out_needs_close = 1;
+
+       if ($in_file =~ m/\.rrd$/i)
+       {
+               open ($in_fh,  '-|', 'rrdtool', 'dump', $in_file) or die ("open (rrdtool): $!");
+       }
+       elsif ($in_file eq '-')
+       {
+               $in_fh = \*STDIN;
+               $in_needs_close = 0;
+       }
+       else
+       {
+               open ($in_fh, '<', $in_file) or die ("open ($in_file): $!");
+       }
+
+       if ($out_file =~ m/\.rrd$/i)
+       {
+               open ($out_fh, '|-', 'rrdtool', 'restore', '-', $out_file) or die ("open (rrdtool): $!");
+       }
+       elsif ($out_file eq '-')
+       {
+               $out_fh = \*STDOUT;
+               $out_needs_close = 0;
+       }
+       else
+       {
+               open ($out_fh, '>', $out_file) or die ("open ($out_file): $!");
+       }
+
+       handle_fh ($in_fh, $out_fh);
+
+       if ($in_needs_close)
+       {
+               close ($in_fh);
+       }
+       if ($out_needs_close)
+       {
+               close ($out_fh);
+       }
+} # main
+
+=head1 LICENSE
+
+This script is licensed under the GNU general public license, versionE<nbsp>2
+(GPLv2).
+
+=head1 AUTHOR
+
+Florian octo Forster E<lt>octo at verplant.orgE<gt>
+
index d305d85..257cadc 100644 (file)
@@ -10,27 +10,31 @@ if COMPILER_IS_GCC
 AM_CFLAGS = -Wall -Werror
 endif
 
-sbin_PROGRAMS = collectd
+AM_CPPFLAGS = -DPREFIX='"${prefix}"'
+AM_CPPFLAGS += -DCONFIGFILE='"${sysconfdir}/${PACKAGE_NAME}.conf"'
+AM_CPPFLAGS += -DLOCALSTATEDIR='"${localstatedir}"'
+AM_CPPFLAGS += -DPKGLOCALSTATEDIR='"${localstatedir}/lib/${PACKAGE_NAME}"'
+if BUILD_FEATURE_DAEMON
+AM_CPPFLAGS += -DPIDFILE='"${localstatedir}/run/${PACKAGE_NAME}.pid"'
+endif
+AM_CPPFLAGS += -DPLUGINDIR='"${pkglibdir}"'
+
+sbin_PROGRAMS = collectd collectdmon
 bin_PROGRAMS = collectd-nagios
 
 collectd_SOURCES = collectd.c collectd.h \
-                  utils_avltree.c utils_avltree.h \
-                  utils_mount.c utils_mount.h \
-                  utils_llist.c utils_llist.h \
-                  utils_ignorelist.c utils_ignorelist.h \
                   common.c common.h \
-                  plugin.c plugin.h \
                   configfile.c configfile.h \
+                  plugin.c plugin.h \
+                  utils_avltree.c utils_avltree.h \
+                  utils_cache.c utils_cache.h \
+                  utils_ignorelist.c utils_ignorelist.h \
+                  utils_llist.c utils_llist.h \
+                  utils_mount.c utils_mount.h \
+                  utils_threshold.c utils_threshold.h \
                   types_list.c types_list.h
 collectd_CPPFLAGS = $(LTDLINCL)
-collectd_CPPFLAGS += -DPREFIX='"${prefix}"'
-collectd_CPPFLAGS += -DCONFIGFILE='"${sysconfdir}/${PACKAGE_NAME}.conf"'
-collectd_CPPFLAGS += -DLOCALSTATEDIR='"${localstatedir}"'
-collectd_CPPFLAGS += -DPKGLOCALSTATEDIR='"${localstatedir}/lib/${PACKAGE_NAME}"'
-if BUILD_FEATURE_DAEMON
-collectd_CPPFLAGS += -DPIDFILE='"${localstatedir}/run/${PACKAGE_NAME}.pid"'
-endif
-collectd_CPPFLAGS += -DPLUGINDIR='"${pkglibdir}"'
+collectd_CPPFLAGS += $(AM_CPPFLAGS)
 
 # Link to these libraries..
 collectd_LDFLAGS = -export-dynamic
@@ -73,6 +77,9 @@ else
 collectd_LDFLAGS += -loconfig
 endif
 
+collectdmon_SOURCES = collectdmon.c
+collectdmon_CPPFLAGS = $(AM_CPPFLAGS)
+
 collectd_nagios_SOURCES = collectd-nagios.c
 collectd_nagios_LDFLAGS =
 if BUILD_WITH_LIBSOCKET
@@ -209,7 +216,9 @@ endif
 
 if BUILD_PLUGIN_EXEC
 pkglib_LTLIBRARIES += exec.la
-exec_la_SOURCES = exec.c utils_cmd_putval.c utils_cmd_putval.h
+exec_la_SOURCES = exec.c \
+                 utils_cmd_putnotif.c utils_cmd_putnotif.h \
+                 utils_cmd_putval.c utils_cmd_putval.h
 exec_la_LDFLAGS = -module -avoid-version
 if BUILD_WITH_LIBPTHREAD
 exec_la_LDFLAGS += -lpthread
@@ -276,6 +285,16 @@ collectd_LDADD += "-dlopen" irq.la
 collectd_DEPENDENCIES += irq.la
 endif
 
+if BUILD_PLUGIN_LIBVIRT
+pkglib_LTLIBRARIES += libvirt.la
+libvirt_la_SOURCES = libvirt.c
+libvirt_la_CFLAGS = $(BUILD_WITH_LIBVIRT_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+libvirt_la_LIBADD = $(BUILD_WITH_LIBVIRT_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+libvirt_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" libvirt.la
+collectd_DEPENDENCIES += libvirt.la
+endif
+
 if BUILD_PLUGIN_LOAD
 pkglib_LTLIBRARIES += load.la
 load_la_SOURCES = load.c
@@ -564,8 +583,7 @@ endif
 
 if BUILD_PLUGIN_UNIXSOCK
 pkglib_LTLIBRARIES += unixsock.la
-unixsock_la_SOURCES = unixsock.c utils_cmd_putval.h utils_cmd_putval.c
-unixsock_la_CPPFLAGS = -DLOCALSTATEDIR='"${localstatedir}"'
+unixsock_la_SOURCES = unixsock.c utils_cmd_putval.h utils_cmd_putval.c utils_cmd_putnotif.h utils_cmd_putnotif.c
 unixsock_la_LDFLAGS = -module -avoid-version -lpthread
 collectd_LDADD += "-dlopen" unixsock.la
 collectd_DEPENDENCIES += unixsock.la
@@ -579,6 +597,16 @@ collectd_LDADD += "-dlopen" users.la
 collectd_DEPENDENCIES += users.la
 endif
 
+if BUILD_PLUGIN_UUID
+pkglib_LTLIBRARIES += uuid.la
+uuid_la_SOURCES = uuid.c
+uuid_la_CFLAGS  = $(BUILD_WITH_LIBHAL_CFLAGS)
+uuid_la_LIBADD  = $(BUILD_WITH_LIBHAL_LIBS)
+uuid_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" uuid.la
+collectd_DEPENDENCIES += uuid.la
+endif
+
 if BUILD_PLUGIN_VSERVER
 pkglib_LTLIBRARIES += vserver.la
 vserver_la_SOURCES = vserver.c
@@ -607,7 +635,8 @@ endif
 
 dist_man_MANS = collectd.1 collectd-nagios.1 collectd.conf.5 \
                collectd-email.5 collectd-exec.5 collectd-perl.5 \
-               collectd-snmp.5 collectd-unixsock.5
+               collectd-snmp.5 collectd-unixsock.5 collectdmon.1 \
+               types.db.5
 
 #collectd_1_SOURCES = collectd.pod
 
@@ -615,7 +644,7 @@ 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
+       collectd.conf.pod collectd.pod collectdmon.pod types.db.pod
 
 .pod.1:
        pod2man --release=$(VERSION) --center=$(PACKAGE) $< >.pod2man.tmp 2>/dev/null && mv -f .pod2man.tmp $@ || true
index 174febe..5a03764 100644 (file)
@@ -112,7 +112,7 @@ static int apcups_shutdown (void)
  * Returns -1 on error
  * Returns socket file descriptor otherwise
  */
-static int net_open (char *host, char *service, int port)
+static int net_open (char *host, int port)
 {
        int              sd;
        int              status;
@@ -262,8 +262,6 @@ static int apc_query_server (char *host, int port,
        char   *key;
        double  value;
 
-       static complain_t compl;
-
 #if APCMAIN
 # define PRINT_VALUE(name, val) printf("  Found property: name = %s; value = %f;\n", name, val)
 #else
@@ -272,17 +270,13 @@ static int apc_query_server (char *host, int port,
 
        if (global_sockfd < 0)
        {
-               if ((global_sockfd = net_open (host, NULL, port)) < 0)
+               global_sockfd = net_open (host, port);
+               if (global_sockfd < 0)
                {
-                       plugin_complain (LOG_ERR, &compl, "apcups plugin: "
-                                       "Connecting to the apcupsd failed.");
+                       ERROR ("apcups plugin: Connecting to the "
+                                       "apcupsd failed.");
                        return (-1);
                }
-               else
-               {
-                       plugin_relief (LOG_NOTICE, &compl, "apcups plugin: "
-                                       "Connection re-established to the apcupsd.");
-               }
        }
 
        if (net_send (&global_sockfd, "status", 6) < 0)
@@ -293,7 +287,7 @@ static int apc_query_server (char *host, int port,
 
        while ((n = net_recv (&global_sockfd, recvline, sizeof (recvline) - 1)) > 0)
        {
-               assert (n < sizeof (recvline));
+               assert ((unsigned int)n < sizeof (recvline));
                recvline[n] = '\0';
 #if APCMAIN
                printf ("net_recv = `%s';\n", recvline);
index 2e27e60..345f606 100644 (file)
@@ -77,7 +77,7 @@ static int battery_init (void)
        {
                len = snprintf (filename, sizeof (filename), battery_pmu_file, battery_pmu_num);
 
-               if ((len >= sizeof (filename)) || (len < 0))
+               if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
                        break;
 
                if (access (filename, R_OK))
@@ -360,11 +360,11 @@ static int battery_read (void)
                double *valptr = NULL;
 
                len = snprintf (filename, sizeof (filename), battery_pmu_file, i);
-               if ((len >= sizeof (filename)) || (len < 0))
+               if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
                        continue;
 
                len = snprintf (batnum_str, sizeof (batnum_str), "%i", i);
-               if ((len >= sizeof (batnum_str)) || (len < 0))
+               if ((len < 0) || ((unsigned int)len >= sizeof (batnum_str)))
                        continue;
 
                if ((fh = fopen (filename, "r")) == NULL)
@@ -438,7 +438,7 @@ static int battery_read (void)
                        len = snprintf (filename, sizeof (filename),
                                        "/proc/acpi/battery/%s/state",
                                        ent->d_name);
-                       if ((len >= sizeof (filename)) || (len < 0))
+                       if ((len < 0) || ((unsigned int)len >= sizeof (filename)))
                                continue;
 
                        if ((fh = fopen (filename, "r")) == NULL)
index 4102136..9882601 100644 (file)
@@ -9,35 +9,69 @@ collectd-exec - Documentation of collectd's C<exec plugin>
   # ...
   <Plugin exec>
     Exec "myuser:mygroup" "myprog"
-    Exec "otheruser" "/path/to/another/binary"
+    Exec "otheruser" "/path/to/another/binary" "arg0" "arg1"
+    NotificationExec "user" "/usr/lib/collectd/exec/handle_notification"
   </Plugin>
 
 =head1 DESCRIPTION
 
-The C<exec plugin> forks of an executable and reads back values that it writes
-to C<STDOUT>. The executable is forked in a fashion similar to L<init>: It is
-forked once and not again until it exits. If it exited, it will be forked again
-after at most I<Interval> seconds. It is perfectly legal for the executable to
-run for a long time and continuously write values to C<STDOUT>.
+The C<exec plugin> forks of an executable either to receive values or to
+dispatch notifications to the outside world. The syntax of the configuration is
+explained in L<collectd.conf(5)> but summarized in the above synopsis.
 
 If you want/need better performance or more functionality you should take a
 long look at the C<perl plugin>, L<collectd-perl(5)>.
 
-=head1 DATA FORMAT
+=head1 EXECUTABLE TYPES
+
+There are currently two types of executables that can be executed by the
+C<exec plugin>:
+
+=over 4
+
+=item C<Exec>
+
+These programs are forked and values that it writes to C<STDOUT> are read back.
+The executable is forked in a fashion similar to L<init>: It is forked once and
+not again until it exits. If it exited, it will be forked again after at most
+I<Interval> seconds. It is perfectly legal for the executable to run for a long
+time and continuously write values to C<STDOUT>.
+
+See L<EXEC DATA FORMAT> below for a description of the output format expected
+from these programs.
+
+B<Warning:> If the executable only writes one value and then exits I will be
+executed every I<Interval> seconds. If I<Interval> is short (the default is 10
+seconds) this may result in serious system load.
+
+=item C<NotificationExec>
+
+The program is forked once for each notification that is handled by the daemon.
+The notification is passed to the program on C<STDIN> in a fashion similar to
+HTTP-headers. In contrast to programs specified with C<Exec> the execution of
+this program is not serialized, so that several instances of this program may
+run at once if multiple notifications are received.
+
+See L<NOTIFICATION DATA FORMAT> below for a description of the data passed to
+these programs.
+
+=back
+
+=head1 EXEC DATA FORMAT
 
 The forked executable is expected to print values to C<STDOUT>. The expected
 format is as follows:
 
 =over 4
 
-=item
+=item Comments
 
 Each line beginning with a C<#> (hash mark) is ignored.
 
-=item
+=item B<PUTVAL> I<Identifier> [I<OptionList>] I<Valuelist>
 
-Other lines must consist of an I<Identifier>, an optional I<Option-List> and a
-I<Value-List>, separated by a spaces. A description of these two parts follows:
+Submits one or more values (identified by I<Identifier>, see below) to the
+daemon which will dispatch it to all it's write-plugins.
 
 An I<Identifier> is of the form
 C<I<host>B</>I<plugin>B<->I<instance>B</>I<type>B<->I<instance>> with both
@@ -46,7 +80,8 @@ omitted, too. I<plugin> and each I<instance>-part may be chosen freely as long
 as the tuple (plugin, plugin instance, type instance) uniquely identifies the
 plugin within collectd. I<type> identifies the type and number of values
 (i.E<nbsp>e. data-set) passed to collectd. A large list of predefined
-data-sets is available in the B<types.db> file.
+data-sets is available in the B<types.db> file. See L<types.db(5)> for a
+description of the format of this file.
 
 The I<OptionList> is an optional list of I<Options>, where each option if a
 key-value-pair. A list of currently understood options can be found below, all
@@ -83,25 +118,141 @@ Since examples usually let one understand a lot better, here are some:
   leeloo/cpu-0/cpu-idle N:2299366
   alice/interface/if_octets-eth0 interval=10 1180647081:421465:479194
 
+Since this action was the only one supported with older versions of the C<exec
+plugin> all lines were treated as if they were prefixed with B<PUTVAL>. This is
+still the case to maintain backwards compatibility but deprecated.
+
+=item B<PUTNOTIF> [I<OptionList>] B<message=>I<Message>
+
+Submits a notification to the daemon which will then dispatch it to all plugins
+which have registered for receiving notifications. 
+
+The B<PUTNOTIF> if followed by a list of options which further describe the
+notification. The B<message> option is special in that it will consume the rest
+of the line as its value. The B<message>, B<severity>, and B<time> options are
+mandatory.
+
+Valid options are:
+
+=over 4
+
+=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.
+
+=item B<severity=failure>|B<warning>|B<okay> (B<REQUIRED>)
+
+Sets the severity of the notification. This option is mandatory.
+
+=item B<time=>I<Time> (B<REQUIRED>)
+
+Sets the time of the notification. The time is given as "epoch", i.E<nbsp>e. as
+seconds since January 1st, 1970, 00:00:00. This option is mandatory.
+
+=item B<host=>I<Hostname>
+
+=item B<plugin=>I<Plugin>
+
+=item B<plugin_instance=>I<Plugin-Instance>
+
+=item B<type=>I<Type>
+
+=item B<type_instance=>I<Type-Instance>
+
+These "associative" options establish a relation between this notification and
+collected performance data. This connection is purely informal, i.E<nbsp>e. the
+daemon itself doesn't do anything with this information. However, websites or
+GUIs may use this information to place notifications near the affected graph or
+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
 
 When collectd exits it sends a B<SIGTERM> to all still running
 child-processes upon which they have to quit.
 
-=head1 CAVEATS
+=head1 NOTIFICATION DATA FORMAT
+
+The notification executables receive values rather than providing them. In
+fact, after the program is started C<STDOUT> is connected to C</dev/null>.
+
+The data is passed to the executables over C<STDIN> in a format very similar to
+HTTP: At first there is a "header" with one line per field. Every line consists
+of a field name, ended by a colon, and the associated value until end-of-line.
+The "header" is ended by two newlines immediately following another,
+i.E<nbsp>e. an empty line. The rest, basically the "body", is the message of
+the notification.
+
+The following is an example notification passed to a program:
+
+  Severity: FAILURE
+  Time: 1200928930
+  Host: myhost.mydomain.org
+  \n
+  This is a test notification to demonstrate the format
+
+The following header files are currently used. Please note, however, that you
+should ignore unknown header files to be as forward-compatible as possible.
 
 =over 4
 
-=item
+=item B<Severity>
+
+Severity of the notification. May either be B<FAILURE>, B<WARNING>, or B<OKAY>.
+
+=item B<Time>
+
+The time in epoch, i.E<nbsp>e. as seconds since 1970-01-01 00:00:00 UTC.
+
+=item B<Host>
 
-If the executable only writes one value and then exits I will be executed every
-I<Interval> seconds. If I<Interval> is short (the default is 10 seconds) this
-may result in serious system load.
+=item B<Plugin>
+
+=item B<PluginInstance>
+
+=item B<Type>
+
+=item B<TypeInstance>
+
+Identification of the performance data this notification is associated with.
+All of these fields are optional because notifications do not B<need> to be
+associated with a certain value.
+
+=back
+
+=head1 USING NAGIOS PLUGINS
+
+Though the interface is far from perfect, there are tons of plugins for Nagios.
+You can use these plugins with collectd by using a simple transition layer,
+C<exec-nagios.px>, which is shipped with the collectd distribution in the
+C<contrib/> directory. It is a simple Perl script that comes with embedded
+documentation. To see it, run the following command:
+
+  perldoc exec-nagios.px
+
+This script expects a configuration file, C<exec-nagios.conf>. You can find an
+example in the C<contrib/> directory, too.
+
+Even a simple mechanism to submit "performance data" to collectd is
+implemented. If you need a more sophisticated setup, please rewrite the plugin
+to make use of collectd's more powerful interface.
+
+=head1 CAVEATS
+
+=over 4
 
 =item
 
 The user, the binary is executed as, may not have root privileges, i.E<nbsp>e.
-must have an UID that is non-zero.
+must have an UID that is non-zero. This is for your own good.
 
 =back
 
index 9707fa3..4a01d14 100644 (file)
@@ -21,9 +21,6 @@ for collectd in Perl. This is a lot more efficient than executing a
 Perl-script every time you want to read a value with the C<exec plugin> (see
 L<collectd-exec(5)>) and provides a lot more functionality, too.
 
-Please note that this is still considered to be experimental and subject to
-change between minor releases.
-
 =head1 CONFIGURATION
 
 =over 4
@@ -50,6 +47,10 @@ using the B<-d:Package> command line option.
 
 See L<perldebug> for detailed documentation about debugging Perl.
 
+This option does not prevent collectd from daemonizing, so you should start
+collectd with the B<-f> command line option. Else you will not be able to use
+the command line driven interface of the debugger.
+
 =item B<IncludeDir> I<Dir>
 
 Adds I<Dir> to the B<@INC> array. This is the same as using the B<-IDir>
@@ -96,6 +97,15 @@ once for each call to B<plugin_dispatch_values>.
 This type of function is used to pass messages of plugins or the daemon itself
 to the user.
 
+=item notification function
+
+This type of function is used to act upon notifications. In general, a
+notification is a status message that may be associated with a data instance.
+Usually, a notification is generated by the daemon if a configured threshold
+has been exceeded (see the section "THRESHOLD CONFIGURATION" in
+L<collectd.conf(5)> for more details), but any plugin may dispatch
+notifications as well.
+
 =item shutdown functions
 
 This type of function is called once before the daemon shuts down. It should
@@ -103,6 +113,10 @@ be used to clean up the plugin (e.g. close sockets, ...).
 
 =back
 
+Any function (except log functions) may set the B<$@> variable to describe
+errors in more detail. The message will be passed on to the user using
+collectd's logging mechanism.
+
 See the documentation of the B<plugin_register> method in the section
 "METHODS" below for the number and types of arguments passed to each
 B<callback function>. This section also explains how to register B<callback
@@ -128,7 +142,7 @@ structure. The general layout looks like this:
 
   [{
     name => 'data_source_name',
-    type => DS_TYPE_COUNTER || DS_TYPE_GAUGE
+    type => DS_TYPE_COUNTER || DS_TYPE_GAUGE,
     min  => value || undef,
     max  => value || undef
   }, ...]
@@ -144,12 +158,28 @@ layout looks like this:
   {
     values => [123, 0.5],
     time   => time (),
-    host   => 'localhost',
+    host   => $hostname_g,
     plugin => 'myplugin',
     plugin_instance => '',
     type_instance   => ''
   }
 
+=item Notification
+
+A notification is one structure defining the severity, time and message of the
+status message as well as an identification of a data instance:
+
+  {
+    severity => NOTIF_FAILURE || NOTIF_WARNING || NOTIF_OKAY,
+    time     => time (),
+    message  => 'status message',
+    host     => $hostname_g,
+    plugin   => 'myplugin',
+    type     => 'mytype',
+    plugin_instance => '',
+    type_instance   => ''
+  }
+
 =back
 
 =head1 METHODS
@@ -175,6 +205,8 @@ I<type> can be one of:
 
 =item TYPE_LOG
 
+=item TYPE_NOTIF
+
 =item TYPE_SHUTDOWN
 
 =item TYPE_DATASET
@@ -186,16 +218,23 @@ depending on the value of I<type>. (Please note that the type of the data-set
 is the value passed as I<name> here and has nothing to do with the I<type>
 argument which simply tells B<plugin_register> what is being registered.)
 
-The last argument, I<data>, is either a function- or an array-reference. If
-I<type> is B<TYPE_DATASET>, then the I<data> argument must be an
+The last argument, I<data>, is either a function name or an array-reference.
+If I<type> is B<TYPE_DATASET>, then the I<data> argument must be an
 array-reference which points to an array of hashes. Each hash describes one
-data-source. For the exact layout see B<Data-Set> above. Please note that
+data-set. For the exact layout see B<Data-Set> above. Please note that
 there is a large number of predefined data-sets available in the B<types.db>
-file which are automatically registered with collectd.
+file which are automatically registered with collectd - see L<types.db(5)> for
+a description of the format of this file.
 
 If the I<type> argument is any of the other types (B<TYPE_INIT>, B<TYPE_READ>,
-...) then I<data> is expected to be a function reference. These functions are
-called in the various stages of the daemon and are passed the following
+...) then I<data> is expected to be a function name. If the name is not
+prefixed with the plugin's package name collectd will add it automatically.
+The interface slightly differs from the C interface (which expects a function
+pointer instead) because Perl does not support to share references to
+subroutines between threads.
+
+These functions are called in the various stages of the daemon (see the
+section "WRITING YOUR OWN PLUGINS" above) and are passed the following
 arguments:
 
 =over 4
@@ -221,6 +260,11 @@ level is B<LOG_DEBUG>, the most important level is B<LOG_ERR>. In between there
 are (from least to most important): B<LOG_INFO>, B<LOG_NOTICE>, and
 B<LOG_WARNING>. I<message> is simply a string B<without> a newline at the end.
 
+=item TYPE_NOTIF
+
+The only argument passed is I<notification>. See above for the layout of this
+data type.
+
 =back
 
 =item B<plugin_unregister> (I<type>, I<plugin>)
@@ -235,6 +279,11 @@ is found (and the number of values matches the number of data-sources) then the
 type, data-set and value-list is passed to all write-callbacks that are
 registered with the daemon.
 
+=item B<plugin_dispatch_notification> (I<notification>)
+
+Submits a I<notification> to the daemon which will then pass it to all
+notification-callbacks that are registered.
+
 =item B<plugin_log> (I<log-level>, I<message>)
 
 Submits a I<message> of level I<log-level> to collectd's logging mechanism.
@@ -247,6 +296,25 @@ B<LOG_NOTICE>, B<LOG_INFO> and B<LOG_DEBUG> respectively as I<log-level>.
 
 =back
 
+=head1 GLOBAL VARIABLES
+
+=over 4
+
+=item B<$hostname_g>
+
+As the name suggests this variable keeps the hostname of the system collectd
+is running on. The value might be influenced by the B<Hostname> or
+B<FQDNLookup> configuration options (see L<collectd.conf(5)> for details).
+
+=item B<$interval_g>
+
+This variable keeps the interval in seconds in which the read functions are
+queried (see the B<Interval> configuration option).
+
+=back
+
+Any changes to these variables will be globally visible in collectd.
+
 =head1 EXPORTS
 
 By default no symbols are exported. However, the following export tags are
@@ -264,6 +332,8 @@ available (B<:all> will export all of them):
 
 =item B<plugin_dispatch_values> ()
 
+=item B<plugin_dispatch_notification> ()
+
 =item B<plugin_log> ()
 
 =back
@@ -320,6 +390,28 @@ available (B<:all> will export all of them):
 
 =back
 
+=item B<:notif>
+
+=over 4
+
+=item B<NOTIF_FAILURE>
+
+=item B<NOTIF_WARNING>
+
+=item B<NOTIF_OKAY>
+
+=back
+
+=item B<:globals>
+
+=over 4
+
+=item B<$hostname_g>
+
+=item B<$interval_g>
+
+=back
+
 =back
 
 =head1 EXAMPLES
@@ -356,25 +448,71 @@ A very simple write function will look like:
 
 To register those functions with collectd:
 
-  plugin_register (TYPE_READ, "foobar", \&foobar_read);
-  plugin_register (TYPE_WRITE, "foobar", \&foobar_write);
+  plugin_register (TYPE_READ, "foobar", "foobar_read");
+  plugin_register (TYPE_WRITE, "foobar", "foobar_write");
 
 See the section "DATA TYPES" above for a complete documentation of the data
 types used by the read and write functions.
 
-=head1 BUGS
+=head1 NOTES
+
+=over 4
+
+=item
 
-This plugin does not yet work correctly if collectd uses multiple threads.
-Perl does not allow multiple threads to access a single interpreter at the
-same time. As a temporary workaround you should use a single read thread only
-(see collectd's B<ReadThread> configuration option).
+Please feel free to send in new plugins to collectd's mailinglist at
+E<lt>collectdE<nbsp>atE<nbsp>verplant.orgE<gt> for review and, possibly,
+inclusion in the main distribution. In the latter case, we will take care of
+keeping the plugin up to date and adapting it to new versions of collectd.
+
+Before submitting your plugin, please take a look at
+L<http://collectd.org/dev-info.shtml>.
+
+=back
+
+=head1 CAVEATS
+
+=over 4
+
+=item
+
+collectd is heavily multi-threaded. Each collectd thread accessing the perl
+plugin will be mapped to a Perl interpreter thread (see L<threads(3perl)>).
+Any such thread will be created and destroyed transparently and on-the-fly.
+
+Hence, any plugin has to be thread-safe if it provides several entry points
+from collectd (i.E<nbsp>e. if it registers more than one callback or if a
+registered callback may be called more than once in parallel). Please note
+that no data is shared between threads by default. You have to use the
+B<threads::shared> module to do so.
+
+=item
+
+Each function name registered with collectd has to be available before the
+first thread has been created (i.E<nbsp>e. basically at compile time). This
+basically means that hacks (yes, I really consider this to be a hack) like
+C<*foo = \&bar; plugin_register (TYPE_READ, "plugin", "foo");> most likely
+will not work. This is due to the fact that the symbol table is not shared
+across different threads.
+
+=item
+
+Each plugin is usually only loaded once and kept in memory for performance
+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.
+
+=back
 
 =head1 SEE ALSO
 
 L<collectd(1)>,
 L<collectd.conf(5)>,
 L<collectd-exec(5)>,
+L<types.db(5)>,
 L<perl(1)>,
+L<threads(3perl)>,
+L<threads::shared(3perl)>,
 L<perldebug(1)>
 
 =head1 AUTHOR
index ea49029..d0acc06 100644 (file)
@@ -85,7 +85,7 @@ queried. The following options can be set:
 collectd's type that is to be used, e.E<nbsp>g. "if_octets" for interface
 traffic or "users" for a user count. The types are read from the B<TypesDB>
 (see L<collectd.conf(5)>), so you may want to check for which types are
-defined.
+defined. See L<types.db(5)> for a description of the format of this file.
 
 =item B<Table> I<true|false>
 
index d82f9cb..3ef2438 100644 (file)
@@ -116,6 +116,64 @@ Example:
   -> | PUTVAL testhost/interface/if_octets-test0 interval=10 1179574444:123:456
   <- | 0 Success
 
+=item B<PUTNOTIF> [I<OptionList>] B<message=>I<Message>
+
+Submits a notification to the daemon which will then dispatch it to all plugins
+which have registered for receiving notifications. 
+
+The B<PUTNOTIF> if followed by a list of options which further describe the
+notification. The B<message> option is special in that it will consume the rest
+of the line as its value. The B<message>, B<severity>, and B<time> options are
+mandatory.
+
+Valid options are:
+
+=over 4
+
+=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.
+
+=item B<severity=failure>|B<warning>|B<okay> (B<REQUIRED>)
+
+Sets the severity of the notification. This option is mandatory.
+
+=item B<time=>I<Time> (B<REQUIRED>)
+
+Sets the time of the notification. The time is given as "epoch", i.E<nbsp>e. as
+seconds since January 1st, 1970, 00:00:00. This option is mandatory.
+
+=item B<host=>I<Hostname>
+
+=item B<plugin=>I<Plugin>
+
+=item B<plugin_instance=>I<Plugin-Instance>
+
+=item B<type=>I<Type>
+
+=item B<type_instance=>I<Type-Instance>
+
+These "associative" options establish a relation between this notification and
+collected performance data. This connection is purely informal, i.E<nbsp>e. the
+daemon itself doesn't do anything with this information. However, websites or
+GUIs may use this information to place notifications near the affected graph or
+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<exec plugin>, see
+L<collectd-exec(5)>.
+
+=back
+
+Example:
+  -> | PUTNOTIF type=temperature severity=warning time=1201094702 message=The roof is on fire!
+  <- | 0 Success
+
 =back
 
 =head2 Identifiers
index 4e18fd6..984ff75 100644 (file)
 #include "collectd.h"
 #include "common.h"
 
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
 #include "plugin.h"
 #include "configfile.h"
-#include "types_list.h"
 
 /*
  * Global variables
@@ -48,25 +51,67 @@ static void sigTermHandler (int signal)
        loop++;
 }
 
-static int init_global_variables (void)
+static int init_hostname (void)
 {
        const char *str;
 
+       struct addrinfo  ai_hints;
+       struct addrinfo *ai_list;
+       struct addrinfo *ai_ptr;
+       int status;
+
        str = global_option_get ("Hostname");
        if (str != NULL)
        {
                strncpy (hostname_g, str, sizeof (hostname_g));
+               hostname_g[sizeof (hostname_g) - 1] = '\0';
+               return (0);
        }
-       else
+
+       if (gethostname (hostname_g, sizeof (hostname_g)) != 0)
        {
-               if (gethostname (hostname_g, sizeof (hostname_g)) != 0)
-               {
-                       fprintf (stderr, "`gethostname' failed and no "
-                                       "hostname was configured.\n");
-                       return (-1);
-               }
+               fprintf (stderr, "`gethostname' failed and no "
+                               "hostname was configured.\n");
+               return (-1);
+       }
+
+       str = global_option_get ("FQDNLookup");
+       if ((strcasecmp ("false", str) == 0)
+                       || (strcasecmp ("no", str) == 0)
+                       || (strcasecmp ("off", str) == 0))
+               return (0);
+
+       memset (&ai_hints, '\0', sizeof (ai_hints));
+       ai_hints.ai_flags = AI_CANONNAME;
+
+       status = getaddrinfo (hostname_g, NULL, &ai_hints, &ai_list);
+       if (status != 0)
+       {
+               ERROR ("Looking up \"%s\" failed. You have set the "
+                               "\"FQDNLookup\" option, but I cannot resolve "
+                               "my hostname to a fully qualified domain "
+                               "name. Please fix you network "
+                               "configuration.", hostname_g);
+               return (-1);
        }
-       DEBUG ("hostname_g = %s;", hostname_g);
+
+       for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+       {
+               if (ai_ptr->ai_canonname == NULL)
+                       continue;
+
+               strncpy (hostname_g, ai_ptr->ai_canonname, sizeof (hostname_g));
+               hostname_g[sizeof (hostname_g) - 1] = '\0';
+               break;
+       }
+
+       freeaddrinfo (ai_list);
+       return (0);
+} /* int init_hostname */
+
+static int init_global_variables (void)
+{
+       const char *str;
 
        str = global_option_get ("Interval");
        if (str == NULL)
@@ -80,6 +125,10 @@ static int init_global_variables (void)
        }
        DEBUG ("interval_g = %i;", interval_g);
 
+       if (init_hostname () != 0)
+               return (-1);
+       DEBUG ("hostname_g = %s;", hostname_g);
+
        return (0);
 } /* int init_global_variables */
 
@@ -170,7 +219,7 @@ static void update_kstat (void)
 /* TODO
  * Remove all settings but `-f' and `-C'
  */
-static void exit_usage (char *name)
+static void exit_usage (void)
 {
        printf ("Usage: "PACKAGE" [OPTIONS]\n\n"
                        
@@ -214,7 +263,6 @@ static int do_init (void)
        }
 #endif
 
-       read_types_list ();
        plugin_init_all ();
 
        return (0);
@@ -244,7 +292,7 @@ static int do_loop (void)
 #endif
 
                /* Issue all plugins */
-               plugin_read_all (&loop);
+               plugin_read_all ();
 
                if (gettimeofday (&tv_now, NULL) < 0)
                {
@@ -360,7 +408,7 @@ int main (int argc, char **argv)
 #endif /* COLLECT_DAEMON */
                        case 'h':
                        default:
-                               exit_usage (argv[0]);
+                               exit_usage ();
                } /* switch (c) */
        } /* while (1) */
 
index 58de525..e125bb6 100644 (file)
@@ -5,6 +5,7 @@
 #
 
 #Hostname    "localhost"
+FQDNLookup   true
 #BaseDir     "@prefix@/var/lib/@PACKAGE_NAME@"
 #PIDFile     "@prefix@/var/run/@PACKAGE_NAME@.pid"
 #PluginDir   "@prefix@/lib/@PACKAGE_NAME@"
@@ -30,6 +31,7 @@
 @BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
 @BUILD_PLUGIN_IPVS_TRUE@LoadPlugin ipvs
 @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
@@ -56,6 +58,7 @@
 @BUILD_PLUGIN_TCPCONNS_TRUE@LoadPlugin tcpconns
 @BUILD_PLUGIN_UNIXSOCK_TRUE@LoadPlugin unixsock
 @BUILD_PLUGIN_USERS_TRUE@LoadPlugin users
+@BUILD_PLUGIN_UUID_TRUE@LoadPlugin uuid
 @BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver
 @BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless
 @BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
@@ -74,6 +77,7 @@
 
 #<Plugin csv>
 #      DataDir "@prefix@/var/lib/@PACKAGE_NAME@/csv"
+#      StoreRates false
 #</Plugin>
 
 #<Plugin df>
 
 #<Plugin exec>
 #      Exec "user:group" "/path/to/exec"
+#      NotificationExec "/path/to/exec"
 #</Plugin>
 
-#<Plugin hddtemp>
+@BUILD_PLUGIN_HDDTEMP_TRUE@<Plugin hddtemp>
 #      Host "127.0.0.1"
 #      Port "7634"
-#</Plugin>
+@BUILD_PLUGIN_HDDTEMP_TRUE@    TranslateDevicename false
+@BUILD_PLUGIN_HDDTEMP_TRUE@</Plugin>
 
 #<Plugin interface>
 #      Interface "eth0"
 #      IgnoreSelected true
 #</Plugin>
 
+#<Plugin libvirt>
+#      Connection "xen:///"
+#      RefreshInterval 60
+#      Domain "name"
+#      BlockDevice "name:device"
+#      InterfaceDevice "name:device"
+#      IgnoreSelected false
+#      HostnameFormat name
+#</Plugin>
+
 #<Plugin logfile>
 #      LogLevel info
 #      File STDOUT
+#      Timestamp true
 #</Plugin>
 
 #<Plugin mbmon>
 #      QDisc "eth0" "pfifo_fast-1:0"
 #      Class "ppp0" "htb-1:10"
 #      Filter "ppp0" "u32-1:0"
+#      IgnoreSelected false
 #</Plugin>
 
 #<Plugin network>
 #<Plugin ntpd>
 #      Host "localhost"
 #      Port 123
+#      ReverseLookups false
 #</Plugin>
 
 #<Plugin nut>
 #<Plugin perl>
 #      IncludeDir "/my/include/path"
 #      BaseName "Collectd::Plugin"
+#      EnableDebugger ""
 #      LoadPlugin foo
 #</Plugin>
 
 #<Plugin ping>
 #      Host "host.foo.bar"
+#      TTL 255
 #</Plugin>
 
 #<Plugin processes>
 #      SocketGroup "collectd"
 #      SocketPerms "0660"
 #</Plugin>
+
+#<Plugin uuid>
+#      UUIDFile "/etc/uuid"
+#</Plugin>
+
index 72c2430..aa4421d 100644 (file)
@@ -53,11 +53,26 @@ directory for the daemon.
 Loads the plugin I<Plugin>. There must be at least one such line or B<collectd>
 will be mostly useless.
 
-=item B<Include> I<File>
+=item B<Include> I<Path>
 
-Includes the file I<File> as if it was copy and pasted here. To prevent loops
-and shooting yourself in the foot in interesting ways the nesting is limited to
-a depth of 8E<nbsp>levels, which should be sufficient for most uses.
+If I<Path> points to a file, includes that file. If I<Path> points to a
+directory, recursively includes all files within that directory and its
+subdirectories. If the C<wordexp> function is available on your system,
+shell-like wildcards are expanded before files are included. This means you can
+use statements like the following:
+
+  Include "/etc/collectd.d/*.conf"
+
+If more than one files are included by a single B<Include> option, the files
+will be included in lexicographical order (as defined by the C<strcmp>
+function). Thus, you can e.E<nbsp>g. use numbered prefixes to specify the
+order in which the files are loaded.
+
+To prevent loops and shooting yourself in the foot in interesting ways the
+nesting is limited to a depth of 8E<nbsp>levels, which should be sufficient for
+most uses. Since symlinks are followed it is still possible to crash the daemon
+by looping symlinks. In our opinion significant stupidity should result in an
+appropriate amount of pain.
 
 It is no problem to have a block like C<E<lt>Plugin fooE<gt>> in more than one
 file, but you cannot include files from within blocks.
@@ -72,23 +87,41 @@ setting using the B<-P> command-line option.
 
 Path to the plugins (shared objects) of collectd.
 
-=item B<TypesDB> I<File>
+=item B<TypesDB> I<File> [I<File> ...]
 
-Set the file that contains the data-set descriptions.
+Set one or more files that contain the data-set descriptions. See
+L<types.db(5)> for a description of the format of this file.
 
 =item B<Interval> I<Seconds>
 
 Configures the interval in which to query the read plugins. Obviously smaller
-values lead to a higher system load produces by collectd, while higher values
+values lead to a higher system load produced by collectd, while higher values
 lead to more coarse statistics.
 
 =item B<ReadThreads> I<Num>
 
-Number of threads to start for reading plugins. The default value if B<5>, but
+Number of threads to start for reading plugins. The default value is B<5>, but
 you may want to increase this if you have more than five plugins that take a
 long time to read. Mostly those are plugin that do network-IO. Setting this to
 a value higher than the number of plugins you've loaded is totally useless.
 
+=item B<Hostname> I<Name>
+
+Sets the hostname that identifies a host. If you omit this setting, the
+hostname will be determinded using the L<gethostname(2)> system call.
+
+=item B<FQDNLookup> B<true|false>
+
+If B<Hostname> is determined automatically this setting controls whether or not
+the daemon should try to figure out the "fully qualified domain name", FQDN.
+This is done using a lookup of the name returned by C<gethostname>.
+
+Using this feature (i.E<nbsp>e. setting this option to B<true>) is recommended.
+However, to preserve backwards compatibility the default is set to B<false>.
+The sample config file that is installed with C<makeE<nbsp>install> includes a
+line which sets this option, though, so that default installations will have
+this setting enabled.
+
 =back
 
 =head1 PLUGIN OPTIONS
@@ -181,6 +214,12 @@ installed and an "cpu governor" (that's a kernel module) is loaded.
 Set the directory to store CSV-files under. Per default CSV-files are generated
 beneath the daemon's working directory, i.E<nbsp>e. the B<BaseDir>.
 
+=item B<StoreRates> B<true|false>
+
+If set to B<true>, convert counter values to rates. If set to B<false> (the
+default) counter values are stored as is, i.E<nbsp>e. as an increasing integer
+number.
+
 =back
 
 =head2 Plugin C<df>
@@ -261,7 +300,9 @@ output that is expected from it.
 
 =over 4
 
-=item B<Exec> I<User>[:[I<Group>]] I<Executable>
+=item B<Exec> I<User>[:[I<Group>]] I<Executable> [I<E<lt>argE<gt>> [I<E<lt>argE<gt>> ...]]
+
+=item B<NotificationExec> I<User>[:[I<Group>]] I<Executable> [I<E<lt>argE<gt>> [I<E<lt>argE<gt>> ...]]
 
 Execute the executable I<Executable> as user I<User>. If the user name is
 followed by a colon and a group name, the effective group is set to that group.
@@ -274,6 +315,15 @@ superuser privileges. If the daemon is run as an unprivileged user you must
 specify the same user/group here. If the daemon is run with superuser
 privileges, you must supply a non-root user here.
 
+The executable may be followed by optional arguments that are passed to the
+program. Please note that due to the configuration parsing numbers and boolean
+values may be changed. If you want to be absolutely sure that something is
+passed as-is please enclose it in quotes.
+
+The B<Exec> and B<NotificationExec> statements change the semantics of the
+programs executed, i.E<nbsp>e. the data passed to them and the response
+expected from them. This is documented in great detail in L<collectd-exec(5)>.
+
 =back
 
 =head2 Plugin C<hddtemp>
@@ -297,6 +347,13 @@ Hostname to connect to. Defaults to B<127.0.0.1>.
 
 TCP-Port to connect to. Defaults to B<7634>.
 
+=item B<TranslateDevicename> I<true>|I<false>
+
+If enabled, translate the disk names to major/minor device numbers
+(e.E<nbsp>g. "8-0" for /dev/sda). For backwards compatibility this defaults to
+I<true> but it's recommended to disable it as it will probably be removed in
+the next major version.
+
 =back
 
 =head2 Plugin C<interface>
@@ -361,6 +418,83 @@ and all other interrupts are collected.
 
 =back
 
+=head2 Plugin C<libvirt>
+
+This plugin allows CPU, disk and network load to be collected for virtualized
+guests on the machine. This means that these characteristics can be collected
+for guest systems without installing any software on them - collectd only runs
+on the hosting system. The statistics are collected through libvirt
+(L<http://libvirt.org/>).
+
+Only I<Connection> is required.
+
+=over 4
+
+=item B<Connection> I<uri>
+
+Connect to the hypervisor given by I<uri>. For example if using Xen use:
+
+ Connection "xen:///"
+
+Details which URIs allowed are given at L<http://libvirt.org/uri.html>.
+
+=item B<RefreshInterval> I<seconds>
+
+Refresh the list of domains and devices every I<seconds>. The default is 60
+seconds. Setting this to be the same or smaller than the I<Interval> will cause
+the list of domains and devices to be refreshed on every iteration.
+
+Refreshing the devices in particular is quite a costly operation, so if your
+virtualization setup is static you might consider increasing this. If this
+option is set to 0, refreshing is disabled completely.
+
+=item B<Domain> I<name>
+
+=item B<BlockDevice> I<name:dev>
+
+=item B<InterfaceDevice> I<name:dev>
+
+=item B<IgnoreSelected> I<true>|I<false>
+
+Select which domains and devices are collected.
+
+If I<IgnoreSelected> is not given or I<false> then only the listed domains and
+disk/network devices are collected.
+
+If I<IgnoreSelected> is I<true> then the test is reversed and the listed
+domains and disk/network devices are ignored, while the rest are collected.
+
+The domain name and device names may use a regular expression, if the name is
+surrounded by I</.../> and collectd was compiled with support for regexps.
+
+The default is to collect statistics for all domains and all their devices.
+
+Example:
+
+ BlockDevice "/:hdb/"
+ IgnoreSelected "true"
+
+Ignore all I<hdb> devices on any domain, but other block devices (eg. I<hda>)
+will be collected.
+
+=item B<HostnameFormat> B<name|uuid|hostname|...>
+
+When the libvirt plugin logs data, it sets the hostname of the collected data
+according to this setting. The default is to use the guest name as provided by
+the hypervisor, which is equal to setting B<name>.
+
+B<uuid> means use the guest's UUID. This is useful if you want to track the
+same guest across migrations.
+
+B<hostname> means to use the global B<Hostname> setting, which is probably not
+useful on its own because all guests will appear to have the same name.
+
+You can also specify combinations of these fields. For example B<name uuid>
+means to concatenate the guest name and UUID (with a literal colon character
+between, thus I<"foo:1234-1234-1234-1234">).
+
+=back
+
 =head2 Plugin C<logfile>
 
 =over 4
@@ -390,7 +524,7 @@ Prefix all lines printed by the current time. Defaults to B<true>.
 
 The C<mbmon plugin> uses mbmon to retrieve temperature, voltage, etc.
 
-Be default collectd connects to B<localhost> (127.0.0.1), port B<411/tcp>.  The
+Be default collectd connects to B<localhost> (127.0.0.1), port B<411/tcp>. The
 B<Host> and B<Port> options can be used to change these values, see below.
 C<mbmon> has to be running to work correctly. If C<mbmon> is not running
 timeouts may appear which may interfere with other statistics..
@@ -507,7 +641,7 @@ QDiscs and classes are identified by their type and handle (or classid).
 Filters don't necessarily have a handle, therefore the parent's handle is used.
 The notation used in collectd differs from that used in tc(1) in that it
 doesn't skip the major or minor number if it's zero and doesn't print special
-ids by their name.  So, for example, a qdisc may be identified by
+ids by their name. So, for example, a qdisc may be identified by
 C<pfifo_fast-1:0> even though the minor number of B<all> qdiscs is zero and
 thus not displayed by tc(1).
 
@@ -645,6 +779,13 @@ Hostname of the host running B<ntpd>. Defaults to B<localhost>.
 
 UDP-Port to connect to. Defaults to B<123>.
 
+=item B<ReverseLookups> B<true>|B<false>
+
+Sets wether or not to perform reverse lookups on peers. Since the name or
+IP-address may be used in a filename it is recommended to disable reverse
+lookups. The default is to do reverse lookups to preserve backwards
+compatibility, though.
+
 =back
 
 =head2 Plugin C<nut>
@@ -881,6 +1022,47 @@ L<chmod(1)>. Defaults to B<0770>.
 
 =back
 
+=head2 Plugin C<uuid>
+
+This plugin, if loaded, causes the Hostname to be taken from the machine's
+UUID. The UUID is a universally unique designation for the machine, usually
+taken from the machine's BIOS. This is most useful if the machine is running in
+a virtual environment such as Xen, in which case the UUID is preserved across
+shutdowns and migration.
+
+The following methods are used to find the machine's UUID, in order:
+
+=over 4
+
+=item
+
+Check I</etc/uuid> (or I<UUIDFile>).
+
+=item
+
+Check for UUID from HAL (L<http://www.freedesktop.org/wiki/Software/hal>) if
+present.
+
+=item
+
+Check for UUID from C<dmidecode> / SMBIOS.
+
+=item
+
+Check for UUID from Xen hypervisor.
+
+=back
+
+If no UUID can be found then the hostname is not modified.
+
+=over 4
+
+=item B<UUIDFile> I<Path>
+
+Take the UUID from the given file (default I</etc/uuid>).
+
+=back
+
 =head2 Plugin C<vserver>
 
 This plugin doesn't have any options. B<VServer> support is only available for
@@ -891,12 +1073,123 @@ the F</proc/virtual> filesystem that is required by this plugin.
 
 The B<VServer> homepage can be found at L<http://linux-vserver.org/>.
 
+=head1 THRESHOLD CONFIGURATION
+
+Starting with version C<4.3.0> collectd has support for B<monitoring>. By that
+we mean that the values are not only stored or sent somewhere, but that they
+are judged and, if a problem is recognized, acted upon. The only action
+collectd takes itself is to generate and dispatch a "notification". Plugins can
+register to receive notifications and perform appropriate further actions.
+
+Since systems and what you expect them to do differ a lot, you can configure
+B<thresholds> for your values freely. This gives you a lot of flexibility but
+also a lot of responsibility.
+
+Every time a value is out of range a notification is dispatched. This means
+that the idle percentage of your CPU needs to be less then the configured
+threshold only once for a notification to be generated. There's no such thing
+as a moving average or similar - at least not now.
+
+Also, all values that match a threshold are considered to be relevant or
+"interesting". As a consequence collectd will issue a notification if they are
+not received for twice the last timeout of the values. If, for example, some
+hosts sends it's CPU statistics to the server every 60 seconds, a notification
+will be dispatched after about 120 seconds. It may take a little longer because
+the timeout is checked only once each B<Interval> on the server.
+
+Here is a configuration example to get you started. Read below for more
+information.
+
+ <Threshold>
+   <Type "foo">
+     WarningMin    0.00
+     WarningMax 1000.00
+     FailureMin    0.00
+     FailureMax 1200.00
+     Invert false
+     Instance "bar"
+   </Type>
+
+   <Plugin "interface">
+     Instance "eth0"
+     <Type "if_octets">
+       FailureMax 10000000
+     </Type>
+   </Plugin>
+
+   <Host "hostname">
+     <Type "cpu">
+       Instance "idle"
+       FailureMin 10
+     </Type>
+
+     <Plugin "memory">
+       <Type "memory">
+         Instance "cached"
+        WarningMin 100000000
+       </Type>
+     </Plugin>
+   </Host>
+ </Threshold>
+
+There are basically two types of configuration statements: The C<Host>,
+C<Plugin>, and C<Type> blocks select the value for which a threshold should be
+configured. The C<Plugin> and C<Type> blocks may be specified further using the
+C<Instance> option. You can combine the block by nesting the blocks, though
+they must be nested in the above order, i.E<nbsp>e. C<Host> may contain either
+C<Plugin> and C<Type> blocks, C<Plugin> may only contain C<Type> blocks and
+C<Type> may not contain other blocks. If multiple blocks apply to the same
+value the most specific block is used.
+
+The other statements specify the threshold to configure. They B<must> be
+included in a C<Type> block. Currently the following statements are recognized:
+
+=over 4
+
+=item B<FailureMax> I<Value>
+
+=item B<WarningMax> I<Value>
+
+Sets the upper bound of acceptable values. If unset defaults to positive
+infinity. If a value is greater than B<FailureMax> a B<FAILURE> notification
+will be created. If the value is greater than B<WarningMax> but less than (or
+equal to) B<FailureMax> a B<WARNING> notification will be created.
+
+=item B<FailureMin> I<Value>
+
+=item B<WarningMin> I<Value>
+
+Sets the lower bound of acceptable values. If unset defaults to negative
+infinity. If a value is less than B<FailureMin> a B<FAILURE> notification will
+be created. If the value is less than B<WarningMin> but greater than (or equal
+to) B<FailureMin> a B<WARNING> notification will be created.
+
+=item B<Invert> B<true>|B<false>
+
+If set to B<true> the range of acceptable values is inverted, i.E<nbsp>e.
+values between B<FailureMin> and B<FailureMax> (B<WarningMin> and
+B<WarningMax>) are not okay. Defaults to B<false>.
+
+=item B<Persist> B<true>|B<false>
+
+Sets how often notifications are generated. If set to B<true> one notification
+will be generated for each value that is out of the acceptable range. If set to
+B<false> (the default) then a notification is only generated if a value is out
+of range but the previous value was okay.
+
+This applies to missing values, too: If set to B<true> a notification about a
+missing value is generated once every B<Interval> seconds. If set to B<false>
+only one such notification is generated until the value appears again.
+
+=back
+
 =head1 SEE ALSO
 
 L<collectd(1)>,
 L<collectd-exec(5)>,
 L<collectd-perl(5)>,
 L<collectd-unixsock(5)>,
+L<types.db(5)>,
 L<hddtemp(8)>,
 L<kstat(3KSTAT)>,
 L<mbmon(1)>,
index e09fd84..b55362a 100644 (file)
@@ -89,6 +89,11 @@ C<logfile plugin> and the C<syslog plugin>. With these plugins collectd can
 provide information about issues and significant situations to the user.
 Several loglevels let you suppress uninteresting messages.
 
+Starting with version C<4.3.0> collectd has support for B<monitoring>. This is
+done by checking thresholds defined by the user. If a value is out of range, a
+notification will be dispatched to "notification plugins". See
+L<collectd.conf(5)> for more detailed information about threshold checking.
+
 Please note that some plugins, that provide other means of communicating with
 the daemon, have manpages of their own to describe their functionality in more
 detail. In particular those are L<collectd-email(5)>, L<collectd-exec(5)>,
@@ -102,6 +107,7 @@ L<collectd-exec(5)>,
 L<collectd-perl(5)>,
 L<collectd-snmp(5)>,
 L<collectd-unixsock(5)>,
+L<types.db(5)>,
 L<http://collectd.org/>
 
 =head1 AUTHOR
diff --git a/src/collectdmon.c b/src/collectdmon.c
new file mode 100644 (file)
index 0000000..e496eb0
--- /dev/null
@@ -0,0 +1,377 @@
+/**
+ * collectd - src/collectdmon.c
+ * Copyright (C) 2007  Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Sebastian Harl <sh at tokkee.org>
+ **/
+
+#include "config.h"
+
+#include <assert.h>
+
+#include <errno.h>
+
+#include <fcntl.h>
+
+#include <signal.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string.h>
+
+#include <syslog.h>
+
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <time.h>
+
+#include <unistd.h>
+
+#ifndef COLLECTDMON_PIDFILE
+# define COLLECTDMON_PIDFILE LOCALSTATEDIR"/run/collectdmon.pid"
+#endif /* ! COLLECTDMON_PIDFILE */
+
+#ifndef WCOREDUMP
+# define WCOREDUMP(s) 0
+#endif /* ! WCOREDUMP */
+
+static int loop    = 0;
+static int restart = 0;
+
+static char  *pidfile      = NULL;
+static pid_t  collectd_pid = 0;
+
+static void exit_usage (char *name)
+{
+       printf ("Usage: %s <options> [-- <collectd options>]\n"
+
+                       "\nAvailable options:\n"
+                       "  -h         Display this help and exit.\n"
+                       "  -c <path>  Path to the collectd binary.\n"
+                       "  -P <file>  PID-file.\n"
+
+                       "\nFor <collectd options> see collectd.conf(5).\n"
+
+                       "\n"PACKAGE" "VERSION", http://collectd.org/\n"
+                       "by Florian octo Forster <octo@verplant.org>\n"
+                       "for contributions see `AUTHORS'\n", name);
+       exit (0);
+} /* exit_usage */
+
+static int pidfile_create (void)
+{
+       FILE *file = NULL;
+
+       if (NULL == pidfile)
+               pidfile = COLLECTDMON_PIDFILE;
+
+       if (NULL == (file = fopen (pidfile, "w"))) {
+               syslog (LOG_ERR, "Error: couldn't open PID-file (%s) for writing: %s",
+                               pidfile, strerror (errno));
+               return -1;
+       }
+
+       fprintf (file, "%i\n", (int)getpid ());
+       fclose (file);
+       return 0;
+} /* pidfile_create */
+
+static int pidfile_delete (void)
+{
+       assert (NULL != pidfile);
+
+       if (0 != unlink (pidfile)) {
+               syslog (LOG_ERR, "Error: couldn't delete PID-file (%s): %s",
+                               pidfile, strerror (errno));
+               return -1;
+       }
+       return 0;
+} /* pidfile_remove */
+
+static int daemonize (void)
+{
+       struct rlimit rl;
+
+       pid_t pid = 0;
+       int   i   = 0;
+
+       if (0 != chdir ("/")) {
+               fprintf (stderr, "Error: chdir() failed: %s\n", strerror (errno));
+               return -1;
+       }
+
+       if (0 != getrlimit (RLIMIT_NOFILE, &rl)) {
+               fprintf (stderr, "Error: getrlimit() failed: %s\n", strerror (errno));
+               return -1;
+       }
+
+       if (0 > (pid = fork ())) {
+               fprintf (stderr, "Error: fork() failed: %s\n", strerror (errno));
+               return -1;
+       }
+       else if (pid != 0) {
+               exit (0);
+       }
+
+       if (0 != pidfile_create ())
+               return -1;
+
+       setsid ();
+
+       if (RLIM_INFINITY == rl.rlim_max)
+               rl.rlim_max = 1024;
+
+       for (i = 0; i < (int)rl.rlim_max; ++i)
+               close (i);
+
+       errno = 0;
+       if (open ("/dev/null", O_RDWR) != 0) {
+               syslog (LOG_ERR, "Error: couldn't connect STDIN to /dev/null: %s",
+                               strerror (errno));
+               return -1;
+       }
+
+       errno = 0;
+       if (dup (0) != 1) {
+               syslog (LOG_ERR, "Error: couldn't connect STDOUT to /dev/null: %s",
+                               strerror (errno));
+               return -1;
+       }
+
+       errno = 0;
+       if (dup (0) != 2) {
+               syslog (LOG_ERR, "Error: couldn't connect STDERR to /dev/null: %s",
+                               strerror (errno));
+               return -1;
+       }
+       return 0;
+} /* daemonize */
+
+static int collectd_start (char **argv)
+{
+       pid_t pid = 0;
+
+       if (0 > (pid = fork ())) {
+               syslog (LOG_ERR, "Error: fork() failed: %s", strerror (errno));
+               return -1;
+       }
+       else if (pid != 0) {
+               collectd_pid = pid;
+               return 0;
+       }
+
+       execvp (argv[0], argv);
+       syslog (LOG_ERR, "Error: execvp(%s) failed: %s",
+                       argv[0], strerror (errno));
+       exit (-1);
+} /* collectd_start */
+
+static int collectd_stop (void)
+{
+       if (0 == collectd_pid)
+               return 0;
+
+       if (0 != kill (collectd_pid, SIGTERM)) {
+               syslog (LOG_ERR, "Error: kill() failed: %s", strerror (errno));
+               return -1;
+       }
+       return 0;
+} /* collectd_stop */
+
+static void sig_int_term_handler (int signo)
+{
+       ++loop;
+       return;
+} /* sig_int_term_handler */
+
+static void sig_hup_handler (int signo)
+{
+       ++restart;
+       return;
+} /* sig_hup_handler */
+
+static void log_status (int status)
+{
+       if (WIFEXITED (status)) {
+               if (0 == WEXITSTATUS (status))
+                       syslog (LOG_INFO, "Info: collectd terminated with exit status %i",
+                                       WEXITSTATUS (status));
+               else
+                       syslog (LOG_WARNING,
+                                       "Warning: collectd terminated with exit status %i",
+                                       WEXITSTATUS (status));
+       }
+       else if (WIFSIGNALED (status)) {
+               syslog (LOG_WARNING, "Warning: collectd was terminated by signal %i%s",
+                               WTERMSIG (status), WCOREDUMP (status) ? " (core dumped)" : "");
+       }
+       return;
+} /* log_status */
+
+static void check_respawn (void)
+{
+       time_t t = time (NULL);
+
+       static time_t timestamp = 0;
+       static int    counter   = 0;
+
+       if ((t - 120) < timestamp)
+               ++counter;
+       else {
+               timestamp = t;
+               counter   = 0;
+       }
+
+       if (10 < counter) {
+               unsigned int time_left = 300;
+
+               syslog (LOG_ERR, "Error: collectd is respawning too fast - "
+                               "disabled for %i seconds", time_left);
+
+               while ((0 < (time_left = sleep (time_left))) && (0 == loop));
+       }
+       return;
+} /* check_respawn */
+
+int main (int argc, char **argv)
+{
+       int    collectd_argc = 0;
+       char  *collectd      = NULL;
+       char **collectd_argv = NULL;
+
+       struct sigaction sa;
+
+       int i = 0;
+
+       /* parse command line options */
+       while (42) {
+               int c = getopt (argc, argv, "hc:P:");
+
+               if (-1 == c)
+                       break;
+
+               switch (c) {
+                       case 'c':
+                               collectd = optarg;
+                               break;
+                       case 'P':
+                               pidfile = optarg;
+                               break;
+                       case 'h':
+                       default:
+                               exit_usage (argv[0]);
+               }
+       }
+
+       for (i = optind; i < argc; ++i)
+               if (0 == strcmp (argv[i], "-f"))
+                       break;
+
+       /* i < argc => -f already present */
+       collectd_argc = 1 + argc - optind + ((i < argc) ? 0 : 1);
+       collectd_argv = (char **)calloc (collectd_argc + 1, sizeof (char *));
+
+       if (NULL == collectd_argv) {
+               fprintf (stderr, "Out of memory.");
+               return 3;
+       }
+
+       collectd_argv[0] = (NULL == collectd) ? "collectd" : collectd;
+
+       if (i == argc)
+               collectd_argv[collectd_argc - 1] = "-f";
+
+       for (i = optind; i < argc; ++i)
+               collectd_argv[i - optind + 1] = argv[i];
+
+       collectd_argv[collectd_argc] = NULL;
+
+       openlog ("collectdmon", LOG_CONS | LOG_PID, LOG_DAEMON);
+
+       if (-1 == daemonize ())
+               return 1;
+
+       sa.sa_handler = sig_int_term_handler;
+       sa.sa_flags   = 0;
+       sigemptyset (&sa.sa_mask);
+
+       if (0 != sigaction (SIGINT, &sa, NULL)) {
+               syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
+               return 1;
+       }
+
+       if (0 != sigaction (SIGTERM, &sa, NULL)) {
+               syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
+               return 1;
+       }
+
+       sa.sa_handler = sig_hup_handler;
+
+       if (0 != sigaction (SIGHUP, &sa, NULL)) {
+               syslog (LOG_ERR, "Error: sigaction() failed: %s", strerror (errno));
+               return 1;
+       }
+
+       sigaddset (&sa.sa_mask, SIGCHLD);
+       if (0 != sigprocmask (SIG_BLOCK, &sa.sa_mask, NULL)) {
+               syslog (LOG_ERR, "Error: sigprocmask() failed: %s", strerror (errno));
+               return 1;
+       }
+
+       while (0 == loop) {
+               int status = 0;
+
+               if (0 != collectd_start (collectd_argv)) {
+                       syslog (LOG_ERR, "Error: failed to start collectd.");
+                       break;
+               }
+
+               assert (0 < collectd_pid);
+               while ((collectd_pid != waitpid (collectd_pid, &status, 0))
+                               && (EINTR == errno))
+                       if ((0 != loop) || (0 != restart))
+                               collectd_stop ();
+
+               collectd_pid = 0;
+
+               log_status (status);
+               check_respawn ();
+
+               if (0 != restart) {
+                       syslog (LOG_INFO, "Info: restarting collectd");
+                       restart = 0;
+               }
+               else if (0 == loop)
+                       syslog (LOG_WARNING, "Warning: restarting collectd");
+       }
+
+       syslog (LOG_INFO, "Info: shutting down collectdmon");
+
+       pidfile_delete ();
+       closelog ();
+
+       free (collectd_argv);
+       return 0;
+} /* main */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/collectdmon.pod b/src/collectdmon.pod
new file mode 100644 (file)
index 0000000..73ba6b8
--- /dev/null
@@ -0,0 +1,73 @@
+=head1 NAME
+
+collectdmon - Monitoring daemon for collectd
+
+=head1 SYNOPSIS
+
+collectdmon I<[options]> [-- I<collectd options>]
+
+=head1 DESCRIPTION
+
+collectdmon is a small "wrapper" daemon which starts and monitors the collectd
+daemon. If collectd terminates it will automatically be restarted, unless
+collectdmon was told to shut it down.
+
+=head1 OPTIONS
+
+collectdmon supports the following options:
+
+=over 4
+
+=item B<-c> I<E<lt>pathE<gt>>
+
+Specify the pathname of the collectd binary. You may either specify an
+absolute path or simply the name of the binary in which case the B<PATH>
+variable will be searched for it. The default is "B<collectd>".
+
+=item B<-P> I<E<lt>pid-fileE<gt>>
+
+Specify the pid file. The default is "I</var/run/collectdmon.pid>".
+
+=item B<-h>
+
+Output usage information and exit.
+
+=item I<collectd options>
+
+Specify options that are passed on to collectd. If it is not already included,
+B<-f> will be added to these options. See L<collectd(1)>.
+
+=back
+
+=head1 SIGNALS
+
+B<collectdmon> accepts the following signals:
+
+=over 4
+
+=item B<SIGINT>, B<SIGTERM>
+
+These signals cause B<collectdmon> to terminate B<collectd>, wait for its
+termination and then shut down.
+
+=item B<SIGHUP>
+
+This signal causes B<collectdmon> to terminate B<collectd>, wait for its
+termination and then restart it.
+
+=back
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<http://collectd.org/>
+
+=head1 AUTHOR
+
+collectd has been written by Florian Forster E<lt>octo at verplant.orgE<gt>
+and many contributors (see `AUTHORS').
+
+collectdmon has been written by Sebastian Harl E<lt>sh@tokkee.orgE<gt>.
+
+=cut
index 9314d81..5c3db5d 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/common.c
- * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2005-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
@@ -53,11 +53,13 @@ static pthread_mutex_t getpwnam_r_lock = PTHREAD_MUTEX_INITIALIZER;
 static pthread_mutex_t strerror_r_lock = PTHREAD_MUTEX_INITIALIZER;
 #endif
 
-void sstrncpy (char *d, const char *s, int len)
+char *sstrncpy (char *dest, const char *src, size_t n)
 {
-       strncpy (d, s, len);
-       d[len - 1] = '\0';
-}
+       strncpy (dest, src, n);
+       dest[n - 1] = '\0';
+
+       return (dest);
+} /* char *sstrncpy */
 
 char *sstrdup (const char *s)
 {
@@ -177,7 +179,7 @@ ssize_t sread (int fd, void *buf, size_t count)
                        return (-1);
                }
 
-               assert (nleft >= status);
+               assert ((0 > status) || (nleft >= (size_t)status));
 
                nleft = nleft - status;
                ptr   = ptr   + status;
@@ -238,8 +240,8 @@ int strjoin (char *dst, size_t dst_len,
                char **fields, size_t fields_num,
                const char *sep)
 {
-       int field_len;
-       int sep_len;
+       size_t field_len;
+       size_t sep_len;
        int i;
 
        memset (dst, '\0', dst_len);
@@ -251,7 +253,7 @@ int strjoin (char *dst, size_t dst_len,
        if (sep != NULL)
                sep_len = strlen (sep);
 
-       for (i = 0; i < fields_num; i++)
+       for (i = 0; i < (int)fields_num; i++)
        {
                if ((i > 0) && (sep_len > 0))
                {
@@ -824,4 +826,38 @@ int getpwnam_r (const char *name, struct passwd *pwbuf, char *buf,
 
        return (status);
 } /* int getpwnam_r */
-#endif
+#endif /* !HAVE_GETPWNAM_R */
+
+int notification_init (notification_t *n, int severity, const char *message,
+               const char *host,
+               const char *plugin, const char *plugin_instance,
+               const char *type, const char *type_instance)
+{
+       memset (n, '\0', sizeof (notification_t));
+
+       n->severity = severity;
+
+       if (message != NULL)
+               strncpy (n->message, message, sizeof (n->message));
+       if (host != NULL)
+               strncpy (n->host, host, sizeof (n->host));
+       if (plugin != NULL)
+               strncpy (n->plugin, plugin, sizeof (n->plugin));
+       if (plugin_instance != NULL)
+               strncpy (n->plugin_instance, plugin_instance,
+                               sizeof (n->plugin_instance));
+       if (type != NULL)
+               strncpy (n->type, type, sizeof (n->type));
+       if (type_instance != NULL)
+               strncpy (n->type_instance, type_instance,
+                               sizeof (n->type_instance));
+
+       n->message[sizeof (n->message) - 1] = '\0';
+       n->host[sizeof (n->host) - 1] = '\0';
+       n->plugin[sizeof (n->plugin) - 1] = '\0';
+       n->plugin_instance[sizeof (n->plugin_instance) - 1] = '\0';
+       n->type[sizeof (n->type) - 1] = '\0';
+       n->type_instance[sizeof (n->type_instance) - 1] = '\0';
+
+       return (0);
+} /* int notification_init */
index 9cd2476..e99aea6 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/common.h
- * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2005-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
@@ -38,7 +38,7 @@
 
 #define STATIC_ARRAY_SIZE(a) (sizeof (a) / sizeof (*(a)))
 
-void sstrncpy(char *d, const char *s, int len);
+char *sstrncpy (char *dest, const char *src, size_t n);
 char *sstrdup(const char *s);
 void *smalloc(size_t size);
 char *sstrerror (int errnum, char *buf, size_t buflen);
@@ -191,4 +191,12 @@ int getpwnam_r (const char *name, struct passwd *pwbuf, char *buf,
                size_t buflen, struct passwd **pwbufp);
 #endif
 
+int notification_init (notification_t *n, int severity, const char *message,
+               const char *host,
+               const char *plugin, const char *plugin_instance,
+               const char *type, const char *type_instance);
+#define NOTIFICATION_INIT_VL(n, vl, ds) \
+       notification_init (n, NOTIF_FAILURE, NULL, \
+                       (vl)->host, (vl)->plugin, (vl)->plugin_instance, \
+                       (ds)->type, (vl)->type_instance)
 #endif /* COMMON_H */
index e6b8ce8..4a9789a 100644 (file)
@@ -18,6 +18,7 @@
  *
  * Authors:
  *   Florian octo Forster <octo at verplant.org>
+ *   Sebastian tokkee Harl <sh at tokkee.org>
  **/
 
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
 #include "configfile.h"
+#include "types_list.h"
+#include "utils_threshold.h"
+
+#if HAVE_WORDEXP_H
+# include <wordexp.h>
+#endif /* HAVE_WORDEXP_H */
 
 #define ESCAPE_NULL(str) ((str) == NULL ? "(null)" : (str))
 
@@ -65,6 +72,7 @@ typedef struct cf_global_option_s
 /*
  * Prototypes of callback functions
  */
+static int dispatch_value_typesdb (const oconfig_item_t *ci);
 static int dispatch_value_plugindir (const oconfig_item_t *ci);
 static int dispatch_value_loadplugin (const oconfig_item_t *ci);
 
@@ -76,6 +84,7 @@ static cf_complex_callback_t *complex_callback_head = NULL;
 
 static cf_value_map_t cf_value_map[] =
 {
+       {"TypesDB",    dispatch_value_typesdb},
        {"PluginDir",  dispatch_value_plugindir},
        {"LoadPlugin", dispatch_value_loadplugin}
 };
@@ -83,15 +92,17 @@ static int cf_value_map_num = STATIC_ARRAY_LEN (cf_value_map);
 
 static cf_global_option_t cf_global_options[] =
 {
-       {"BaseDir",   NULL, PKGLOCALSTATEDIR},
-       {"PIDFile",   NULL, PIDFILE},
-       {"Hostname",  NULL, NULL},
-       {"Interval",  NULL, "10"},
-       {"ReadThreads", NULL, "5"},
-       {"TypesDB",   NULL, PLUGINDIR"/types.db"} /* FIXME: Configure path */
+       {"BaseDir",     NULL, PKGLOCALSTATEDIR},
+       {"PIDFile",     NULL, PIDFILE},
+       {"Hostname",    NULL, NULL},
+       {"FQDNLookup",  NULL, "false"},
+       {"Interval",    NULL, "10"},
+       {"ReadThreads", NULL, "5"}
 };
 static int cf_global_options_num = STATIC_ARRAY_LEN (cf_global_options);
 
+static int cf_default_typesdb = 1;
+
 /*
  * Functions to handle register/unregister, search, and other plugin related
  * stuff
@@ -175,10 +186,38 @@ static int dispatch_global_option (const oconfig_item_t *ci)
                tmp[127] = '\0';
                return (global_option_set (ci->key, tmp));
        }
+       else if (ci->values[0].type == OCONFIG_TYPE_BOOLEAN)
+       {
+               if (ci->values[0].value.boolean)
+                       return (global_option_set (ci->key, "true"));
+               else
+                       return (global_option_set (ci->key, "false"));
+       }
 
        return (-1);
 } /* int dispatch_global_option */
 
+static int dispatch_value_typesdb (const oconfig_item_t *ci)
+{
+       int i = 0;
+
+       assert (strcasecmp (ci->key, "TypesDB") == 0);
+
+       cf_default_typesdb = 0;
+
+       if (ci->values_num < 1)
+               return (-1);
+
+       for (i = 0; i < ci->values_num; ++i)
+       {
+               if (OCONFIG_TYPE_STRING != ci->values[i].type)
+                       continue;
+
+               read_types_list (ci->values[i].value.string);
+       }
+       return (0);
+} /* int dispatch_value_typesdb */
+
 static int dispatch_value_plugindir (const oconfig_item_t *ci)
 {
        assert (strcasecmp (ci->key, "PluginDir") == 0);
@@ -300,12 +339,115 @@ static int dispatch_block (oconfig_item_t *ci)
 {
        if (strcasecmp (ci->key, "Plugin") == 0)
                return (dispatch_block_plugin (ci));
+       else if (strcasecmp (ci->key, "Threshold") == 0)
+               return (ut_config (ci));
 
        return (0);
 }
 
+static int cf_ci_replace_child (oconfig_item_t *dst, oconfig_item_t *src,
+               int offset)
+{
+       oconfig_item_t *temp;
+       int i;
+
+       assert (offset >= 0);
+       assert (dst->children_num > offset);
+
+       /* Free the memory used by the replaced child. Usually that's the
+        * `Include "blah"' statement. */
+       temp = dst->children + offset;
+       for (i = 0; i < temp->values_num; i++)
+       {
+               if (temp->values[i].type == OCONFIG_TYPE_STRING)
+               {
+                       sfree (temp->values[i].value.string);
+               }
+       }
+       sfree (temp->values);
+       temp = NULL;
+
+       /* If (src->children_num == 0) the array size is decreased. If offset
+        * is _not_ the last element, (offset < (src->children_num - 1)), then
+        * we need to move the trailing elements before resizing the array. */
+       if ((src->children_num == 0) && (offset < (src->children_num - 1)))
+       {
+               int nmemb = src->children_num - (offset + 1);
+               memmove (src->children + offset, src->children + offset + 1,
+                               sizeof (oconfig_item_t) * nmemb);
+       }
+
+       /* Resize the memory containing the children to be big enough to hold
+        * all children. */
+       temp = (oconfig_item_t *) realloc (dst->children,
+                       sizeof (oconfig_item_t)
+                       * (dst->children_num + src->children_num - 1));
+       if (temp == NULL)
+       {
+               ERROR ("configfile: realloc failed.");
+               return (-1);
+       }
+       dst->children = temp;
+
+       /* If there are children behind the include statement, and they have
+        * not yet been moved because (src->children_num == 0), then move them
+        * to the end of the list, so that the new children have room before
+        * them. */
+       if ((src->children_num > 0)
+                       && ((dst->children_num - (offset + 1)) > 0))
+       {
+               int nmemb = dst->children_num - (offset + 1);
+               int old_offset = offset + 1;
+               int new_offset = offset + src->children_num;
+
+               memmove (dst->children + new_offset,
+                               dst->children + old_offset,
+                               sizeof (oconfig_item_t) * nmemb);
+       }
+
+       /* Last but not least: If there are new childrem, copy them to the
+        * memory reserved for them. */
+       if (src->children_num > 0)
+       {
+               memcpy (dst->children + offset,
+                               src->children,
+                               sizeof (oconfig_item_t) * src->children_num);
+       }
+
+       /* Update the number of children. */
+       dst->children_num += (src->children_num - 1);
+
+       return (0);
+} /* int cf_ci_replace_child */
+
+static int cf_ci_append_children (oconfig_item_t *dst, oconfig_item_t *src)
+{
+       oconfig_item_t *temp;
+
+       if ((src == NULL) || (src->children_num == 0))
+               return (0);
+
+       temp = (oconfig_item_t *) realloc (dst->children,
+                       sizeof (oconfig_item_t)
+                       * (dst->children_num + src->children_num));
+       if (temp == NULL)
+       {
+               ERROR ("configfile: realloc failed.");
+               return (-1);
+       }
+       dst->children = temp;
+
+       memcpy (dst->children + dst->children_num,
+                       src->children,
+                       sizeof (oconfig_item_t)
+                       * src->children_num);
+       dst->children_num += src->children_num;
+
+       return (0);
+} /* int cf_ci_append_children */
+
 #define CF_MAX_DEPTH 8
-static oconfig_item_t *cf_read_file (const char *file, int depth);
+static oconfig_item_t *cf_read_generic (const char *path, int depth);
 
 static int cf_include_all (oconfig_item_t *root, int depth)
 {
@@ -332,99 +474,263 @@ static int cf_include_all (oconfig_item_t *root, int depth)
                        continue;
                }
 
-               new = cf_read_file (old->values[0].value.string, depth + 1);
+               new = cf_read_generic (old->values[0].value.string, depth + 1);
                if (new == NULL)
                        continue;
 
-               /* There are more children now. We need to expand
-                * root->children. */
-               if (new->children_num > 1)
+               /* Now replace the i'th child in `root' with `new'. */
+               cf_ci_replace_child (root, new, i);
+
+               sfree (new->values);
+               sfree (new);
+       } /* for (i = 0; i < root->children_num; i++) */
+
+       return (0);
+} /* int cf_include_all */
+
+static oconfig_item_t *cf_read_file (const char *file, int depth)
+{
+       oconfig_item_t *root;
+
+       assert (depth < CF_MAX_DEPTH);
+
+       root = oconfig_parse_file (file);
+       if (root == NULL)
+       {
+               ERROR ("configfile: Cannot read file `%s'.", file);
+               return (NULL);
+       }
+
+       cf_include_all (root, depth);
+
+       return (root);
+} /* oconfig_item_t *cf_read_file */
+
+static int cf_compare_string (const void *p1, const void *p2)
+{
+       return strcmp (*(const char **) p1, *(const char **) p2);
+}
+
+static oconfig_item_t *cf_read_dir (const char *dir, int depth)
+{
+       oconfig_item_t *root = NULL;
+       DIR *dh;
+       struct dirent *de;
+       char **filenames = NULL;
+       int filenames_num = 0;
+       int status;
+       int i;
+
+       assert (depth < CF_MAX_DEPTH);
+
+       dh = opendir (dir);
+       if (dh == NULL)
+       {
+               char errbuf[1024];
+               ERROR ("configfile: opendir failed: %s",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (NULL);
+       }
+
+       root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t));
+       if (root == NULL)
+       {
+               ERROR ("configfile: malloc failed.");
+               return (NULL);
+       }
+       memset (root, '\0', sizeof (oconfig_item_t));
+
+       while ((de = readdir (dh)) != NULL)
+       {
+               char   name[1024];
+               char **tmp;
+
+               if ((de->d_name[0] == '.') || (de->d_name[0] == '\0'))
+                       continue;
+
+               status = snprintf (name, sizeof (name), "%s/%s",
+                               dir, de->d_name);
+               if (status >= sizeof (name))
                {
-                       oconfig_item_t *temp;
-
-                       DEBUG ("configfile: Resizing root-children from %i to %i elements.",
-                                       root->children_num,
-                                       root->children_num + new->children_num - 1);
-
-                       temp = (oconfig_item_t *) realloc (root->children,
-                                       sizeof (oconfig_item_t)
-                                       * (root->children_num + new->children_num - 1));
-                       if (temp == NULL)
-                       {
-                               ERROR ("configfile: realloc failed.");
-                               oconfig_free (new);
-                               continue;
-                       }
-                       root->children = temp;
+                       ERROR ("configfile: Not including `%s/%s' because its"
+                                       " name is too long.",
+                                       dir, de->d_name);
+                       for (i = 0; i < filenames_num; ++i)
+                               free (filenames[i]);
+                       free (filenames);
+                       free (root);
+                       return (NULL);
                }
 
-               /* Clean up the old include directive while we still have a
-                * valid pointer */
-               DEBUG ("configfile: Cleaning up `old'");
-               /* sfree (old->values[0].value.string); */
-               sfree (old->values);
-
-               /* If there are trailing children and the number of children
-                * changes, we need to move the trailing ones either one to the
-                * front or (new->num - 1) to the back */
-               if (((root->children_num - i) > 1)
-                               && (new->children_num != 1))
-               {
-                       DEBUG ("configfile: Moving trailing children.");
-                       memmove (root->children + i + new->children_num,
-                                       root->children + i + 1,
-                                       sizeof (oconfig_item_t)
-                                       * (root->children_num - (i + 1)));
+               ++filenames_num;
+               tmp = (char **) realloc (filenames,
+                               filenames_num * sizeof (*filenames));
+               if (tmp == NULL) {
+                       ERROR ("configfile: realloc failed.");
+                       for (i = 0; i < filenames_num - 1; ++i)
+                               free (filenames[i]);
+                       free (filenames);
+                       free (root);
+                       return (NULL);
                }
+               filenames = tmp;
 
-               /* Now copy the new children to where the include statement was */
-               if (new->children_num > 0)
-               {
-                       DEBUG ("configfile: Copying new children.");
-                       memcpy (root->children + i,
-                                       new->children,
-                                       sizeof (oconfig_item_t)
-                                       * new->children_num);
+               filenames[filenames_num - 1] = sstrdup (name);
+       }
+
+       qsort ((void *) filenames, filenames_num, sizeof (*filenames),
+                       cf_compare_string);
+
+       for (i = 0; i < filenames_num; ++i)
+       {
+               oconfig_item_t *temp;
+               char *name = filenames[i];
+
+               temp = cf_read_generic (name, depth);
+               if (temp == NULL) {
+                       int j;
+                       for (j = i; j < filenames_num; ++j)
+                               free (filenames[j]);
+                       free (filenames);
+                       oconfig_free (root);
+                       return (NULL);
                }
 
-               /* Adjust the number of children and the position in the list. */
-               root->children_num = root->children_num + new->children_num - 1;
-               i = i + new->children_num - 1;
+               cf_ci_append_children (root, temp);
+               sfree (temp->children);
+               sfree (temp);
 
-               /* Clean up the `new' struct. We set `new->children' to NULL so
-                * the stuff we've just copied pointers to isn't freed by
-                * `oconfig_free' */
-               DEBUG ("configfile: Cleaning up `new'");
-               sfree (new->values); /* should be NULL anyway */
-               sfree (new);
-               new = NULL;
-       } /* for (i = 0; i < root->children_num; i++) */
+               free (name);
+       }
 
-       return (0);
-} /* int cf_include_all */
+       free(filenames);
+       return (root);
+} /* oconfig_item_t *cf_read_dir */
 
-static oconfig_item_t *cf_read_file (const char *file, int depth)
+/* 
+ * cf_read_generic
+ *
+ * Path is stat'ed and either cf_read_file or cf_read_dir is called
+ * accordingly.
+ *
+ * There are two versions of this function: If `wordexp' exists shell wildcards
+ * will be expanded and the function will include all matches found. If
+ * `wordexp' (or, more precisely, it's header file) is not available the
+ * simpler function is used which does not do any such expansion.
+ */
+#if HAVE_WORDEXP_H
+static oconfig_item_t *cf_read_generic (const char *path, int depth)
 {
-       oconfig_item_t *root;
+       oconfig_item_t *root = NULL;
+       int status;
+       const char *path_ptr;
+       wordexp_t we;
+       int i;
 
        if (depth >= CF_MAX_DEPTH)
        {
-               ERROR ("configfile: Not including `%s' because the maximum nesting depth has been reached.",
-                               file);
+               ERROR ("configfile: Not including `%s' because the maximum "
+                               "nesting depth has been reached.", path);
                return (NULL);
        }
 
-       root = oconfig_parse_file (file);
+       status = wordexp (path, &we, WRDE_NOCMD);
+       if (status != 0)
+       {
+               ERROR ("configfile: wordexp (%s) failed.", path);
+               return (NULL);
+       }
+
+       root = (oconfig_item_t *) malloc (sizeof (oconfig_item_t));
        if (root == NULL)
        {
-               ERROR ("configfile: Cannot read file `%s'.", file);
+               ERROR ("configfile: malloc failed.");
                return (NULL);
        }
+       memset (root, '\0', sizeof (oconfig_item_t));
 
-       cf_include_all (root, depth);
+       /* wordexp() might return a sorted list already. That's not
+        * documented though, so let's make sure we get what we want. */
+       qsort ((void *) we.we_wordv, we.we_wordc, sizeof (*we.we_wordv),
+                       cf_compare_string);
+
+       for (i = 0; i < we.we_wordc; i++)
+       {
+               oconfig_item_t *temp;
+               struct stat statbuf;
+
+               path_ptr = we.we_wordv[i];
+
+               status = stat (path_ptr, &statbuf);
+               if (status != 0)
+               {
+                       char errbuf[1024];
+                       ERROR ("configfile: stat (%s) failed: %s",
+                                       path_ptr,
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+                       oconfig_free (root);
+                       return (NULL);
+               }
+
+               if (S_ISREG (statbuf.st_mode))
+                       temp = cf_read_file (path_ptr, depth);
+               else if (S_ISDIR (statbuf.st_mode))
+                       temp = cf_read_dir (path_ptr, depth);
+               else
+               {
+                       ERROR ("configfile: %s is neither a file nor a "
+                                       "directory.", path);
+                       continue;
+               }
+
+               if (temp == NULL) {
+                       oconfig_free (root);
+                       return (NULL);
+               }
+
+               cf_ci_append_children (root, temp);
+               sfree (temp->children);
+               sfree (temp);
+       }
+
+       wordfree (&we);
 
        return (root);
-} /* oconfig_item_t *cf_read_file */
+} /* oconfig_item_t *cf_read_generic */
+/* #endif HAVE_WORDEXP_H */
+
+#else /* if !HAVE_WORDEXP_H */
+static oconfig_item_t *cf_read_generic (const char *path, int depth)
+{
+       struct stat statbuf;
+       int status;
+
+       if (depth >= CF_MAX_DEPTH)
+       {
+               ERROR ("configfile: Not including `%s' because the maximum "
+                               "nesting depth has been reached.", path);
+               return (NULL);
+       }
+
+       status = stat (path, &statbuf);
+       if (status != 0)
+       {
+               char errbuf[1024];
+               ERROR ("configfile: stat (%s) failed: %s",
+                               path,
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (NULL);
+       }
+
+       if (S_ISREG (statbuf.st_mode))
+               return (cf_read_file (path, depth));
+       else if (S_ISDIR (statbuf.st_mode))
+               return (cf_read_dir (path, depth));
+
+       ERROR ("configfile: %s is neither a file nor a directory.", path);
+       return (NULL);
+} /* oconfig_item_t *cf_read_generic */
+#endif /* !HAVE_WORDEXP_H */
 
 /* 
  * Public functions
@@ -567,7 +873,7 @@ int cf_read (char *filename)
        oconfig_item_t *conf;
        int i;
 
-       conf = cf_read_file (filename, 0 /* depth */);
+       conf = cf_read_generic (filename, 0 /* depth */);
        if (conf == NULL)
        {
                ERROR ("Unable to read config file %s.", filename);
@@ -582,5 +888,7 @@ int cf_read (char *filename)
                        dispatch_block (conf->children + i);
        }
 
+       if (cf_default_typesdb)
+               read_types_list (PLUGINDIR"/types.db"); /* FIXME: Configure path */
        return (0);
 } /* int cf_read */
index ba0c1b1..c79c4b6 100644 (file)
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -277,20 +277,14 @@ static int cpu_read (void)
        char *fields[9];
        int numfields;
 
-       static complain_t complain_obj;
-
        if ((fh = fopen ("/proc/stat", "r")) == NULL)
        {
                char errbuf[1024];
-               plugin_complain (LOG_ERR, &complain_obj, "cpu plugin: "
-                               "fopen (/proc/stat) failed: %s",
+               ERROR ("cpu plugin: fopen (/proc/stat) failed: %s",
                                sstrerror (errno, errbuf, sizeof (errbuf)));
                return (-1);
        }
 
-       plugin_relief (LOG_NOTICE, &complain_obj, "cpu plugin: "
-                       "fopen (/proc/stat) succeeded.");
-
        while (fgets (buf, 1024, fh) != NULL)
        {
                if (strncmp (buf, "cpu", 3))
@@ -360,22 +354,16 @@ static int cpu_read (void)
        long cpuinfo[CPUSTATES];
        size_t cpuinfo_size;
 
-       static complain_t complain_obj;
-
        cpuinfo_size = sizeof (cpuinfo);
 
        if (sysctlbyname("kern.cp_time", &cpuinfo, &cpuinfo_size, NULL, 0) < 0)
        {
                char errbuf[1024];
-               plugin_complain (LOG_ERR, &complain_obj, "cpu plugin: "
-                               "sysctlbyname failed: %s.",
+               ERROR ("cpu plugin: sysctlbyname failed: %s.",
                                sstrerror (errno, errbuf, sizeof (errbuf)));
                return (-1);
        }
 
-       plugin_relief (LOG_NOTICE, &complain_obj, "cpu plugin: "
-                       "sysctlbyname succeeded.");
-
        cpuinfo[CP_SYS] += cpuinfo[CP_INTR];
 
        submit (0, "user", cpuinfo[CP_USER]);
index b1037c3..42248a9 100644 (file)
@@ -40,7 +40,7 @@ static int cpufreq_init (void)
                status = snprintf (filename, sizeof (filename),
                                "/sys/devices/system/cpu/cpu%d/cpufreq/"
                                "scaling_cur_freq", num_cpu);
-               if (status < 1 || status >= sizeof (filename))
+               if ((status < 1) || ((unsigned int)status >= sizeof (filename)))
                        break;
 
                if (access (filename, R_OK))
@@ -90,7 +90,7 @@ static int cpufreq_read (void)
                status = snprintf (filename, sizeof (filename),
                                "/sys/devices/system/cpu/cpu%d/cpufreq/"
                                "scaling_cur_freq", i);
-               if (status < 1 || status >= sizeof (filename))
+               if ((status < 1) || ((unsigned int)status >= sizeof (filename)))
                        return (-1);
 
                if ((fp = fopen (filename, "r")) == NULL)
index 192cf52..ff59f91 100644 (file)
--- a/src/csv.c
+++ b/src/csv.c
 #include "collectd.h"
 #include "plugin.h"
 #include "common.h"
+#include "utils_cache.h"
 
 /*
  * Private variables
  */
 static const char *config_keys[] =
 {
-       "DataDir"
+       "DataDir",
+       "StoreRates"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 static char *datadir   = NULL;
+static int store_rates = 0;
 
 static int value_list_to_string (char *buffer, int buffer_len,
                const data_set_t *ds, const value_list_t *vl)
@@ -40,6 +43,7 @@ static int value_list_to_string (char *buffer, int buffer_len,
        int offset;
        int status;
        int i;
+       gauge_t *rates = NULL;
 
        memset (buffer, '\0', buffer_len);
 
@@ -55,18 +59,45 @@ static int value_list_to_string (char *buffer, int buffer_len,
                        return (-1);
 
                if (ds->ds[i].type == DS_TYPE_COUNTER)
-                       status = snprintf (buffer + offset, buffer_len - offset,
-                                       ",%llu", vl->values[i].counter);
-               else
+               {
+                       if (store_rates == 0)
+                       {
+                               status = snprintf (buffer + offset,
+                                               buffer_len - offset,
+                                               ",%llu",
+                                               vl->values[i].counter);
+                       }
+                       else /* if (store_rates == 1) */
+                       {
+                               if (rates == NULL)
+                                       rates = uc_get_rate (ds, vl);
+                               if (rates == NULL)
+                               {
+                                       WARNING ("csv plugin: "
+                                                       "uc_get_rate failed.");
+                                       return (-1);
+                               }
+                               status = snprintf (buffer + offset,
+                                               buffer_len - offset,
+                                               ",%lf", rates[i]);
+                       }
+               }
+               else /* if (ds->ds[i].type == DS_TYPE_GAUGE) */
+               {
                        status = snprintf (buffer + offset, buffer_len - offset,
                                        ",%lf", vl->values[i].gauge);
+               }
 
                if ((status < 1) || (status >= (buffer_len - offset)))
+               {
+                       sfree (rates);
                        return (-1);
+               }
 
                offset += status;
        } /* for ds->ds_num */
 
+       sfree (rates);
        return (0);
 } /* int value_list_to_string */
 
@@ -181,6 +212,19 @@ static int csv_config (const char *key, const char *value)
                        }
                }
        }
+       else if (strcasecmp ("StoreRates", key) == 0)
+       {
+               if ((strcasecmp ("True", value) == 0)
+                               || (strcasecmp ("Yes", value) == 0)
+                               || (strcasecmp ("On", value) == 0))
+               {
+                       store_rates = 1;
+               }
+               else
+               {
+                       store_rates = 0;
+               }
+       }
        else
        {
                return (-1);
index 490da07..8feaa8d 100644 (file)
@@ -231,22 +231,14 @@ static int disk_read (void)
        int  disk_minor;
        char disk_name[64];
 
-       static complain_t complain_obj;
-
        /* Get the list of all disk objects. */
        if (IOServiceGetMatchingServices (io_master_port,
                                IOServiceMatching (kIOBlockStorageDriverClass),
                                &disk_list) != kIOReturnSuccess)
        {
-               plugin_complain (LOG_ERR, &complain_obj, "disk plugin: "
-                               "IOServiceGetMatchingServices failed.");
+               ERROR ("disk plugin: IOServiceGetMatchingServices failed.");
                return (-1);
        }
-       else if (complain_obj.interval != 0)
-       {
-               plugin_relief (LOG_NOTICE, &complain_obj, "disk plugin: "
-                               "IOServiceGetMatchingServices succeeded.");
-       }
 
        while ((disk = IOIteratorNext (disk_list)) != 0)
        {
@@ -386,15 +378,12 @@ static int disk_read (void)
 
        diskstats_t *ds, *pre_ds;
 
-       static complain_t complain_obj;
-
        if ((fh = fopen ("/proc/diskstats", "r")) == NULL)
        {
-               if ((fh = fopen ("/proc/partitions", "r")) == NULL)
+               fh = fopen ("/proc/partitions", "r");
+               if (fh == NULL)
                {
-                       plugin_complain (LOG_ERR, &complain_obj,
-                                       "disk plugin: Failed to open /proc/"
-                                       "{diskstats,partitions}.");
+                       ERROR ("disk plugin: fopen (/proc/{diskstats,partitions}) failed.");
                        return (-1);
                }
 
@@ -402,9 +391,6 @@ static int disk_read (void)
                fieldshift = 1;
        }
 
-       plugin_relief (LOG_NOTICE, &complain_obj, "disk plugin: "
-                       "Succeeded to open /proc/{diskstats,partitions}.");
-
        while (fgets (buffer, sizeof (buffer), fh) != NULL)
        {
                char *disk_name;
@@ -576,9 +562,8 @@ static int disk_read (void)
 
                if (is_disk)
                {
-                       if ((read_merged != -1LL) || (write_merged != -1LL))
-                               disk_submit (disk_name, "disk_merged",
-                                               read_merged, write_merged);
+                       disk_submit (disk_name, "disk_merged",
+                                       read_merged, write_merged);
                } /* if (is_disk) */
        } /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */
 
index 52dd831..5eae906 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/exec.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
@@ -22,7 +22,9 @@
 #include "collectd.h"
 #include "common.h"
 #include "plugin.h"
+
 #include "utils_cmd_putval.h"
+#include "utils_cmd_putnotif.h"
 
 #include <sys/types.h>
 #include <pwd.h>
 
 #include <pthread.h>
 
+#define PL_NORMAL        0x01
+#define PL_NOTIF_ACTION  0x02
+
+#define PL_RUNNING       0x10
+
 /*
  * Private data types
  */
+/*
+ * Access to this structure is serialized using the `pl_lock' lock and the
+ * `PL_RUNNING' flag. The execution of notifications is *not* serialized, so
+ * all functions used to handle notifications MUST NOT write to this structure.
+ * The `pid' and `status' fields are thus unused if the `PL_NOTIF_ACTION' flag
+ * is set.
+ * The `PL_RUNNING' flag is set in `exec_read' and unset in `exec_read_one'.
+ */
 struct program_list_s;
 typedef struct program_list_s program_list_t;
 struct program_list_s
@@ -41,85 +56,217 @@ struct program_list_s
   char           *user;
   char           *group;
   char           *exec;
+  char          **argv;
   int             pid;
+  int             status;
+  int             flags;
   program_list_t *next;
 };
 
+typedef struct program_list_and_notification_s
+{
+  program_list_t *pl;
+  notification_t n;
+} program_list_and_notification_t;
+
 /*
  * Private variables
  */
-static const char *config_keys[] =
-{
-  "Exec"
-};
-static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
-
 static program_list_t *pl_head = NULL;
+static pthread_mutex_t pl_lock = PTHREAD_MUTEX_INITIALIZER;
 
 /*
  * Functions
  */
-static int exec_config (const char *key, const char *value)
+static void sigchld_handler (int signal) /* {{{ */
 {
-  if (strcasecmp ("Exec", key) == 0)
+  pid_t pid;
+  int status;
+  while ((pid = waitpid (-1, &status, WNOHANG)) > 0)
   {
     program_list_t *pl;
-    pl = (program_list_t *) malloc (sizeof (program_list_t));
-    if (pl == NULL)
-      return (1);
-    memset (pl, '\0', sizeof (program_list_t));
+    for (pl = pl_head; pl != NULL; pl = pl->next)
+      if (pl->pid == pid)
+       break;
+    if (pl != NULL)
+      pl->status = status;
+  } /* while (waitpid) */
+} /* void sigchld_handler }}} */
+
+static int exec_config_exec (oconfig_item_t *ci) /* {{{ */
+{
+  program_list_t *pl;
+  char buffer[128];
+  int i;
 
-    pl->user = strdup (value);
-    if (pl->user == NULL)
-    {
-      sfree (pl);
-      return (1);
-    }
+  if (ci->children_num != 0)
+  {
+    WARNING ("exec plugin: The config option `%s' may not be a block.",
+       ci->key);
+    return (-1);
+  }
+  if (ci->values_num < 2)
+  {
+    WARNING ("exec plugin: The config option `%s' needs at least two "
+       "arguments.", ci->key);
+    return (-1);
+  }
+  if ((ci->values[0].type != OCONFIG_TYPE_STRING)
+      || (ci->values[1].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("exec plugin: The first two arguments to the `%s' option must "
+       "be string arguments.", ci->key);
+    return (-1);
+  }
+
+  pl = (program_list_t *) malloc (sizeof (program_list_t));
+  if (pl == NULL)
+  {
+    ERROR ("exec plugin: malloc failed.");
+    return (-1);
+  }
+  memset (pl, '\0', sizeof (program_list_t));
+
+  if (strcasecmp ("NotificationExec", ci->key) == 0)
+    pl->flags |= PL_NOTIF_ACTION;
+  else
+    pl->flags |= PL_NORMAL;
+
+  pl->user = strdup (ci->values[0].value.string);
+  if (pl->user == NULL)
+  {
+    ERROR ("exec plugin: strdup failed.");
+    sfree (pl);
+    return (-1);
+  }
+
+  pl->group = strchr (pl->user, ':');
+  if (pl->group != NULL)
+  {
+    *pl->group = '\0';
+    pl->group++;
+  }
+
+  pl->exec = strdup (ci->values[1].value.string);
+  if (pl->exec == NULL)
+  {
+    ERROR ("exec plugin: strdup failed.");
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
+  }
 
-    pl->exec = strchr (pl->user, ' ');
-    if (pl->exec == NULL)
+  pl->argv = (char **) malloc (ci->values_num * sizeof (char *));
+  if (pl->argv == NULL)
+  {
+    ERROR ("exec plugin: malloc failed.");
+    sfree (pl->exec);
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
+  }
+  memset (pl->argv, '\0', ci->values_num * sizeof (char *));
+
+  {
+    char *tmp = strrchr (ci->values[1].value.string, '/');
+    if (tmp == NULL)
+      strncpy (buffer, ci->values[1].value.string, sizeof (buffer));
+    else
+      strncpy (buffer, tmp + 1, sizeof (buffer));
+    buffer[sizeof (buffer) - 1] = '\0';
+  }
+  pl->argv[0] = strdup (buffer);
+  if (pl->argv[0] == NULL)
+  {
+    ERROR ("exec plugin: malloc failed.");
+    sfree (pl->argv);
+    sfree (pl->exec);
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
+  }
+
+  for (i = 1; i < (ci->values_num - 1); i++)
+  {
+    if (ci->values[i + 1].type == OCONFIG_TYPE_STRING)
     {
-      sfree (pl->user);
-      sfree (pl);
-      return (1);
+      pl->argv[i] = strdup (ci->values[i + 1].value.string);
     }
-    while (*pl->exec == ' ')
+    else
     {
-      *pl->exec = '\0';
-      pl->exec++;
+      if (ci->values[i + 1].type == OCONFIG_TYPE_NUMBER)
+      {
+       snprintf (buffer, sizeof (buffer), "%lf",
+           ci->values[i + 1].value.number);
+      }
+      else
+      {
+       if (ci->values[i + 1].value.boolean)
+         strncpy (buffer, "true", sizeof (buffer));
+       else
+         strncpy (buffer, "false", sizeof (buffer));
+      }
+      buffer[sizeof (buffer) - 1] = '\0';
+
+      pl->argv[i] = strdup (buffer);
     }
 
-    if (*pl->exec == '\0')
+    if (pl->argv[i] == NULL)
     {
-      sfree (pl->user);
-      sfree (pl);
-      return (1);
+      ERROR ("exec plugin: strdup failed.");
+      break;
     }
+  } /* for (i) */
 
-    pl->next = pl_head;
-    pl_head = pl;
-
-    pl->group = strchr (pl->user, ':');
-    if (NULL != pl->group) {
-      *pl->group = '\0';
-      pl->group++;
+  if (i < (ci->values_num - 1))
+  {
+    while ((--i) >= 0)
+    {
+      sfree (pl->argv[i]);
     }
+    sfree (pl->argv);
+    sfree (pl->exec);
+    sfree (pl->user);
+    sfree (pl);
+    return (-1);
   }
-  else
+
+  for (i = 0; pl->argv[i] != NULL; i++)
   {
-    return (-1);
+    DEBUG ("exec plugin: argv[%i] = %s", i, pl->argv[i]);
   }
 
+  pl->next = pl_head;
+  pl_head = pl;
+
+  return (0);
+} /* int exec_config_exec }}} */
+
+static int exec_config (oconfig_item_t *ci) /* {{{ */
+{
+  int i;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *child = ci->children + i;
+    if ((strcasecmp ("Exec", child->key) == 0)
+       || (strcasecmp ("NotificationExec", child->key) == 0))
+      exec_config_exec (child);
+    else
+    {
+      WARNING ("exec plugin: Unknown config option `%s'.", child->key);
+    }
+  } /* for (i) */
+
   return (0);
-} /* int exec_config */
+} /* int exec_config }}} */
 
-static void exec_child (program_list_t *pl)
+static void exec_child (program_list_t *pl) /* {{{ */
 {
   int status;
   int uid;
   int gid;
   int egid;
-  char *arg0;
 
   struct passwd *sp_ptr;
   struct passwd sp;
@@ -224,28 +371,29 @@ static void exec_child (program_list_t *pl)
     exit (-1);
   }
 
-  arg0 = strrchr (pl->exec, '/');
-  if (arg0 != NULL)
-    arg0++;
-  if ((arg0 == NULL) || (*arg0 == '\0'))
-    arg0 = pl->exec;
-
-  status = execlp (pl->exec, arg0, (char *) 0);
+  status = execvp (pl->exec, pl->argv);
 
   ERROR ("exec plugin: exec failed: %s",
       sstrerror (errno, errbuf, sizeof (errbuf)));
   exit (-1);
-} /* void exec_child */
+} /* void exec_child }}} */
 
-static int fork_child (program_list_t *pl)
+/*
+ * Creates two pipes (one for reading, ong for writing), forks a child, sets up
+ * the pipes so that fd_in is connected to STDIN of the child and fd_out is
+ * connected to STDOUT and STDERR of the child. Then is calls `exec_child'.
+ */
+static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
 {
-  int fd_pipe[2];
+  int fd_pipe_in[2];
+  int fd_pipe_out[2];
   int status;
+  int pid;
 
   if (pl->pid != 0)
     return (-1);
 
-  status = pipe (fd_pipe);
+  status = pipe (fd_pipe_in);
   if (status != 0)
   {
     char errbuf[1024];
@@ -254,15 +402,24 @@ static int fork_child (program_list_t *pl)
     return (-1);
   }
 
-  pl->pid = fork ();
-  if (pl->pid < 0)
+  status = pipe (fd_pipe_out);
+  if (status != 0)
+  {
+    char errbuf[1024];
+    ERROR ("exec plugin: pipe failed: %s",
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    return (-1);
+  }
+
+  pid = fork ();
+  if (pid < 0)
   {
     char errbuf[1024];
     ERROR ("exec plugin: fork failed: %s",
        sstrerror (errno, errbuf, sizeof (errbuf)));
     return (-1);
   }
-  else if (pl->pid == 0)
+  else if (pid == 0)
   {
     int fd_num;
     int fd;
@@ -271,49 +428,96 @@ static int fork_child (program_list_t *pl)
     fd_num = getdtablesize ();
     for (fd = 0; fd < fd_num; fd++)
     {
-      if (fd == fd_pipe[1])
+      if ((fd == fd_pipe_in[0]) || (fd == fd_pipe_out[1]))
        continue;
       close (fd);
     }
 
-    /* Connect the pipe to STDOUT and STDERR */
-    if (fd_pipe[1] != STDOUT_FILENO)
-      dup2 (fd_pipe[1], STDOUT_FILENO);
-    if (fd_pipe[1] != STDERR_FILENO)
-      dup2 (fd_pipe[1], STDERR_FILENO);
-    if ((fd_pipe[1] != STDOUT_FILENO) && (fd_pipe[1] != STDERR_FILENO))
-      close (fd_pipe[1]);
+    /* If the `out' pipe has the filedescriptor STDIN we have to be careful
+     * with the `dup's below. So, if this is the case we have to handle the
+     * `out' pipe first. */
+    if (fd_pipe_out[1] == STDIN_FILENO)
+    {
+      int new_fileno = (fd_pipe_in[0] == STDOUT_FILENO)
+       ? STDERR_FILENO : STDOUT_FILENO;
+      dup2 (fd_pipe_out[1], new_fileno);
+      close (fd_pipe_out[1]);
+      fd_pipe_out[1] = new_fileno;
+    }
+    /* Now `fd_pipe_out[1]' is either `STDOUT' or `STDERR', but definitely not
+     * `STDIN_FILENO'. */
+
+    /* Connect the `in' pipe to STDIN */
+    if (fd_pipe_in[0] != STDIN_FILENO)
+    {
+      dup2 (fd_pipe_in[0], STDIN_FILENO);
+      close (fd_pipe_in[0]);
+      fd_pipe_in[0] = STDIN_FILENO;
+    }
+
+    /* Now connect the `out' pipe to STDOUT and STDERR */
+    if (fd_pipe_out[1] != STDOUT_FILENO)
+      dup2 (fd_pipe_out[1], STDOUT_FILENO);
+    if (fd_pipe_out[1] != STDERR_FILENO)
+      dup2 (fd_pipe_out[1], STDERR_FILENO);
+
+    /* If the pipe has some FD that's something completely different, close it
+     * now. */
+    if ((fd_pipe_out[1] != STDOUT_FILENO) && (fd_pipe_out[1] != STDERR_FILENO))
+    {
+      close (fd_pipe_out[1]);
+      fd_pipe_out[1] = STDOUT_FILENO;
+    }
 
     exec_child (pl);
     /* does not return */
   }
 
-  close (fd_pipe[1]);
-  return (fd_pipe[0]);
-} /* int fork_child */
+  close (fd_pipe_in[0]);
+  close (fd_pipe_out[1]);
 
-static int parse_line (char *buffer)
+  if (fd_in != NULL)
+    *fd_in = fd_pipe_in[1];
+  else
+    close (fd_pipe_in[1]);
+
+  if (fd_out != NULL)
+    *fd_out = fd_pipe_out[0];
+  else
+    close (fd_pipe_out[0]);
+
+  return (pid);
+} /* int fork_child }}} */
+
+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);
+  fields_num = strsplit (buffer, fields + 1, STATIC_ARRAY_SIZE(fields) - 1);
 
-  handle_putval (stdout, fields, fields_num + 1);
-  return (0);
-} /* int parse_line */
+  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));
 
-static void *exec_read_one (void *arg)
+  /* compatibility code */
+  return (handle_putval (stdout, fields, fields_num + 1));
+} /* int parse_line }}} */
+
+static void *exec_read_one (void *arg) /* {{{ */
 {
   program_list_t *pl = (program_list_t *) arg;
   int fd;
   FILE *fh;
   char buffer[1024];
+  int status;
 
-  fd = fork_child (pl);
-  if (fd < 0)
+  status = fork_child (pl, NULL, &fd);
+  if (status < 0)
     pthread_exit ((void *) 1);
+  pl->pid = status;
 
   assert (pl->pid != 0);
 
@@ -329,6 +533,7 @@ static void *exec_read_one (void *arg)
     pthread_exit ((void *) 1);
   }
 
+  buffer[0] = '\0';
   while (fgets (buffer, sizeof (buffer), fh) != NULL)
   {
     int len;
@@ -346,13 +551,102 @@ static void *exec_read_one (void *arg)
   } /* while (fgets) */
 
   fclose (fh);
+
+  if (waitpid (pl->pid, &status, 0) > 0)
+    pl->status = status;
+
+  DEBUG ("exec plugin: Child %i exited with status %i.",
+      (int) pl->pid, pl->status);
+
   pl->pid = 0;
 
+  pthread_mutex_lock (&pl_lock);
+  pl->flags &= ~PL_RUNNING;
+  pthread_mutex_unlock (&pl_lock);
+
   pthread_exit ((void *) 0);
   return (NULL);
-} /* void *exec_read_one */
+} /* void *exec_read_one }}} */
 
-static int exec_read (void)
+static void *exec_notification_one (void *arg) /* {{{ */
+{
+  program_list_t *pl = ((program_list_and_notification_t *) arg)->pl;
+  const notification_t *n = &((program_list_and_notification_t *) arg)->n;
+  int fd;
+  FILE *fh;
+  int pid;
+  int status;
+  const char *severity;
+
+  pid = fork_child (pl, &fd, NULL);
+  if (pid < 0) {
+    sfree (arg);
+    pthread_exit ((void *) 1);
+  }
+
+  fh = fdopen (fd, "w");
+  if (fh == NULL)
+  {
+    char errbuf[1024];
+    ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
+       sstrerror (errno, errbuf, sizeof (errbuf)));
+    kill (pl->pid, SIGTERM);
+    pl->pid = 0;
+    close (fd);
+    sfree (arg);
+    pthread_exit ((void *) 1);
+  }
+
+  severity = "FAILURE";
+  if (n->severity == NOTIF_WARNING)
+    severity = "WARNING";
+  else if (n->severity == NOTIF_OKAY)
+    severity = "OKAY";
+
+  fprintf (fh,
+      "Severity: %s\n"
+      "Time: %u\n",
+      severity, (unsigned int) n->time);
+
+  /* Print the optional fields */
+  if (strlen (n->host) > 0)
+    fprintf (fh, "Host: %s\n", n->host);
+  if (strlen (n->plugin) > 0)
+    fprintf (fh, "Plugin: %s\n", n->plugin);
+  if (strlen (n->plugin_instance) > 0)
+    fprintf (fh, "PluginInstance: %s\n", n->plugin_instance);
+  if (strlen (n->type) > 0)
+    fprintf (fh, "Type: %s\n", n->type);
+  if (strlen (n->type_instance) > 0)
+    fprintf (fh, "TypeInstance: %s\n", n->type_instance);
+
+  fprintf (fh, "\n%s\n", n->message);
+
+  fflush (fh);
+  fclose (fh);
+
+  waitpid (pid, &status, 0);
+
+  DEBUG ("exec plugin: Child %i exited with status %i.",
+      pid, status);
+
+  sfree (arg);
+  pthread_exit ((void *) 0);
+  return (NULL);
+} /* void *exec_notification_one }}} */
+
+static int exec_init (void) /* {{{ */
+{
+  struct sigaction sa;
+
+  memset (&sa, '\0', sizeof (sa));
+  sa.sa_handler = sigchld_handler;
+  sigaction (SIGCHLD, &sa, NULL);
+
+  return (0);
+} /* int exec_init }}} */
+
+static int exec_read (void) /* {{{ */
 {
   program_list_t *pl;
 
@@ -361,18 +655,66 @@ static int exec_read (void)
     pthread_t t;
     pthread_attr_t attr;
 
-    if (pl->pid != 0)
+    /* Only execute `normal' style executables here. */
+    if ((pl->flags & PL_NORMAL) == 0)
       continue;
 
+    pthread_mutex_lock (&pl_lock);
+    /* Skip if a child is already running. */
+    if ((pl->flags & PL_RUNNING) != 0)
+    {
+      pthread_mutex_unlock (&pl_lock);
+      continue;
+    }
+    pl->flags |= PL_RUNNING;
+    pthread_mutex_unlock (&pl_lock);
+
     pthread_attr_init (&attr);
     pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
     pthread_create (&t, &attr, exec_read_one, (void *) pl);
   } /* for (pl) */
 
   return (0);
-} /* int exec_read */
+} /* int exec_read }}} */
+
+static int exec_notification (const notification_t *n)
+{
+  program_list_t *pl;
+  program_list_and_notification_t *pln;
+
+  for (pl = pl_head; pl != NULL; pl = pl->next)
+  {
+    pthread_t t;
+    pthread_attr_t attr;
+
+    /* Only execute `notification' style executables here. */
+    if ((pl->flags & PL_NOTIF_ACTION) == 0)
+      continue;
+
+    /* Skip if a child is already running. */
+    if (pl->pid != 0)
+      continue;
+
+    pln = (program_list_and_notification_t *) malloc (sizeof
+       (program_list_and_notification_t));
+    if (pln == NULL)
+    {
+      ERROR ("exec plugin: malloc failed.");
+      continue;
+    }
+
+    pln->pl = pl;
+    memcpy (&pln->n, n, sizeof (notification_t));
+
+    pthread_attr_init (&attr);
+    pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+    pthread_create (&t, &attr, exec_notification_one, (void *) pln);
+  } /* for (pl) */
+
+  return (0);
+} /* int exec_notification */
 
-static int exec_shutdown (void)
+static int exec_shutdown (void) /* {{{ */
 {
   program_list_t *pl;
   program_list_t *next;
@@ -396,15 +738,17 @@ static int exec_shutdown (void)
   pl_head = NULL;
 
   return (0);
-} /* int exec_shutdown */
+} /* int exec_shutdown }}} */
 
 void module_register (void)
 {
-  plugin_register_config ("exec", exec_config, config_keys, config_keys_num);
+  plugin_register_complex_config ("exec", exec_config);
+  plugin_register_init ("exec", exec_init);
   plugin_register_read ("exec", exec_read);
+  plugin_register_notification ("exec", exec_notification);
   plugin_register_shutdown ("exec", exec_shutdown);
 } /* void module_register */
 
 /*
- * vim:shiftwidth=2:softtabstop=2:tabstop=8
+ * vim:shiftwidth=2:softtabstop=2:tabstop=8:fdm=marker
  */
index 09f6bbe..36ada53 100644 (file)
@@ -48,9 +48,9 @@ static const char *config_keys[] =
 {
        "Host",
        "Port",
-       NULL
+       "TranslateDevicename"
 };
-static int config_keys_num = 2;
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
 
 typedef struct hddname
 {
@@ -63,6 +63,7 @@ typedef struct hddname
 static hddname_t *first_hddname = NULL;
 static char *hddtemp_host = NULL;
 static char hddtemp_port[16];
+static int translate_devicename = 1;
 
 /*
  * NAME
@@ -228,6 +229,15 @@ static int hddtemp_config (const char *key, const char *value)
                        strncpy (hddtemp_port, value, sizeof (hddtemp_port));
                hddtemp_port[sizeof (hddtemp_port) - 1] = '\0';
        }
+       else if (strcasecmp (key, "TranslateDevicename") == 0)
+       {
+               if ((strcasecmp ("true", value) == 0)
+                               || (strcasecmp ("yes", value) == 0)
+                               || (strcasecmp ("on", value) == 0))
+                       translate_devicename = 1;
+               else
+                       translate_devicename = 0;
+       }
        else
        {
                return (-1);
@@ -395,14 +405,14 @@ static int hddtemp_init (void)
 } /* int hddtemp_init */
 
 /*
- * hddtemp_get_name
+ * hddtemp_get_major_minor
  *
  * Description:
  *   Try to "cook" a bit the drive name as returned
  *   by the hddtemp daemon. The intend is to transform disk
  *   names into <major>-<minor> when possible.
  */
-static char *hddtemp_get_name (char *drive)
+static char *hddtemp_get_major_minor (char *drive)
 {
        hddname_t *list;
        char *ret;
@@ -477,7 +487,7 @@ static int hddtemp_read (void)
 
        for (i = 0; i < num_disks; i++)
        {
-               char *name, *submit_name;
+               char *name, *major_minor;
                double temperature;
                char *mode;
 
@@ -494,10 +504,11 @@ static int hddtemp_read (void)
                if (mode[0] == 'F')
                        temperature = (temperature - 32.0) * 5.0 / 9.0;
 
-               if ((submit_name = hddtemp_get_name (name)) != NULL)
+               if (translate_devicename
+                               && (major_minor = hddtemp_get_major_minor (name)) != NULL)
                {
-                       hddtemp_submit (submit_name, temperature);
-                       free (submit_name);
+                       hddtemp_submit (major_minor, temperature);
+                       free (major_minor);
                }
                else
                {
index 5fa1f40..72b4481 100644 (file)
@@ -106,7 +106,7 @@ static int iptables_config (const char *key, const char *value)
                chain = fields[1];
 
                table_len = strlen (table);
-               if (table_len >= sizeof(temp.table))
+               if ((unsigned int)table_len >= sizeof(temp.table))
                {
                        ERROR ("Table `%s' too long.", table);
                        free (value_copy);
@@ -116,7 +116,7 @@ static int iptables_config (const char *key, const char *value)
                temp.table[table_len] = '\0';
 
                chain_len = strlen (chain);
-               if (chain_len >= sizeof(temp.chain))
+               if ((unsigned int)chain_len >= sizeof(temp.chain))
                {
                        ERROR ("Chain `%s' too long.", chain);
                        free (value_copy);
@@ -224,7 +224,7 @@ static int submit_match (const struct ipt_entry_match *match,
 
     status = snprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
            "%s-%s", chain->table, chain->chain);
-    if ((status >= sizeof (vl.plugin_instance)) || (status < 1))
+    if ((status < 1) || ((unsigned int)status >= sizeof (vl.plugin_instance)))
        return (0);
 
     if (chain->name[0] != '\0')
@@ -284,7 +284,7 @@ static void submit_chain( iptc_handle_t *handle, ip_chain_t *chain ) {
 static int iptables_read (void)
 {
     int i;
-    static complain_t complaint;
+    int num_failures = 0;
 
     /* Init the iptc handle structure and query the correct table */    
     for (i = 0; i < chain_num; i++)
@@ -295,26 +295,24 @@ static int iptables_read (void)
        chain = chain_list[i];
        if (!chain)
        {
-           DEBUG ("chain == NULL");
+           DEBUG ("iptables plugin: chain == NULL");
            continue;
        }
 
-       handle = iptc_init( chain->table );
+       handle = iptc_init (chain->table);
        if (!handle)
        {
-           DEBUG ("iptc_init (%s) failed: %s", chain->table, iptc_strerror (errno));
-           plugin_complain (LOG_ERR, &complaint, "iptc_init (%s) failed: %s",
+           ERROR ("iptables plugin: iptc_init (%s) failed: %s",
                    chain->table, iptc_strerror (errno));
+           num_failures++;
            continue;
        }
-       plugin_relief (LOG_INFO, &complaint, "iptc_init (%s) succeeded",
-               chain->table);
 
        submit_chain (&handle, chain);
        iptc_free (&handle);
-    }
+    } /* for (i = 0 .. chain_num) */
 
-    return (0);
+    return ((num_failures < chain_num) ? 0 : -1);
 } /* int iptables_read */
 
 static int iptables_shutdown (void)
index b6b8c4c..9eb1de4 100644 (file)
--- a/src/irq.c
+++ b/src/irq.c
@@ -111,7 +111,7 @@ static int check_ignore_irq (const unsigned int irq)
        if (irq_list_num < 1)
                return (0);
 
-       for (i = 0; i < irq_list_num; i++)
+       for (i = 0; (unsigned int)i < irq_list_num; i++)
                if (irq == irq_list[i])
                        return (irq_list_action);
 
@@ -137,7 +137,7 @@ static void irq_submit (unsigned int irq, counter_t value)
 
        status = snprintf (vl.type_instance, sizeof (vl.type_instance),
                        "%u", irq);
-       if ((status < 1) || (status >= sizeof (vl.type_instance)))
+       if ((status < 1) || ((unsigned int)status >= sizeof (vl.type_instance)))
                return;
 
        plugin_dispatch_values ("irq", &vl);
index d237273..49cd139 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * oconfig - src/parser.y
- * Copyright (C) 2007  Florian octo Forster <octo at verplant.org>
+ * Copyright (C) 2007,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
@@ -112,6 +112,12 @@ option:
        ;
 
 block_begin:
+       OPENBRAC identifier CLOSEBRAC EOL
+       {
+        memset (&$$, '\0', sizeof ($$));
+        $$.key = $2;
+       }
+       |
        OPENBRAC identifier argument_list CLOSEBRAC EOL
        {
         memset (&$$, '\0', sizeof ($$));
@@ -154,7 +160,7 @@ statement_list:
        statement_list statement
        {
         $$ = $1;
-        if ($2.values_num > 0)
+        if (($2.values_num > 0) || ($2.children_num > 0))
         {
                 $$.statement_num++;
                 $$.statement = realloc ($$.statement, $$.statement_num * sizeof (oconfig_item_t));
@@ -163,7 +169,7 @@ statement_list:
        }
        | statement
        {
-        if ($1.values_num > 0)
+        if (($1.values_num > 0) || ($1.children_num > 0))
         {
                 $$.statement = malloc (sizeof (oconfig_item_t));
                 $$.statement[0] = $1;
@@ -216,8 +222,8 @@ static char *unquote (const char *orig)
        if ((len < 2) || (ret[0] != '"') || (ret[len - 1] != '"'))
                return (ret);
 
-       ret++;
        len -= 2;
+       memmove (ret, ret + 1, len);
        ret[len] = '\0';
 
        for (i = 0; i < len; i++)
diff --git a/src/libvirt.c b/src/libvirt.c
new file mode 100644 (file)
index 0000000..327536a
--- /dev/null
@@ -0,0 +1,787 @@
+/**
+ * collectd - src/libvirt.c
+ * Copyright (C) 2006-2008  Red Hat Inc.
+ *
+ * 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:
+ *   Richard W.M. Jones <rjones@redhat.com>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_ignorelist.h"
+
+#include <libvirt/libvirt.h>
+#include <libvirt/virterror.h>
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+
+static const char *config_keys[] = {
+    "Connection",
+
+    "RefreshInterval",
+
+    "Domain",
+    "BlockDevice",
+    "InterfaceDevice",
+    "IgnoreSelected",
+
+    "HostnameFormat",
+
+    NULL
+};
+#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
+
+/* Connection. */
+static virConnectPtr conn = 0;
+
+/* Seconds between list refreshes, 0 disables completely. */
+static int interval = 60;
+
+/* List of domains, if specified. */
+static ignorelist_t *il_domains = NULL;
+/* List of block devices, if specified. */
+static ignorelist_t *il_block_devices = NULL;
+/* List of network interface devices, if specified. */
+static ignorelist_t *il_interface_devices = NULL;
+
+static int ignore_device_match (ignorelist_t *,
+                                const char *domname, const char *devpath);
+
+/* Actual list of domains found on last refresh. */
+static virDomainPtr *domains = NULL;
+static int nr_domains = 0;
+
+static void free_domains (void);
+static int add_domain (virDomainPtr dom);
+
+/* Actual list of block devices found on last refresh. */
+struct block_device {
+    virDomainPtr dom;           /* domain */
+    char *path;                 /* name of block device */
+};
+
+static struct block_device *block_devices = NULL;
+static int nr_block_devices = 0;
+
+static void free_block_devices (void);
+static int add_block_device (virDomainPtr dom, const char *path);
+
+/* Actual list of network interfaces found on last refresh. */
+struct interface_device {
+    virDomainPtr dom;           /* domain */
+    char *path;                 /* name of interface device */
+};
+
+static struct interface_device *interface_devices = NULL;
+static int nr_interface_devices = 0;
+
+static void free_interface_devices (void);
+static int add_interface_device (virDomainPtr dom, const char *path);
+
+/* HostnameFormat. */
+#define HF_MAX_FIELDS 3
+
+enum hf_field {
+    hf_none = 0,
+    hf_hostname,
+    hf_name,
+    hf_uuid
+};
+
+static enum hf_field hostname_format[HF_MAX_FIELDS] =
+    { hf_name };
+
+/* Time that we last refreshed. */
+static time_t last_refresh = (time_t) 0;
+
+static int refresh_lists (void);
+
+/* Submit functions. */
+static void cpu_submit (unsigned long long cpu_time,
+                        time_t t,
+                        virDomainPtr dom, const char *type);
+static void vcpu_submit (unsigned long long cpu_time,
+                         time_t t,
+                         virDomainPtr dom, int vcpu_nr, const char *type);
+static void submit_counter2 (const char *type, counter_t v0, counter_t v1,
+             time_t t,
+             virDomainPtr dom, const char *devname);
+
+/* ERROR(...) macro for virterrors. */
+#define VIRT_ERROR(conn,s) do {                 \
+        virErrorPtr err;                        \
+        err = (conn) ? virConnGetLastError ((conn)) : virGetLastError (); \
+        if (err) ERROR ("%s: %s", (s), err->message);                   \
+    } while(0)
+
+static int
+lv_init (void)
+{
+    if (virInitialize () != 0)
+        return -1;
+
+       return 0;
+}
+
+static int
+lv_config (const char *key, const char *value)
+{
+    if (virInitialize () != 0)
+        return 1;
+
+    if (il_domains == NULL)
+        il_domains = ignorelist_create (1);
+    if (il_block_devices == NULL)
+        il_block_devices = ignorelist_create (1);
+    if (il_interface_devices == NULL)
+        il_interface_devices = ignorelist_create (1);
+
+    if (strcasecmp (key, "Connection") == 0) {
+        if (conn != 0) {
+            ERROR ("Connection may only be given once in config file");
+            return 1;
+        }
+        conn = virConnectOpenReadOnly (value);
+        if (!conn) {
+            VIRT_ERROR (NULL, "connection failed");
+            return 1;
+        }
+        return 0;
+    }
+
+    if (strcasecmp (key, "RefreshInterval") == 0) {
+        char *eptr = NULL;
+        interval = strtol (value, &eptr, 10);
+        if (eptr == NULL || *eptr != '\0') return 1;
+        return 0;
+    }
+
+    if (strcasecmp (key, "Domain") == 0) {
+        if (ignorelist_add (il_domains, value)) return 1;
+        return 0;
+    }
+    if (strcasecmp (key, "BlockDevice") == 0) {
+        if (ignorelist_add (il_block_devices, value)) return 1;
+        return 0;
+    }
+    if (strcasecmp (key, "InterfaceDevice") == 0) {
+        if (ignorelist_add (il_interface_devices, value)) return 1;
+        return 0;
+    }
+
+    if (strcasecmp (key, "IgnoreSelected") == 0) {
+        if (strcasecmp (value, "True") == 0 ||
+            strcasecmp (value, "Yes") == 0 ||
+            strcasecmp (value, "On") == 0)
+        {
+            ignorelist_set_invert (il_domains, 0);
+            ignorelist_set_invert (il_block_devices, 0);
+            ignorelist_set_invert (il_interface_devices, 0);
+        }
+        else
+        {
+            ignorelist_set_invert (il_domains, 1);
+            ignorelist_set_invert (il_block_devices, 1);
+            ignorelist_set_invert (il_interface_devices, 1);
+        }
+        return 0;
+    }
+
+    if (strcasecmp (key, "HostnameFormat") == 0) {
+        char *value_copy;
+        char *fields[HF_MAX_FIELDS];
+        int i, n;
+
+        value_copy = strdup (value);
+        if (value_copy == NULL) {
+            ERROR ("libvirt plugin: strdup failed.");
+            return -1;
+        }
+
+        n = strsplit (value_copy, fields, HF_MAX_FIELDS);
+        if (n < 1) {
+            free (value_copy);
+            ERROR ("HostnameFormat: no fields");
+            return -1;
+        }
+
+        for (i = 0; i < n; ++i) {
+            if (strcasecmp (fields[i], "hostname") == 0)
+                hostname_format[i] = hf_hostname;
+            else if (strcasecmp (fields[i], "name") == 0)
+                hostname_format[i] = hf_name;
+            else if (strcasecmp (fields[i], "uuid") == 0)
+                hostname_format[i] = hf_uuid;
+            else {
+                free (value_copy);
+                ERROR ("unknown HostnameFormat field: %s", fields[i]);
+                return -1;
+            }
+        }
+        free (value_copy);
+
+        for (i = n; i < HF_MAX_FIELDS; ++i)
+            hostname_format[i] = hf_none;
+
+        return 0;
+    }
+
+    /* Unrecognised option. */
+    return -1;
+}
+
+static int
+lv_read (void)
+{
+    time_t t;
+    int i;
+
+    if (conn == NULL) {
+        ERROR ("libvirt plugin: Not connected. Use Connection in "
+                "config file to supply connection URI.  For more information "
+                "see <http://libvirt.org/uri.html>");
+        return -1;
+    }
+
+    time (&t);
+
+    /* Need to refresh domain or device lists? */
+    if ((last_refresh == (time_t) 0) ||
+            ((interval > 0) && ((last_refresh + interval) <= t))) {
+        if (refresh_lists () != 0)
+            return -1;
+        last_refresh = t;
+    }
+
+#if 0
+    for (i = 0; i < nr_domains; ++i)
+        fprintf (stderr, "domain %s\n", virDomainGetName (domains[i]));
+    for (i = 0; i < nr_block_devices; ++i)
+        fprintf  (stderr, "block device %d %s:%s\n",
+                  i, virDomainGetName (block_devices[i].dom),
+                  block_devices[i].path);
+    for (i = 0; i < nr_interface_devices; ++i)
+        fprintf (stderr, "interface device %d %s:%s\n",
+                 i, virDomainGetName (interface_devices[i].dom),
+                 interface_devices[i].path);
+#endif
+
+    /* Get CPU usage, VCPU usage for each domain. */
+    for (i = 0; i < nr_domains; ++i) {
+        virDomainInfo info;
+        virVcpuInfoPtr vinfo = NULL;
+        int j;
+
+        if (virDomainGetInfo (domains[i], &info) != 0)
+            continue;
+
+        cpu_submit (info.cpuTime, t, domains[i], "virt_cpu_total");
+
+        vinfo = malloc (info.nrVirtCpu * sizeof vinfo[0]);
+        if (vinfo == NULL) {
+            ERROR ("libvirt plugin: malloc failed.");
+            continue;
+        }
+
+        if (virDomainGetVcpus (domains[i], vinfo, info.nrVirtCpu,
+                    NULL, 0) != 0) {
+            free (vinfo);
+            continue;
+        }
+
+        for (j = 0; j < info.nrVirtCpu; ++j)
+            vcpu_submit (vinfo[j].cpuTime,
+                    t, domains[i], vinfo[j].number, "virt_vcpu");
+
+        free (vinfo);
+    }
+
+    /* Get block device stats for each domain. */
+    for (i = 0; i < nr_block_devices; ++i) {
+        struct _virDomainBlockStats stats;
+
+        if (virDomainBlockStats (block_devices[i].dom, block_devices[i].path,
+                    &stats, sizeof stats) != 0)
+            continue;
+
+        if ((stats.rd_req != -1) && (stats.wr_req != -1))
+            submit_counter2 ("disk_ops",
+                    (counter_t) stats.rd_req, (counter_t) stats.wr_req,
+                    t, block_devices[i].dom, block_devices[i].path);
+
+        if ((stats.rd_bytes != -1) && (stats.wr_bytes != -1))
+            submit_counter2 ("disk_octets",
+                    (counter_t) stats.rd_bytes, (counter_t) stats.wr_bytes,
+                    t, block_devices[i].dom, block_devices[i].path);
+    } /* for (nr_block_devices) */
+
+    /* Get interface stats for each domain. */
+    for (i = 0; i < nr_interface_devices; ++i) {
+        struct _virDomainInterfaceStats stats;
+
+        if (virDomainInterfaceStats (interface_devices[i].dom,
+                    interface_devices[i].path,
+                    &stats, sizeof stats) != 0)
+            continue;
+
+       if ((stats.rx_bytes != -1) && (stats.tx_bytes != -1))
+           submit_counter2 ("if_octets",
+                   (counter_t) stats.rx_bytes, (counter_t) stats.tx_bytes,
+                   t, interface_devices[i].dom, interface_devices[i].path);
+
+       if ((stats.rx_packets != -1) && (stats.tx_packets != -1))
+           submit_counter2 ("if_packets",
+                   (counter_t) stats.rx_packets, (counter_t) stats.tx_packets,
+                   t, interface_devices[i].dom, interface_devices[i].path);
+
+       if ((stats.rx_errs != -1) && (stats.tx_errs != -1))
+           submit_counter2 ("if_errors",
+                   (counter_t) stats.rx_errs, (counter_t) stats.tx_errs,
+                   t, interface_devices[i].dom, interface_devices[i].path);
+
+       if ((stats.rx_drop != -1) && (stats.tx_drop != -1))
+           submit_counter2 ("if_dropped",
+                   (counter_t) stats.rx_drop, (counter_t) stats.tx_drop,
+                   t, interface_devices[i].dom, interface_devices[i].path);
+    } /* for (nr_interface_devices) */
+
+    return 0;
+}
+
+static int
+refresh_lists (void)
+{
+    int n;
+
+    n = virConnectNumOfDomains (conn);
+    if (n < 0) {
+        VIRT_ERROR (conn, "reading number of domains");
+        return -1;
+    }
+
+    if (n > 0) {
+        int i;
+        int *domids;
+
+        /* Get list of domains. */
+        domids = malloc (sizeof (int) * n);
+        if (domids == 0) {
+            ERROR ("libvirt plugin: malloc failed.");
+            return -1;
+        }
+
+        n = virConnectListDomains (conn, domids, n);
+        if (n < 0) {
+            VIRT_ERROR (conn, "reading list of domains");
+            free (domids);
+            return -1;
+        }
+
+        free_block_devices ();
+        free_interface_devices ();
+        free_domains ();
+
+        /* Fetch each domain and add it to the list, unless ignore. */
+        for (i = 0; i < n; ++i) {
+            virDomainPtr dom = NULL;
+            const char *name;
+            char *xml = NULL;
+            xmlDocPtr xml_doc = NULL;
+            xmlXPathContextPtr xpath_ctx = NULL;
+            xmlXPathObjectPtr xpath_obj = NULL;
+            int j;
+
+            dom = virDomainLookupByID (conn, domids[i]);
+            if (dom == NULL) {
+                VIRT_ERROR (conn, "virDomainLookupByID");
+                /* Could be that the domain went away -- ignore it anyway. */
+                continue;
+            }
+
+            name = virDomainGetName (dom);
+            if (name == NULL) {
+                VIRT_ERROR (conn, "virDomainGetName");
+                goto cont;
+            }
+
+            if (il_domains && ignorelist_match (il_domains, name) != 0)
+                goto cont;
+
+            if (add_domain (dom) < 0) {
+                ERROR ("libvirt plugin: malloc failed.");
+                goto cont;
+            }
+
+            /* Get a list of devices for this domain. */
+            xml = virDomainGetXMLDesc (dom, 0);
+            if (!xml) {
+                VIRT_ERROR (conn, "virDomainGetXMLDesc");
+                goto cont;
+            }
+
+            /* Yuck, XML.  Parse out the devices. */
+            xml_doc = xmlReadDoc ((xmlChar *) xml, NULL, NULL, XML_PARSE_NONET);
+            if (xml_doc == NULL) {
+                VIRT_ERROR (conn, "xmlReadDoc");
+                goto cont;
+            }
+
+            xpath_ctx = xmlXPathNewContext (xml_doc);
+
+            /* Block devices. */
+            xpath_obj = xmlXPathEval
+                ((xmlChar *) "/domain/devices/disk/target[@dev]",
+                 xpath_ctx);
+            if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
+                xpath_obj->nodesetval == NULL)
+                goto cont;
+
+            for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
+                xmlNodePtr node;
+                char *path = NULL;
+
+                node = xpath_obj->nodesetval->nodeTab[j];
+                if (!node) continue;
+                path = (char *) xmlGetProp (node, (xmlChar *) "dev");
+                if (!path) continue;
+
+                if (il_block_devices &&
+                    ignore_device_match (il_block_devices, name, path) != 0)
+                    goto cont2;
+
+                add_block_device (dom, path);
+            cont2:
+                if (path) xmlFree (path);
+            }
+            xmlXPathFreeObject (xpath_obj);
+
+            /* Network interfaces. */
+            xpath_obj = xmlXPathEval
+                ((xmlChar *) "/domain/devices/interface/target[@dev]",
+                 xpath_ctx);
+            if (xpath_obj == NULL || xpath_obj->type != XPATH_NODESET ||
+                xpath_obj->nodesetval == NULL)
+                goto cont;
+
+            for (j = 0; j < xpath_obj->nodesetval->nodeNr; ++j) {
+                xmlNodePtr node;
+                char *path = NULL;
+
+                node = xpath_obj->nodesetval->nodeTab[j];
+                if (!node) continue;
+                path = (char *) xmlGetProp (node, (xmlChar *) "dev");
+                if (!path) continue;
+
+                if (il_interface_devices &&
+                    ignore_device_match (il_interface_devices, name, path) != 0)
+                    goto cont3;
+
+                add_interface_device (dom, path);
+            cont3:
+                if (path) xmlFree (path);
+            }
+
+        cont:
+            if (xpath_obj) xmlXPathFreeObject (xpath_obj);
+            if (xpath_ctx) xmlXPathFreeContext (xpath_ctx);
+            if (xml_doc) xmlFreeDoc (xml_doc);
+            if (xml) free (xml);
+        }
+
+        free (domids);
+    }
+
+    return 0;
+}
+
+static void
+free_domains ()
+{
+    int i;
+
+    if (domains) {
+        for (i = 0; i < nr_domains; ++i)
+            virDomainFree (domains[i]);
+        free (domains);
+    }
+    domains = NULL;
+    nr_domains = 0;
+}
+
+static int
+add_domain (virDomainPtr dom)
+{
+    virDomainPtr *new_ptr;
+    int new_size = sizeof (domains[0]) * (nr_domains+1);
+
+    if (domains)
+        new_ptr = realloc (domains, new_size);
+    else
+        new_ptr = malloc (new_size);
+
+    if (new_ptr == NULL)
+        return -1;
+
+    domains = new_ptr;
+    domains[nr_domains] = dom;
+    return nr_domains++;
+}
+
+static void
+free_block_devices ()
+{
+    int i;
+
+    if (block_devices) {
+        for (i = 0; i < nr_block_devices; ++i)
+            free (block_devices[i].path);
+        free (block_devices);
+    }
+    block_devices = NULL;
+    nr_block_devices = 0;
+}
+
+static int
+add_block_device (virDomainPtr dom, const char *path)
+{
+    struct block_device *new_ptr;
+    int new_size = sizeof (block_devices[0]) * (nr_block_devices+1);
+    char *path_copy;
+
+    path_copy = strdup (path);
+    if (!path_copy)
+        return -1;
+
+    if (block_devices)
+        new_ptr = realloc (block_devices, new_size);
+    else
+        new_ptr = malloc (new_size);
+
+    if (new_ptr == NULL) {
+        free (path_copy);
+        return -1;
+    }
+    block_devices = new_ptr;
+    block_devices[nr_block_devices].dom = dom;
+    block_devices[nr_block_devices].path = path_copy;
+    return nr_block_devices++;
+}
+
+static void
+free_interface_devices ()
+{
+    int i;
+
+    if (interface_devices) {
+        for (i = 0; i < nr_interface_devices; ++i)
+            free (interface_devices[i].path);
+        free (interface_devices);
+    }
+    interface_devices = NULL;
+    nr_interface_devices = 0;
+}
+
+static int
+add_interface_device (virDomainPtr dom, const char *path)
+{
+    struct interface_device *new_ptr;
+    int new_size = sizeof (interface_devices[0]) * (nr_interface_devices+1);
+    char *path_copy;
+
+    path_copy = strdup (path);
+    if (!path_copy) return -1;
+
+    if (interface_devices)
+        new_ptr = realloc (interface_devices, new_size);
+    else
+        new_ptr = malloc (new_size);
+
+    if (new_ptr == NULL) {
+        free (path_copy);
+        return -1;
+    }
+    interface_devices = new_ptr;
+    interface_devices[nr_interface_devices].dom = dom;
+    interface_devices[nr_interface_devices].path = path_copy;
+    return nr_interface_devices++;
+}
+
+static int
+ignore_device_match (ignorelist_t *il, const char *domname, const char *devpath)
+{
+    char *name;
+    int n, r;
+
+    n = sizeof (char) * (strlen (domname) + strlen (devpath) + 2);
+    name = malloc (n);
+    if (name == NULL) {
+        ERROR ("libvirt plugin: malloc failed.");
+        return 0;
+    }
+    snprintf (name, n, "%s:%s", domname, devpath);
+    r = ignorelist_match (il, name);
+    free (name);
+    return r;
+}
+
+static void
+init_value_list (value_list_t *vl, time_t t, virDomainPtr dom)
+{
+    int i, n;
+    const char *name;
+    char uuid[VIR_UUID_STRING_BUFLEN];
+    char  *host_ptr;
+    size_t host_len;
+
+    vl->time = t;
+    vl->interval = interval_g;
+
+    strncpy (vl->plugin, "libvirt", sizeof (vl->plugin));
+    vl->plugin[sizeof (vl->plugin) - 1] = '\0';
+
+    vl->host[0] = '\0';
+    host_ptr = vl->host;
+    host_len = sizeof (vl->host);
+
+    /* Construct the hostname field according to HostnameFormat. */
+    for (i = 0; i < HF_MAX_FIELDS; ++i) {
+        if (hostname_format[i] == hf_none)
+            continue;
+
+        n = DATA_MAX_NAME_LEN - strlen (vl->host) - 2;
+
+        if (i > 0 && n >= 1) {
+            strcat (vl->host, ":");
+            n--;
+        }
+
+        switch (hostname_format[i]) {
+        case hf_none: break;
+        case hf_hostname:
+            strncat (vl->host, hostname_g, n);
+            break;
+        case hf_name:
+            name = virDomainGetName (dom);
+            if (name)
+                strncat (vl->host, name, n);
+            break;
+        case hf_uuid:
+            if (virDomainGetUUIDString (dom, uuid) == 0)
+                strncat (vl->host, uuid, n);
+            break;
+        }
+    }
+
+    vl->host[sizeof (vl->host) - 1] = '\0';
+} /* void init_value_list */
+
+static void
+cpu_submit (unsigned long long cpu_time,
+            time_t t,
+            virDomainPtr dom, const char *type)
+{
+    value_t values[1];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    init_value_list (&vl, t, dom);
+
+    values[0].counter = cpu_time;
+
+    vl.values = values;
+    vl.values_len = 1;
+
+    plugin_dispatch_values (type, &vl);
+}
+
+static void
+vcpu_submit (counter_t cpu_time,
+             time_t t,
+             virDomainPtr dom, int vcpu_nr, const char *type)
+{
+    value_t values[1];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    init_value_list (&vl, t, dom);
+
+    values[0].counter = cpu_time;
+    vl.values = values;
+    vl.values_len = 1;
+
+    snprintf (vl.type_instance, sizeof (vl.type_instance), "%d", vcpu_nr);
+    vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+
+    plugin_dispatch_values (type, &vl);
+}
+
+static void
+submit_counter2 (const char *type, counter_t v0, counter_t v1,
+             time_t t,
+             virDomainPtr dom, const char *devname)
+{
+    value_t values[2];
+    value_list_t vl = VALUE_LIST_INIT;
+
+    init_value_list (&vl, t, dom);
+
+    values[0].counter = v0;
+    values[1].counter = v1;
+    vl.values = values;
+    vl.values_len = 2;
+
+    strncpy (vl.type_instance, devname, sizeof (vl.type_instance));
+    vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+
+    plugin_dispatch_values (type, &vl);
+} /* void submit_counter2 */
+
+static int
+lv_shutdown (void)
+{
+    free_block_devices ();
+    free_interface_devices ();
+    free_domains ();
+
+    if (conn != NULL)
+       virConnectClose (conn);
+    conn = NULL;
+
+    ignorelist_free (il_domains);
+    il_domains = NULL;
+    ignorelist_free (il_block_devices);
+    il_block_devices = NULL;
+    ignorelist_free (il_interface_devices);
+    il_interface_devices = NULL;
+
+    return 0;
+}
+
+void
+module_register (void)
+{
+    plugin_register_config ("libvirt",
+           lv_config,
+           config_keys, NR_CONFIG_KEYS);
+    plugin_register_init ("libvirt", lv_init);
+    plugin_register_read ("libvirt", lv_read);
+    plugin_register_shutdown ("libvirt", lv_shutdown);
+}
+
+/*
+ * vim: shiftwidth=4 tabstop=8 softtabstop=4 expandtab fdm=marker
+ */
index 2b546ac..36ac58d 100644 (file)
@@ -1,7 +1,7 @@
 /**
  * collectd - src/logfile.c
  * Copyright (C) 2007  Sebastian Harl
- * Copyright (C) 2007  Florian Forster
+ * Copyright (C) 2007,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
@@ -27,6 +27,8 @@
 
 #include <pthread.h>
 
+#define DEFAULT_LOGFILE LOCALSTATEDIR"/log/collectd.log"
+
 #if COLLECT_DEBUG
 static int log_level = LOG_DEBUG;
 #else
@@ -85,20 +87,15 @@ static int logfile_config (const char *key, const char *value)
        return 0;
 } /* int logfile_config (const char *, const char *) */
 
-static void logfile_log (int severity, const char *msg)
+static void logfile_print (const char *msg, time_t timestamp_time)
 {
        FILE *fh;
        int do_close = 0;
-       time_t timestamp_time;
        struct tm timestamp_tm;
        char timestamp_str[64];
 
-       if (severity > log_level)
-               return;
-
        if (print_timestamp)
        {
-               timestamp_time = time (NULL);
                localtime_r (&timestamp_time, &timestamp_tm);
 
                strftime (timestamp_str, sizeof (timestamp_str), "%Y-%m-%d %H:%M:%S",
@@ -108,7 +105,12 @@ static void logfile_log (int severity, const char *msg)
 
        pthread_mutex_lock (&file_lock);
 
-       if ((log_file == NULL) || (strcasecmp (log_file, "stderr") == 0))
+       if (log_file == NULL)
+       {
+               fh = fopen (DEFAULT_LOGFILE, "a");
+               do_close = 1;
+       }
+       else if (strcasecmp (log_file, "stderr") == 0)
                fh = stderr;
        else if (strcasecmp (log_file, "stdout") == 0)
                fh = stdout;
@@ -122,7 +124,7 @@ static void logfile_log (int severity, const char *msg)
        {
                        char errbuf[1024];
                        fprintf (stderr, "logfile plugin: fopen (%s) failed: %s\n",
-                                       (log_file == NULL) ? "<null>" : log_file,
+                                       (log_file == NULL) ? DEFAULT_LOGFILE : log_file,
                                        sstrerror (errno, errbuf, sizeof (errbuf)));
        }
        else
@@ -139,13 +141,61 @@ static void logfile_log (int severity, const char *msg)
        pthread_mutex_unlock (&file_lock);
 
        return;
+} /* void logfile_print */
+
+static void logfile_log (int severity, const char *msg)
+{
+       if (severity > log_level)
+               return;
+
+       logfile_print (msg, time (NULL));
 } /* void logfile_log (int, const char *) */
 
+static int logfile_notification (const notification_t *n)
+{
+       char  buf[1024] = "";
+       char *buf_ptr = buf;
+       int   buf_len = sizeof (buf);
+       int status;
+
+       status = snprintf (buf_ptr, buf_len, "Notification: severity = %s",
+                       (n->severity == NOTIF_FAILURE) ? "FAILURE"
+                       : ((n->severity == NOTIF_WARNING) ? "WARNING"
+                               : ((n->severity == NOTIF_OKAY) ? "OKAY" : "UNKNOWN")));
+       if (status > 0)
+       {
+               buf_ptr += status;
+               buf_len -= status;
+       }
+
+#define APPEND(bufptr, buflen, key, value) \
+       if ((buflen > 0) && (strlen (value) > 0)) { \
+               int status = snprintf (bufptr, buflen, ", %s = %s", key, value); \
+               if (status > 0) { \
+                       bufptr += status; \
+                       buflen -= status; \
+               } \
+       }
+       APPEND (buf_ptr, buf_len, "host", n->host);
+       APPEND (buf_ptr, buf_len, "plugin", n->plugin);
+       APPEND (buf_ptr, buf_len, "plugin_instance", n->plugin_instance);
+       APPEND (buf_ptr, buf_len, "type", n->type);
+       APPEND (buf_ptr, buf_len, "type_instance", n->type_instance);
+       APPEND (buf_ptr, buf_len, "message", n->message);
+
+       buf[sizeof (buf) - 1] = '\0';
+
+       logfile_print (buf, n->time);
+
+       return (0);
+} /* int logfile_notification */
+
 void module_register (void)
 {
        plugin_register_config ("logfile", logfile_config,
                        config_keys, config_keys_num);
        plugin_register_log ("logfile", logfile_log);
+       plugin_register_notification ("logfile", logfile_notification);
 } /* void module_register (void) */
 
 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */
index bad1a38..50d7363 100644 (file)
@@ -240,7 +240,7 @@ static void trim_spaces (char *s)
 {
        size_t l;
 
-       for (l = strlen (s) - 1; (l > 0) && isspace (s[l]); l--)
+       for (l = strlen (s) - 1; (l > 0) && isspace ((int) s[l]); l--)
                s[l] = '\0';
 }
 
index 34a1669..b67928c 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/network.c
- * Copyright (C) 2005-2007  Florian octo Forster
+ * Copyright (C) 2005-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
@@ -679,15 +679,18 @@ static int parse_packet (void *buffer, int buffer_len)
 
        value_list_t vl = VALUE_LIST_INIT;
        char type[DATA_MAX_NAME_LEN];
+       notification_t n;
 
        DEBUG ("network plugin: parse_packet: buffer = %p; buffer_len = %i;",
                        buffer, buffer_len);
 
        memset (&vl, '\0', sizeof (vl));
        memset (&type, '\0', sizeof (type));
+       memset (&n, '\0', sizeof (n));
        status = 0;
 
-       while ((status == 0) && (buffer_len > sizeof (part_header_t)))
+       while ((status == 0) && (0 < buffer_len)
+                       && ((unsigned int) buffer_len > sizeof (part_header_t)))
        {
                uint16_t pkg_length;
                uint16_t pkg_type;
@@ -735,14 +738,19 @@ static int parse_packet (void *buffer, int buffer_len)
                else if (pkg_type == TYPE_TIME)
                {
                        uint64_t tmp = 0;
-                       status = parse_part_number (&buffer, &buffer_len, &tmp);
+                       status = parse_part_number (&buffer, &buffer_len,
+                                       &tmp);
                        if (status == 0)
+                       {
                                vl.time = (time_t) tmp;
+                               n.time = (time_t) tmp;
+                       }
                }
                else if (pkg_type == TYPE_INTERVAL)
                {
                        uint64_t tmp = 0;
-                       status = parse_part_number (&buffer, &buffer_len, &tmp);
+                       status = parse_part_number (&buffer, &buffer_len,
+                                       &tmp);
                        if (status == 0)
                                vl.interval = (int) tmp;
                }
@@ -750,26 +758,85 @@ static int parse_packet (void *buffer, int buffer_len)
                {
                        status = parse_part_string (&buffer, &buffer_len,
                                        vl.host, sizeof (vl.host));
+                       if (status == 0)
+                               sstrncpy (n.host, vl.host, sizeof (n.host));
                }
                else if (pkg_type == TYPE_PLUGIN)
                {
                        status = parse_part_string (&buffer, &buffer_len,
                                        vl.plugin, sizeof (vl.plugin));
+                       if (status == 0)
+                               sstrncpy (n.plugin, vl.plugin,
+                                               sizeof (n.plugin));
                }
                else if (pkg_type == TYPE_PLUGIN_INSTANCE)
                {
                        status = parse_part_string (&buffer, &buffer_len,
-                                       vl.plugin_instance, sizeof (vl.plugin_instance));
+                                       vl.plugin_instance,
+                                       sizeof (vl.plugin_instance));
+                       if (status == 0)
+                               sstrncpy (n.plugin_instance,
+                                               vl.plugin_instance,
+                                               sizeof (n.plugin_instance));
                }
                else if (pkg_type == TYPE_TYPE)
                {
                        status = parse_part_string (&buffer, &buffer_len,
                                        type, sizeof (type));
+                       if (status == 0)
+                               sstrncpy (n.type, type, sizeof (n.type));
                }
                else if (pkg_type == TYPE_TYPE_INSTANCE)
                {
                        status = parse_part_string (&buffer, &buffer_len,
-                                       vl.type_instance, sizeof (vl.type_instance));
+                                       vl.type_instance,
+                                       sizeof (vl.type_instance));
+                       if (status == 0)
+                               sstrncpy (n.type_instance, vl.type_instance,
+                                               sizeof (n.type_instance));
+               }
+               else if (pkg_type == TYPE_MESSAGE)
+               {
+                       status = parse_part_string (&buffer, &buffer_len,
+                                       n.message, sizeof (n.message));
+
+                       if (status != 0)
+                       {
+                               /* do nothing */
+                       }
+                       else if ((n.severity != NOTIF_FAILURE)
+                                       && (n.severity != NOTIF_WARNING)
+                                       && (n.severity != NOTIF_OKAY))
+                       {
+                               INFO ("network plugin: "
+                                               "Ignoring notification with "
+                                               "unknown severity %i.",
+                                               n.severity);
+                       }
+                       else if (n.time <= 0)
+                       {
+                               INFO ("network plugin: "
+                                               "Ignoring notification with "
+                                               "time == 0.");
+                       }
+                       else if (strlen (n.message) <= 0)
+                       {
+                               INFO ("network plugin: "
+                                               "Ignoring notification with "
+                                               "an empty message.");
+                       }
+                       else
+                       {
+                               plugin_dispatch_notification (&n);
+                       }
+               }
+               else if (pkg_type == TYPE_SEVERITY)
+               {
+                       uint64_t tmp = 0;
+                       status = parse_part_number (&buffer, &buffer_len,
+                                       &tmp);
+                       if (status == 0)
+                               n.severity = (int) tmp;
                }
                else
                {
@@ -859,6 +926,16 @@ static int network_set_ttl (const sockent_t *se, const struct addrinfo *ai)
 static int network_bind_socket (const sockent_t *se, const struct addrinfo *ai)
 {
        int loop = 0;
+       int yes  = 1;
+
+       /* allow multiple sockets to use the same PORT number */
+       if (setsockopt(se->fd, SOL_SOCKET, SO_REUSEADDR,
+                               &yes, sizeof(yes)) == -1) {
+                char errbuf[1024];
+                ERROR ("setsockopt: %s", 
+                                sstrerror (errno, errbuf, sizeof (errbuf)));
+               return (-1);
+       }
 
        DEBUG ("fd = %i; calling `bind'", se->fd);
 
@@ -1518,6 +1595,77 @@ static int network_config (const char *key, const char *val)
        return (0);
 } /* int network_config */
 
+static int network_notification (const notification_t *n)
+{
+  char  buffer[BUFF_SIZE];
+  char *buffer_ptr = buffer;
+  int   buffer_free = sizeof (buffer);
+  int   status;
+
+  memset (buffer, '\0', sizeof (buffer));
+
+
+  status = write_part_number (&buffer_ptr, &buffer_free, TYPE_TIME,
+      (uint64_t) n->time);
+  if (status != 0)
+    return (-1);
+
+  status = write_part_number (&buffer_ptr, &buffer_free, TYPE_SEVERITY,
+      (uint64_t) n->severity);
+  if (status != 0)
+    return (-1);
+
+  if (strlen (n->host) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_HOST,
+       n->host, strlen (n->host));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->plugin) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_PLUGIN,
+       n->plugin, strlen (n->plugin));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->plugin_instance) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free,
+       TYPE_PLUGIN_INSTANCE,
+       n->plugin_instance, strlen (n->plugin_instance));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->type) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_TYPE,
+       n->type, strlen (n->type));
+    if (status != 0)
+      return (-1);
+  }
+
+  if (strlen (n->type_instance) > 0)
+  {
+    status = write_part_string (&buffer_ptr, &buffer_free, TYPE_TYPE_INSTANCE,
+       n->type_instance, strlen (n->type_instance));
+    if (status != 0)
+      return (-1);
+  }
+
+  status = write_part_string (&buffer_ptr, &buffer_free, TYPE_MESSAGE,
+      n->message, strlen (n->message));
+  if (status != 0)
+    return (-1);
+
+  network_send_buffer (buffer, sizeof (buffer) - buffer_free);
+
+  return (0);
+} /* int network_notification */
+
 static int network_shutdown (void)
 {
        listen_loop++;
@@ -1575,7 +1723,10 @@ static int network_init (void)
 
        /* setup socket(s) and so on */
        if (sending_sockets != NULL)
+       {
                plugin_register_write ("network", network_write);
+               plugin_register_notification ("network", network_notification);
+       }
 
        if ((listen_sockets_num != 0) && (receive_thread_id == 0))
        {
index 6d8e966..4318ef9 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/network.h
- * Copyright (C) 2005,2006  Florian octo Forster
+ * Copyright (C) 2005-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
@@ -63,4 +63,8 @@
 #define TYPE_VALUES          0x0006
 #define TYPE_INTERVAL        0x0007
 
+/* Types to transmit notifications */
+#define TYPE_MESSAGE         0x0100
+#define TYPE_SEVERITY        0x0101
+
 #endif /* NETWORK_H */
index 9e09f81..90fdfd7 100644 (file)
@@ -50,9 +50,11 @@ static const char *config_keys[] =
 {
        "Host",
        "Port",
-       NULL
+       "ReverseLookups"
 };
-static int config_keys_num = 2;
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int do_reverse_lookups = 1;
 
 # define NTPD_DEFAULT_HOST "localhost"
 # define NTPD_DEFAULT_PORT "123"
@@ -247,9 +249,9 @@ static char *refclock_names[] =
        "CHRONOLOG",  "DUMBCLOCK",    "ULINK_M320", "PCF",         /* 32-35 */
        "WWV_AUDIO",  "GPS_FG",       "HOPF_S",     "HOPF_P",      /* 36-39 */
        "JJY",        "TT_IRIG",      "GPS_ZYFER",  "GPS_RIPENCC", /* 40-43 */
-       "NEOCLK4X",   NULL                                         /* 44    */
+       "NEOCLK4X"                                                 /* 44    */
 };
-static int refclock_names_num = 45;
+static int refclock_names_num = STATIC_ARRAY_SIZE (refclock_names);
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  * End of the copied stuff..                                         *
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@@ -273,6 +275,15 @@ static int ntpd_config (const char *key, const char *value)
                        strncpy (ntpd_port, value, sizeof (ntpd_port));
                ntpd_port[sizeof (ntpd_port) - 1] = '\0';
        }
+       else if (strcasecmp (key, "ReverseLookups") == 0)
+       {
+               if ((strcasecmp (value, "True") == 0)
+                               || (strcasecmp (value, "Yes") == 0)
+                               || (strcasecmp (value, "On") == 0))
+                       do_reverse_lookups = 1;
+               else
+                       do_reverse_lookups = 0;
+       }
        else
        {
                return (-1);
@@ -397,7 +408,7 @@ static int ntpd_connect (void)
 }
 
 /* For a description of the arguments see `ntpd_do_query' below. */
-static int ntpd_receive_response (int req_code, int *res_items, int *res_size,
+static int ntpd_receive_response (int *res_items, int *res_size,
                char **res_data, int res_item_size)
 {
        int              sd;
@@ -755,7 +766,7 @@ static int ntpd_do_query (int req_code, int req_items, int req_size, char *req_d
        if (status != 0)
                return (status);
 
-       status = ntpd_receive_response (req_code, res_items, res_size, res_data,
+       status = ntpd_receive_response (res_items, res_size, res_data,
                        res_item_size);
        return (status);
 }
@@ -848,38 +859,13 @@ static int ntpd_read (void)
                ptr = ps + i;
                refclock_id = 0;
 
-               /*
-               if (((ntohl (ptr->dstadr) & 0xFFFFFF00) == 0x7F000000) || (ptr->dstadr == 0))
-                       continue;
-                       */
-
                /* Convert the `long floating point' offset value to double */
                M_LFPTOD (ntohl (ptr->offset_int), ntohl (ptr->offset_frc), offset);
 
-               if (ptr->v6_flag)
-               {
-                       struct sockaddr_in6 sa;
-
-                       memset (&sa, 0, sizeof (sa));
-                       sa.sin6_family = AF_INET6;
-                       sa.sin6_port = htons (123);
-                       memcpy (&sa.sin6_addr, &ptr->srcadr6, sizeof (struct in6_addr));
-
-                       status = getnameinfo ((const struct sockaddr *) &sa,
-                                       sizeof (sa),
-                                       peername, sizeof (peername),
-                                       NULL, 0, 0 /* no flags */);
-                       if (status != 0)
-                       {
-                               char errbuf[1024];
-                               ERROR ("ntpd plugin: getnameinfo failed: %s",
-                                               (status == EAI_SYSTEM)
-                                               ? sstrerror (errno, errbuf, sizeof (errbuf))
-                                               : gai_strerror (status));
-                               continue;
-                       }
-               }
-               else if ((ntohl (ptr->srcadr) & REFCLOCK_MASK) == REFCLOCK_ADDR)
+               /* Special IP addresses for hardware clocks and stuff.. */
+               if (!ptr->v6_flag
+                               && ((ntohl (ptr->srcadr) & REFCLOCK_MASK)
+                                       == REFCLOCK_ADDR))
                {
                        struct in_addr  addr_obj;
                        char *addr_str;
@@ -900,25 +886,53 @@ static int ntpd_read (void)
                                strncpy (peername, addr_str, sizeof (peername));
                        }
                }
-               else /* IPv4 */
+               else /* Normal network host. */
                {
-                       struct in_addr  addr_obj;
-                       struct hostent *addr_he;
-                       char           *addr_str;
+                       struct sockaddr_storage sa;
+                       socklen_t sa_len;
+                       int flags = 0;
 
-                       memset ((void *) &addr_obj, '\0', sizeof (addr_obj));
-                       addr_obj.s_addr = ptr->srcadr;
-                       addr_str = inet_ntoa (addr_obj);
+                       memset (&sa, '\0', sizeof (sa));
 
-                       addr_he = gethostbyaddr ((const void *) &addr_obj,
-                                       sizeof (addr_obj), AF_INET);
-                       if (addr_he != NULL)
+                       if (ptr->v6_flag)
                        {
-                               strncpy (peername, addr_he->h_name, sizeof (peername));
+                               struct sockaddr_in6 *sa_ptr;
+                               sa_ptr = (struct sockaddr_in6 *) &sa;
+
+                               sa_ptr->sin6_family = AF_INET6;
+                               sa_ptr->sin6_port = htons (123);
+                               memcpy (&sa_ptr->sin6_addr, &ptr->srcadr6,
+                                               sizeof (struct in6_addr));
+                               sa_len = sizeof (struct sockaddr_in6);
                        }
                        else
                        {
-                               strncpy (peername, addr_str, sizeof (peername));
+                               struct sockaddr_in *sa_ptr;
+                               sa_ptr = (struct sockaddr_in *) &sa;
+
+                               sa_ptr->sin_family = AF_INET;
+                               sa_ptr->sin_port = htons (123);
+                               memcpy (&sa_ptr->sin_addr, &ptr->srcadr,
+                                               sizeof (struct in_addr));
+                               sa_len = sizeof (struct sockaddr_in);
+                       }
+
+                       if (do_reverse_lookups == 0)
+                               flags |= NI_NUMERICHOST;
+
+                       status = getnameinfo ((const struct sockaddr *) &sa,
+                                       sa_len,
+                                       peername, sizeof (peername),
+                                       NULL, 0, /* No port name */
+                                       flags);
+                       if (status != 0)
+                       {
+                               char errbuf[1024];
+                               ERROR ("ntpd plugin: getnameinfo failed: %s",
+                                               (status == EAI_SYSTEM)
+                                               ? sstrerror (errno, errbuf, sizeof (errbuf))
+                                               : gai_strerror (status));
+                               continue;
                        }
                }
 
index 0283757..96e8562 100644 (file)
@@ -24,6 +24,9 @@
  * interface for collectd plugins written in perl.
  */
 
+/* do not automatically get the thread specific perl interpreter */
+#define PERL_NO_GET_CONTEXT
+
 #include "collectd.h"
 
 #include "configfile.h"
 #include "plugin.h"
 #include "common.h"
 
+#include <pthread.h>
+
+#if !defined(USE_ITHREADS)
+# error "Perl does not support ithreads!"
+#endif /* !defined(USE_ITHREADS) */
+
+/* clear the Perl sub's stack frame
+ * (this should only be used inside an XSUB) */
+#define CLEAR_STACK_FRAME PL_stack_sp = PL_stack_base + *PL_markstack_ptr
+
 #define PLUGIN_INIT     0
 #define PLUGIN_READ     1
 #define PLUGIN_WRITE    2
 #define PLUGIN_SHUTDOWN 3
 #define PLUGIN_LOG      4
+#define PLUGIN_NOTIF    5
 
-#define PLUGIN_TYPES    5
+#define PLUGIN_TYPES    6
 
 #define PLUGIN_DATASET  255
 
@@ -63,15 +77,47 @@ 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_dispatch_notification);
 static XS (Collectd_plugin_log);
+static XS (Collectd_call_by_name);
+
+/*
+ * private data types
+ */
+
+typedef struct c_ithread_s {
+       /* the thread's Perl interpreter */
+       PerlInterpreter *interp;
+
+       /* double linked list of threads */
+       struct c_ithread_s *prev;
+       struct c_ithread_s *next;
+} c_ithread_t;
+
+typedef struct {
+       c_ithread_t *head;
+       c_ithread_t *tail;
+
+#if COLLECT_DEBUG
+       /* some usage stats */
+       int number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       pthread_mutex_t mutex;
+} c_ithread_list_t;
 
 /*
  * private variables
  */
 
-static PerlInterpreter *perl = NULL;
+/* if perl_threads != NULL perl_threads->head must
+ * point to the "base" thread */
+static c_ithread_list_t *perl_threads = NULL;
+
+/* the key used to store each pthread's ithread */
+static pthread_key_t perl_thr_key;
 
-static int  perl_argc   = 0;
+static int    perl_argc = 0;
 static char **perl_argv = NULL;
 
 static char base_name[DATA_MAX_NAME_LEN] = "";
@@ -84,7 +130,10 @@ 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_dispatch_notification",
+               Collectd_plugin_dispatch_notification },
        { "Collectd::plugin_log",                 Collectd_plugin_log },
+       { "Collectd::call_by_name",               Collectd_call_by_name },
        { "", NULL }
 };
 
@@ -98,6 +147,7 @@ struct {
        { "Collectd::TYPE_WRITE",      PLUGIN_WRITE },
        { "Collectd::TYPE_SHUTDOWN",   PLUGIN_SHUTDOWN },
        { "Collectd::TYPE_LOG",        PLUGIN_LOG },
+       { "Collectd::TYPE_NOTIF",      PLUGIN_NOTIF },
        { "Collectd::TYPE_DATASET",    PLUGIN_DATASET },
        { "Collectd::DS_TYPE_COUNTER", DS_TYPE_COUNTER },
        { "Collectd::DS_TYPE_GAUGE",   DS_TYPE_GAUGE },
@@ -106,9 +156,30 @@ struct {
        { "Collectd::LOG_NOTICE",      LOG_NOTICE },
        { "Collectd::LOG_INFO",        LOG_INFO },
        { "Collectd::LOG_DEBUG",       LOG_DEBUG },
+       { "Collectd::NOTIF_FAILURE",   NOTIF_FAILURE },
+       { "Collectd::NOTIF_WARNING",   NOTIF_WARNING },
+       { "Collectd::NOTIF_OKAY",      NOTIF_OKAY },
        { "", 0 }
 };
 
+struct {
+       char  name[64];
+       char *var;
+} g_strings[] =
+{
+       { "Collectd::hostname_g", hostname_g },
+       { "", NULL }
+};
+
+struct {
+       char  name[64];
+       int  *var;
+} g_integers[] =
+{
+       { "Collectd::interval_g", &interval_g },
+       { "", NULL }
+};
+
 /*
  * Helper functions for data type conversion.
  */
@@ -125,14 +196,14 @@ struct {
  *   ...
  * ]
  */
-static int hv2data_source (HV *hash, data_source_t *ds)
+static int hv2data_source (pTHX_ HV *hash, data_source_t *ds)
 {
        SV **tmp = NULL;
 
        if ((NULL == hash) || (NULL == ds))
                return -1;
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, hash, "name", 4, 0))) {
+       if (NULL != (tmp = hv_fetch (hash, "name", 4, 0))) {
                strncpy (ds->name, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
                ds->name[DATA_MAX_NAME_LEN - 1] = '\0';
        }
@@ -141,7 +212,7 @@ static int hv2data_source (HV *hash, data_source_t *ds)
                return -1;
        }
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, hash, "type", 4, 0))) {
+       if (NULL != (tmp = hv_fetch (hash, "type", 4, 0))) {
                ds->type = SvIV (*tmp);
 
                if ((DS_TYPE_COUNTER != ds->type) && (DS_TYPE_GAUGE != ds->type)) {
@@ -153,19 +224,19 @@ static int hv2data_source (HV *hash, data_source_t *ds)
                ds->type = DS_TYPE_COUNTER;
        }
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, hash, "min", 3, 0)))
+       if (NULL != (tmp = hv_fetch (hash, "min", 3, 0)))
                ds->min = SvNV (*tmp);
        else
                ds->min = NAN;
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, hash, "max", 3, 0)))
+       if (NULL != (tmp = hv_fetch (hash, "max", 3, 0)))
                ds->max = SvNV (*tmp);
        else
                ds->max = NAN;
        return 0;
 } /* static data_source_t *hv2data_source (HV *) */
 
-static int av2value (char *name, AV *array, value_t *value, int len)
+static int av2value (pTHX_ char *name, AV *array, value_t *value, int len)
 {
        const data_set_t *ds;
 
@@ -174,8 +245,8 @@ static int av2value (char *name, AV *array, value_t *value, int len)
        if ((NULL == name) || (NULL == array) || (NULL == value))
                return -1;
 
-       if (Perl_av_len (perl, array) < len - 1)
-               len = Perl_av_len (perl, array) + 1;
+       if (av_len (array) < len - 1)
+               len = av_len (array) + 1;
 
        if (0 >= len)
                return -1;
@@ -192,7 +263,7 @@ static int av2value (char *name, AV *array, value_t *value, int len)
        }
 
        for (i = 0; i < len; ++i) {
-               SV **tmp = Perl_av_fetch (perl, array, i, 0);
+               SV **tmp = av_fetch (array, i, 0);
 
                if (NULL != tmp) {
                        if (DS_TYPE_COUNTER == ds->ds[i].type)
@@ -207,44 +278,42 @@ static int av2value (char *name, AV *array, value_t *value, int len)
        return len;
 } /* static int av2value (char *, AV *, value_t *, int) */
 
-static int data_set2av (data_set_t *ds, AV *array)
+static int data_set2av (pTHX_ data_set_t *ds, AV *array)
 {
        int i = 0;
 
        if ((NULL == ds) || (NULL == array))
                return -1;
 
-       Perl_av_extend (perl, array, ds->ds_num);
+       av_extend (array, ds->ds_num);
 
        for (i = 0; i < ds->ds_num; ++i) {
-               HV *source = Perl_newHV (perl);
+               HV *source = newHV ();
 
-               if (NULL == Perl_hv_store (perl, source, "name", 4,
-                               Perl_newSVpv (perl, ds->ds[i].name, 0), 0))
+               if (NULL == hv_store (source, "name", 4,
+                               newSVpv (ds->ds[i].name, 0), 0))
                        return -1;
 
-               if (NULL == Perl_hv_store (perl, source, "type", 4,
-                               Perl_newSViv (perl, ds->ds[i].type), 0))
+               if (NULL == hv_store (source, "type", 4, newSViv (ds->ds[i].type), 0))
                        return -1;
 
                if (! isnan (ds->ds[i].min))
-                       if (NULL == Perl_hv_store (perl, source, "min", 3,
-                                       Perl_newSVnv (perl, ds->ds[i].min), 0))
+                       if (NULL == hv_store (source, "min", 3,
+                                       newSVnv (ds->ds[i].min), 0))
                                return -1;
 
                if (! isnan (ds->ds[i].max))
-                       if (NULL == Perl_hv_store (perl, source, "max", 3,
-                                       Perl_newSVnv (perl, ds->ds[i].max), 0))
+                       if (NULL == hv_store (source, "max", 3,
+                                       newSVnv (ds->ds[i].max), 0))
                                return -1;
 
-               if (NULL == Perl_av_store (perl, array, i,
-                               Perl_newRV_noinc (perl, (SV *)source)))
+               if (NULL == av_store (array, i, newRV_noinc ((SV *)source)))
                        return -1;
        }
        return 0;
 } /* static int data_set2av (data_set_t *, AV *) */
 
-static int value_list2hv (value_list_t *vl, data_set_t *ds, HV *hash)
+static int value_list2hv (pTHX_ value_list_t *vl, data_set_t *ds, HV *hash)
 {
        AV *values = NULL;
 
@@ -261,54 +330,87 @@ static int value_list2hv (value_list_t *vl, data_set_t *ds, HV *hash)
                len = ds->ds_num;
        }
 
-       values = Perl_newAV (perl);
-       Perl_av_extend (perl, values, len - 1);
+       values = newAV ();
+       av_extend (values, len - 1);
 
        for (i = 0; i < len; ++i) {
                SV *val = NULL;
 
                if (DS_TYPE_COUNTER == ds->ds[i].type)
-                       val = Perl_newSViv (perl, vl->values[i].counter);
+                       val = newSViv (vl->values[i].counter);
                else
-                       val = Perl_newSVnv (perl, vl->values[i].gauge);
+                       val = newSVnv (vl->values[i].gauge);
 
-               if (NULL == Perl_av_store (perl, values, i, val)) {
-                       Perl_av_undef (perl, values);
+               if (NULL == av_store (values, i, val)) {
+                       av_undef (values);
                        return -1;
                }
        }
 
-       if (NULL == Perl_hv_store (perl, hash, "values", 6,
-                       Perl_newRV_noinc (perl, (SV *)values), 0))
+       if (NULL == hv_store (hash, "values", 6, newRV_noinc ((SV *)values), 0))
                return -1;
 
        if (0 != vl->time)
-               if (NULL == Perl_hv_store (perl, hash, "time", 4,
-                               Perl_newSViv (perl, vl->time), 0))
+               if (NULL == hv_store (hash, "time", 4, newSViv (vl->time), 0))
                        return -1;
 
        if ('\0' != vl->host[0])
-               if (NULL == Perl_hv_store (perl, hash, "host", 4,
-                               Perl_newSVpv (perl, vl->host, 0), 0))
+               if (NULL == hv_store (hash, "host", 4, newSVpv (vl->host, 0), 0))
                        return -1;
 
        if ('\0' != vl->plugin[0])
-               if (NULL == Perl_hv_store (perl, hash, "plugin", 6,
-                               Perl_newSVpv (perl, vl->plugin, 0), 0))
+               if (NULL == hv_store (hash, "plugin", 6, newSVpv (vl->plugin, 0), 0))
                        return -1;
 
        if ('\0' != vl->plugin_instance[0])
-               if (NULL == Perl_hv_store (perl, hash, "plugin_instance", 15,
-                               Perl_newSVpv (perl, vl->plugin_instance, 0), 0))
+               if (NULL == hv_store (hash, "plugin_instance", 15,
+                               newSVpv (vl->plugin_instance, 0), 0))
                        return -1;
 
        if ('\0' != vl->type_instance[0])
-               if (NULL == Perl_hv_store (perl, hash, "type_instance", 13,
-                               Perl_newSVpv (perl, vl->type_instance, 0), 0))
+               if (NULL == hv_store (hash, "type_instance", 13,
+                               newSVpv (vl->type_instance, 0), 0))
                        return -1;
        return 0;
 } /* static int value2av (value_list_t *, data_set_t *, HV *) */
 
+static int notification2hv (pTHX_ notification_t *n, HV *hash)
+{
+       if (NULL == hv_store (hash, "severity", 8, newSViv (n->severity), 0))
+               return -1;
+
+       if (0 != n->time)
+               if (NULL == hv_store (hash, "time", 4, newSViv (n->time), 0))
+                       return -1;
+
+       if ('\0' != *n->message)
+               if (NULL == hv_store (hash, "message", 7, newSVpv (n->message, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->host)
+               if (NULL == hv_store (hash, "host", 4, newSVpv (n->host, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->plugin)
+               if (NULL == hv_store (hash, "plugin", 6, newSVpv (n->plugin, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->plugin_instance)
+               if (NULL == hv_store (hash, "plugin_instance", 15,
+                               newSVpv (n->plugin_instance, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->type)
+               if (NULL == hv_store (hash, "type", 4, newSVpv (n->type, 0), 0))
+                       return -1;
+
+       if ('\0' != *n->type_instance)
+               if (NULL == hv_store (hash, "type_instance", 13,
+                               newSVpv (n->type_instance, 0), 0))
+                       return -1;
+       return 0;
+} /* static int notification2hv (notification_t *, HV *) */
+
 /*
  * Internal functions.
  */
@@ -319,7 +421,7 @@ static char *get_module_name (char *buf, size_t buf_len, const char *module) {
                status = snprintf (buf, buf_len, "%s", module);
        else
                status = snprintf (buf, buf_len, "%s::%s", base_name, module);
-       if ((status < 0) || (status >= buf_len))
+       if ((status < 0) || ((unsigned int)status >= buf_len))
                return (NULL);
        buf[buf_len - 1] = '\0';
        return (buf);
@@ -328,9 +430,10 @@ static char *get_module_name (char *buf, size_t buf_len, const char *module) {
 /*
  * Add a plugin's data set definition.
  */
-static int pplugin_register_data_set (char *name, AV *dataset)
+static int pplugin_register_data_set (pTHX_ char *name, AV *dataset)
 {
        int len = -1;
+       int ret = 0;
        int i   = 0;
 
        data_source_t *ds  = NULL;
@@ -339,7 +442,7 @@ static int pplugin_register_data_set (char *name, AV *dataset)
        if ((NULL == name) || (NULL == dataset))
                return -1;
 
-       len = Perl_av_len (perl, dataset);
+       len = av_len (dataset);
 
        if (-1 == len)
                return -1;
@@ -348,7 +451,7 @@ static int pplugin_register_data_set (char *name, AV *dataset)
        set = (data_set_t *)smalloc (sizeof (data_set_t));
 
        for (i = 0; i <= len; ++i) {
-               SV **elem = Perl_av_fetch (perl, dataset, i, 0);
+               SV **elem = av_fetch (dataset, i, 0);
 
                if (NULL == elem)
                        return -1;
@@ -358,7 +461,7 @@ static int pplugin_register_data_set (char *name, AV *dataset)
                        return -1;
                }
 
-               if (-1 == hv2data_source ((HV *)SvRV (*elem), &ds[i]))
+               if (-1 == hv2data_source (aTHX_ (HV *)SvRV (*elem), &ds[i]))
                        return -1;
 
                log_debug ("pplugin_register_data_set: "
@@ -371,7 +474,12 @@ static int pplugin_register_data_set (char *name, AV *dataset)
 
        set->ds_num = len + 1;
        set->ds = ds;
-       return plugin_register_data_set (set);
+
+       ret = plugin_register_data_set (set);
+
+       free (ds);
+       free (set);
+       return ret;
 } /* static int pplugin_register_data_set (char *, SV *) */
 
 /*
@@ -397,7 +505,7 @@ static int pplugin_unregister_data_set (char *name)
  *   type_instance   => $tinstance,
  * }
  */
-static int pplugin_dispatch_values (char *name, HV *values)
+static int pplugin_dispatch_values (pTHX_ char *name, HV *values)
 {
        value_list_t list = VALUE_LIST_INIT;
        value_t      *val = NULL;
@@ -409,7 +517,7 @@ static int pplugin_dispatch_values (char *name, HV *values)
        if ((NULL == name) || (NULL == values))
                return -1;
 
-       if ((NULL == (tmp = Perl_hv_fetch (perl, values, "values", 6, 0)))
+       if ((NULL == (tmp = hv_fetch (values, "values", 6, 0)))
                        || (! (SvROK (*tmp) && (SVt_PVAV == SvTYPE (SvRV (*tmp)))))) {
                log_err ("pplugin_dispatch_values: No valid values given.");
                return -1;
@@ -417,14 +525,14 @@ static int pplugin_dispatch_values (char *name, HV *values)
 
        {
                AV  *array = (AV *)SvRV (*tmp);
-               int len    = Perl_av_len (perl, array) + 1;
+               int len    = av_len (array) + 1;
 
                if (len <= 0)
                        return -1;
 
                val = (value_t *)smalloc (len * sizeof (value_t));
 
-               list.values_len = av2value (name, (AV *)SvRV (*tmp), val, len);
+               list.values_len = av2value (aTHX_ name, (AV *)SvRV (*tmp), val, len);
                list.values = val;
 
                if (-1 == list.values_len) {
@@ -433,14 +541,14 @@ static int pplugin_dispatch_values (char *name, HV *values)
                }
        }
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, values, "time", 4, 0))) {
+       if (NULL != (tmp = hv_fetch (values, "time", 4, 0))) {
                list.time = (time_t)SvIV (*tmp);
        }
        else {
                list.time = time (NULL);
        }
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, values, "host", 4, 0))) {
+       if (NULL != (tmp = hv_fetch (values, "host", 4, 0))) {
                strncpy (list.host, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
                list.host[DATA_MAX_NAME_LEN - 1] = '\0';
        }
@@ -448,18 +556,17 @@ static int pplugin_dispatch_values (char *name, HV *values)
                strcpy (list.host, hostname_g);
        }
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, values, "plugin", 6, 0))) {
+       if (NULL != (tmp = hv_fetch (values, "plugin", 6, 0))) {
                strncpy (list.plugin, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
                list.plugin[DATA_MAX_NAME_LEN - 1] = '\0';
        }
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, values,
-                       "plugin_instance", 15, 0))) {
+       if (NULL != (tmp = hv_fetch (values, "plugin_instance", 15, 0))) {
                strncpy (list.plugin_instance, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
                list.plugin_instance[DATA_MAX_NAME_LEN - 1] = '\0';
        }
 
-       if (NULL != (tmp = Perl_hv_fetch (perl, values, "type_instance", 13, 0))) {
+       if (NULL != (tmp = hv_fetch (values, "type_instance", 13, 0))) {
                strncpy (list.type_instance, SvPV_nolen (*tmp), DATA_MAX_NAME_LEN);
                list.type_instance[DATA_MAX_NAME_LEN - 1] = '\0';
        }
@@ -471,9 +578,74 @@ static int pplugin_dispatch_values (char *name, HV *values)
 } /* static int pplugin_dispatch_values (char *, HV *) */
 
 /*
+ * Dispatch a notification.
+ *
+ * notification:
+ * {
+ *   severity => $severity,
+ *   time     => $time,
+ *   message  => $msg,
+ *   host     => $host,
+ *   plugin   => $plugin,
+ *   type     => $type,
+ *   plugin_instance => $instance,
+ *   type_instance   => $type_instance
+ * }
+ */
+static int pplugin_dispatch_notification (pTHX_ HV *notif)
+{
+       notification_t n;
+
+       SV **tmp = NULL;
+
+       if (NULL == notif)
+               return -1;
+
+       memset (&n, 0, sizeof (n));
+
+       if (NULL != (tmp = hv_fetch (notif, "severity", 8, 0)))
+               n.severity = SvIV (*tmp);
+       else
+               n.severity = NOTIF_FAILURE;
+
+       if (NULL != (tmp = hv_fetch (notif, "time", 4, 0)))
+               n.time = (time_t)SvIV (*tmp);
+       else
+               n.time = time (NULL);
+
+       if (NULL != (tmp = hv_fetch (notif, "message", 7, 0)))
+               strncpy (n.message, SvPV_nolen (*tmp), sizeof (n.message));
+       n.message[sizeof (n.message) - 1] = '\0';
+
+       if (NULL != (tmp = hv_fetch (notif, "host", 4, 0)))
+               strncpy (n.host, SvPV_nolen (*tmp), sizeof (n.host));
+       else
+               strncpy (n.host, hostname_g, sizeof (n.host));
+       n.host[sizeof (n.host) - 1] = '\0';
+
+       if (NULL != (tmp = hv_fetch (notif, "plugin", 6, 0)))
+               strncpy (n.plugin, SvPV_nolen (*tmp), sizeof (n.plugin));
+       n.plugin[sizeof (n.plugin) - 1] = '\0';
+
+       if (NULL != (tmp = hv_fetch (notif, "plugin_instance", 15, 0)))
+               strncpy (n.plugin_instance, SvPV_nolen (*tmp),
+                               sizeof (n.plugin_instance));
+       n.plugin_instance[sizeof (n.plugin_instance) - 1] = '\0';
+
+       if (NULL != (tmp = hv_fetch (notif, "type", 4, 0)))
+               strncpy (n.type, SvPV_nolen (*tmp), sizeof (n.type));
+       n.type[sizeof (n.type) - 1] = '\0';
+
+       if (NULL != (tmp = hv_fetch (notif, "type_instance", 13, 0)))
+               strncpy (n.type_instance, SvPV_nolen (*tmp), sizeof (n.type_instance));
+       n.type_instance[sizeof (n.type_instance) - 1] = '\0';
+       return plugin_dispatch_notification (&n);
+} /* static int pplugin_dispatch_notification (HV *) */
+
+/*
  * Call all working functions of the given type.
  */
-static int pplugin_call_all (int type, ...)
+static int pplugin_call_all (pTHX_ int type, ...)
 {
        int retvals = 0;
 
@@ -492,7 +664,7 @@ static int pplugin_call_all (int type, ...)
 
        PUSHMARK (SP);
 
-       XPUSHs (sv_2mortal (Perl_newSViv (perl, (IV)type)));
+       XPUSHs (sv_2mortal (newSViv ((IV)type)));
 
        if (PLUGIN_WRITE == type) {
                /*
@@ -522,21 +694,29 @@ static int pplugin_call_all (int type, ...)
                data_set_t   *ds;
                value_list_t *vl;
 
-               AV *pds = Perl_newAV (perl);
-               HV *pvl = Perl_newHV (perl);
+               AV *pds = newAV ();
+               HV *pvl = newHV ();
 
                ds = va_arg (ap, data_set_t *);
                vl = va_arg (ap, value_list_t *);
 
-               if (-1 == data_set2av (ds, pds))
-                       return -1;
+               if (-1 == data_set2av (aTHX_ ds, pds)) {
+                       av_clear (pds);
+                       av_undef (pds);
+                       pds = Nullav;
+                       ret = -1;
+               }
 
-               if (-1 == value_list2hv (vl, ds, pvl))
-                       return -1;
+               if (-1 == value_list2hv (aTHX_ vl, ds, pvl)) {
+                       hv_clear (pvl);
+                       hv_undef (pvl);
+                       pvl = Nullhv;
+                       ret = -1;
+               }
 
-               XPUSHs (sv_2mortal (Perl_newSVpv (perl, ds->type, 0)));
-               XPUSHs (sv_2mortal (Perl_newRV_noinc (perl, (SV *)pds)));
-               XPUSHs (sv_2mortal (Perl_newRV_noinc (perl, (SV *)pvl)));
+               XPUSHs (sv_2mortal (newSVpv (ds->type, 0)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pds)));
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)pvl)));
        }
        else if (PLUGIN_LOG == type) {
                /*
@@ -544,13 +724,41 @@ static int pplugin_call_all (int type, ...)
                 *
                 * $_[1] = $message;
                 */
-               XPUSHs (sv_2mortal (Perl_newSViv (perl, va_arg (ap, int))));
-               XPUSHs (sv_2mortal (Perl_newSVpv (perl, va_arg (ap, char *), 0)));
+               XPUSHs (sv_2mortal (newSViv (va_arg (ap, int))));
+               XPUSHs (sv_2mortal (newSVpv (va_arg (ap, char *), 0)));
+       }
+       else if (PLUGIN_NOTIF == type) {
+               /*
+                * $_[0] =
+                * {
+                *   severity => $severity,
+                *   time     => $time,
+                *   message  => $msg,
+                *   host     => $host,
+                *   plugin   => $plugin,
+                *   type     => $type,
+                *   plugin_instance => $instance,
+                *   type_instance   => $type_instance
+                * };
+                */
+               notification_t *n;
+               HV *notif = newHV ();
+
+               n = va_arg (ap, notification_t *);
+
+               if (-1 == notification2hv (aTHX_ n, notif)) {
+                       hv_clear (notif);
+                       hv_undef (notif);
+                       notif = Nullhv;
+                       ret = -1;
+               }
+
+               XPUSHs (sv_2mortal (newRV_noinc ((SV *)notif)));
        }
 
        PUTBACK;
 
-       retvals = Perl_call_pv (perl, "Collectd::plugin_call_all", G_SCALAR);
+       retvals = call_pv ("Collectd::plugin_call_all", G_SCALAR);
 
        SPAGAIN;
        if (0 < retvals) {
@@ -599,7 +807,7 @@ static XS (Collectd_plugin_register_ds)
        data = ST (1);
 
        if (SvROK (data) && (SVt_PVAV == SvTYPE (SvRV (data)))) {
-               ret = pplugin_register_data_set (SvPV_nolen (ST (0)),
+               ret = pplugin_register_data_set (aTHX_ SvPV_nolen (ST (0)),
                                (AV *)SvRV (data));
        }
        else {
@@ -631,7 +839,7 @@ static XS (Collectd_plugin_unregister_ds)
        log_debug ("Collectd::plugin_unregister_data_set: type = \"%s\"",
                        SvPV_nolen (ST (0)));
 
-       if (0 == pplugin_unregister_data_set (SvPV_nolen (ST (1))))
+       if (0 == pplugin_unregister_data_set (SvPV_nolen (ST (0))))
                XSRETURN_YES;
        else
                XSRETURN_EMPTY;
@@ -673,7 +881,8 @@ static XS (Collectd_plugin_dispatch_values)
        if ((NULL == ST (0)) || (NULL == values))
                XSRETURN_EMPTY;
 
-       ret = pplugin_dispatch_values (SvPV_nolen (ST (0)), (HV *)SvRV (values));
+       ret = pplugin_dispatch_values (aTHX_ SvPV_nolen (ST (0)),
+                       (HV *)SvRV (values));
 
        if (0 == ret)
                XSRETURN_YES;
@@ -682,6 +891,43 @@ static XS (Collectd_plugin_dispatch_values)
 } /* static XS (Collectd_plugin_dispatch_values) */
 
 /*
+ * Collectd::plugin_dispatch_notification (notif).
+ *
+ * notif:
+ *   notification to dispatch
+ */
+static XS (Collectd_plugin_dispatch_notification)
+{
+       SV *notif = NULL;
+
+       int ret = 0;
+
+       dXSARGS;
+
+       if (1 != items) {
+               log_err ("Usage: Collectd::plugin_dispatch_notification(notif)");
+               XSRETURN_EMPTY;
+       }
+
+       log_debug ("Collectd::plugin_dispatch_notification: notif = \"%s\"",
+                       SvPV_nolen (ST (0)));
+
+       notif = ST (0);
+
+       if (! (SvROK (notif) && (SVt_PVHV == SvTYPE (SvRV (notif))))) {
+               log_err ("Collectd::plugin_dispatch_notification: Invalid notif.");
+               XSRETURN_EMPTY;
+       }
+
+       ret = pplugin_dispatch_notification (aTHX_ (HV *)SvRV (notif));
+
+       if (0 == ret)
+               XSRETURN_YES;
+       else
+               XSRETURN_EMPTY;
+} /* static XS (Collectd_plugin_dispatch_notification) */
+
+/*
  * Collectd::plugin_log (level, message).
  *
  * level:
@@ -704,70 +950,304 @@ static XS (Collectd_plugin_log)
 } /* static XS (Collectd_plugin_log) */
 
 /*
+ * Collectd::call_by_name (...).
+ *
+ * Call a Perl sub identified by its name passed through $Collectd::cb_name.
+ */
+static XS (Collectd_call_by_name)
+{
+       SV   *tmp  = NULL;
+       char *name = NULL;
+
+       if (NULL == (tmp = get_sv ("Collectd::cb_name", 0))) {
+               sv_setpv (get_sv ("@", 1), "cb_name has not been set");
+               CLEAR_STACK_FRAME;
+               return;
+       }
+
+       name = SvPV_nolen (tmp);
+
+       if (NULL == get_cv (name, 0)) {
+               sv_setpvf (get_sv ("@", 1), "unknown callback \"%s\"", name);
+               CLEAR_STACK_FRAME;
+               return;
+       }
+
+       /* simply pass on the subroutine call without touching the stack,
+        * thus leaving any arguments and return values in place */
+       call_pv (name, 0);
+} /* static XS (Collectd_call_by_name) */
+
+/*
+ * collectd's perl interpreter based thread implementation.
+ *
+ * This has been inspired by Perl's ithreads introduced in version 5.6.0.
+ */
+
+/* must be called with perl_threads->mutex locked */
+static void c_ithread_destroy (c_ithread_t *ithread)
+{
+       dTHXa (ithread->interp);
+
+       assert (NULL != perl_threads);
+
+       PERL_SET_CONTEXT (aTHX);
+       log_debug ("Shutting down Perl interpreter %p...", aTHX);
+
+#if COLLECT_DEBUG
+       sv_report_used ();
+
+       --perl_threads->number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       perl_destruct (aTHX);
+       perl_free (aTHX);
+
+       if (NULL == ithread->prev)
+               perl_threads->head = ithread->next;
+       else
+               ithread->prev->next = ithread->next;
+
+       if (NULL == ithread->next)
+               perl_threads->tail = ithread->prev;
+       else
+               ithread->next->prev = ithread->prev;
+
+       sfree (ithread);
+       return;
+} /* static void c_ithread_destroy (c_ithread_t *) */
+
+static void c_ithread_destructor (void *arg)
+{
+       c_ithread_t *ithread = (c_ithread_t *)arg;
+       c_ithread_t *t = NULL;
+
+       if (NULL == perl_threads)
+               return;
+
+       pthread_mutex_lock (&perl_threads->mutex);
+
+       for (t = perl_threads->head; NULL != t; t = t->next)
+               if (t == ithread)
+                       break;
+
+       /* the ithread no longer exists */
+       if (NULL == t)
+               return;
+
+       c_ithread_destroy (ithread);
+
+       pthread_mutex_unlock (&perl_threads->mutex);
+       return;
+} /* static void c_ithread_destructor (void *) */
+
+/* must be called with perl_threads->mutex locked */
+static c_ithread_t *c_ithread_create (PerlInterpreter *base)
+{
+       c_ithread_t *t = NULL;
+       dTHXa (NULL);
+
+       assert (NULL != perl_threads);
+
+       t = (c_ithread_t *)smalloc (sizeof (c_ithread_t));
+       memset (t, 0, sizeof (c_ithread_t));
+
+       t->interp = (NULL == base)
+               ? NULL
+               : perl_clone (base, CLONEf_KEEP_PTR_TABLE);
+
+       aTHX = t->interp;
+
+       if (NULL != base) {
+               av_clear (PL_endav);
+               av_undef (PL_endav);
+               PL_endav = Nullav;
+       }
+
+#if COLLECT_DEBUG
+       ++perl_threads->number_of_threads;
+#endif /* COLLECT_DEBUG */
+
+       t->next = NULL;
+
+       if (NULL == perl_threads->tail) {
+               perl_threads->head = t;
+               t->prev = NULL;
+       }
+       else {
+               perl_threads->tail->next = t;
+               t->prev = perl_threads->tail;
+       }
+
+       perl_threads->tail = t;
+
+       pthread_setspecific (perl_thr_key, (const void *)t);
+       return t;
+} /* static c_ithread_t *c_ithread_create (PerlInterpreter *) */
+
+/*
  * Interface to collectd.
  */
 
 static int perl_init (void)
 {
-       if (NULL == perl)
+       dTHX;
+
+       if (NULL == perl_threads)
                return 0;
 
-       PERL_SET_CONTEXT (perl);
-       return pplugin_call_all (PLUGIN_INIT);
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("perl_init: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+       return pplugin_call_all (aTHX_ PLUGIN_INIT);
 } /* static int perl_init (void) */
 
 static int perl_read (void)
 {
-       if (NULL == perl)
+       dTHX;
+
+       if (NULL == perl_threads)
                return 0;
 
-       PERL_SET_CONTEXT (perl);
-       return pplugin_call_all (PLUGIN_READ);
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("perl_read: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+       return pplugin_call_all (aTHX_ PLUGIN_READ);
 } /* static int perl_read (void) */
 
 static int perl_write (const data_set_t *ds, const value_list_t *vl)
 {
-       if (NULL == perl)
+       dTHX;
+
+       if (NULL == perl_threads)
                return 0;
 
-       PERL_SET_CONTEXT (perl);
-       return pplugin_call_all (PLUGIN_WRITE, ds, vl);
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("perl_write: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+       return pplugin_call_all (aTHX_ PLUGIN_WRITE, ds, vl);
 } /* static int perl_write (const data_set_t *, const value_list_t *) */
 
 static void perl_log (int level, const char *msg)
 {
-       if (NULL == perl)
+       dTHX;
+
+       if (NULL == perl_threads)
                return;
 
-       PERL_SET_CONTEXT (perl);
-       pplugin_call_all (PLUGIN_LOG, level, msg);
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       pplugin_call_all (aTHX_ PLUGIN_LOG, level, msg);
        return;
 } /* static void perl_log (int, const char *) */
 
+static int perl_notify (const notification_t *notif)
+{
+       dTHX;
+
+       if (NULL == perl_threads)
+               return 0;
+
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+       return pplugin_call_all (aTHX_ PLUGIN_NOTIF, notif);
+} /* static int perl_notify (const notification_t *) */
+
 static int perl_shutdown (void)
 {
+       c_ithread_t *t = NULL;
+
        int ret = 0;
 
+       dTHX;
+
        plugin_unregister_complex_config ("perl");
 
-       if (NULL == perl)
+       if (NULL == perl_threads)
                return 0;
 
+       if (NULL == aTHX) {
+               c_ithread_t *t = NULL;
+
+               pthread_mutex_lock (&perl_threads->mutex);
+               t = c_ithread_create (perl_threads->head->interp);
+               pthread_mutex_unlock (&perl_threads->mutex);
+
+               aTHX = t->interp;
+       }
+
+       log_debug ("perl_shutdown: c_ithread: interp = %p (active threads: %i)",
+                       aTHX, perl_threads->number_of_threads);
+
        plugin_unregister_log ("perl");
+       plugin_unregister_notification ("perl");
        plugin_unregister_init ("perl");
        plugin_unregister_read ("perl");
        plugin_unregister_write ("perl");
 
-       PERL_SET_CONTEXT (perl);
-       ret = pplugin_call_all (PLUGIN_SHUTDOWN);
+       ret = pplugin_call_all (aTHX_ PLUGIN_SHUTDOWN);
 
-#if COLLECT_DEBUG
-       Perl_sv_report_used (perl);
-#endif /* COLLECT_DEBUG */
+       pthread_mutex_lock (&perl_threads->mutex);
+       t = perl_threads->tail;
+
+       while (NULL != t) {
+               c_ithread_t *thr = t;
 
-       perl_destruct (perl);
-       perl_free (perl);
-       perl = NULL;
+               /* the pointer has to be advanced before destroying
+                * the thread as this will free the memory */
+               t = t->prev;
+
+               c_ithread_destroy (thr);
+       }
+
+       pthread_mutex_unlock (&perl_threads->mutex);
+       pthread_mutex_destroy (&perl_threads->mutex);
+
+       sfree (perl_threads);
+
+       pthread_key_delete (perl_thr_key);
 
        PERL_SYS_TERM ();
 
@@ -775,10 +1255,50 @@ static int perl_shutdown (void)
        return ret;
 } /* static void perl_shutdown (void) */
 
+/*
+ * Access functions for global variables.
+ *
+ * These functions implement the "magic" used to access
+ * the global variables from Perl.
+ */
+
+static int g_pv_get (pTHX_ SV *var, MAGIC *mg)
+{
+       char *pv = mg->mg_ptr;
+       sv_setpv (var, pv);
+       return 0;
+} /* static int g_pv_get (pTHX_ SV *, MAGIC *) */
+
+static int g_pv_set (pTHX_ SV *var, MAGIC *mg)
+{
+       char *pv = mg->mg_ptr;
+       strncpy (pv, SvPV_nolen (var), DATA_MAX_NAME_LEN);
+       pv[DATA_MAX_NAME_LEN - 1] = '\0';
+       return 0;
+} /* static int g_pv_set (pTHX_ SV *, MAGIC *) */
+
+static int g_iv_get (pTHX_ SV *var, MAGIC *mg)
+{
+       int *iv = (int *)mg->mg_ptr;
+       sv_setiv (var, *iv);
+       return 0;
+} /* static int g_iv_get (pTHX_ SV *, MAGIC *) */
+
+static int g_iv_set (pTHX_ SV *var, MAGIC *mg)
+{
+       int *iv = (int *)mg->mg_ptr;
+       *iv = (int)SvIV (var);
+       return 0;
+} /* static int g_iv_set (pTHX_ SV *, MAGIC *) */
+
+static MGVTBL g_pv_vtbl = { g_pv_get, g_pv_set, NULL, NULL, NULL, NULL, NULL };
+static MGVTBL g_iv_vtbl = { g_iv_get, g_iv_set, NULL, NULL, NULL, NULL, NULL };
+
 /* bootstrap the Collectd module */
 static void xs_init (pTHX)
 {
        HV   *stash = NULL;
+       SV   *tmp   = NULL;
        char *file  = __FILE__;
 
        int i = 0;
@@ -786,25 +1306,45 @@ static void xs_init (pTHX)
        dXSUB_SYS;
 
        /* enable usage of Perl modules using shared libraries */
-       Perl_newXS (perl, "DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+       newXS ("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
 
        /* register API */
        for (i = 0; NULL != api[i].f; ++i)
-               Perl_newXS (perl, api[i].name, api[i].f, file);
+               newXS (api[i].name, api[i].f, file);
 
-       stash = Perl_gv_stashpv (perl, "Collectd", 1);
+       stash = gv_stashpv ("Collectd", 1);
 
        /* export "constants" */
        for (i = 0; '\0' != constants[i].name[0]; ++i)
-               Perl_newCONSTSUB (perl, stash, constants[i].name,
-                               Perl_newSViv (perl, constants[i].value));
+               newCONSTSUB (stash, constants[i].name, newSViv (constants[i].value));
+
+       /* export global variables
+        * by adding "magic" to the SV's representing the globale variables
+        * perl is able to automagically call the get/set function when
+        * accessing any such variable (this is basically the same as using
+        * tie() in Perl) */
+       /* global strings */
+       for (i = 0; '\0' != g_strings[i].name[0]; ++i) {
+               tmp = get_sv (g_strings[i].name, 1);
+               sv_magicext (tmp, NULL, PERL_MAGIC_ext, &g_pv_vtbl,
+                               g_strings[i].var, 0);
+       }
+
+       /* global integers */
+       for (i = 0; '\0' != g_integers[i].name[0]; ++i) {
+               tmp = get_sv (g_integers[i].name, 1);
+               sv_magicext (tmp, NULL, PERL_MAGIC_ext, &g_iv_vtbl,
+                               (char *)g_integers[i].var, 0);
+       }
        return;
 } /* static void xs_init (pTHX) */
 
 /* Initialize the global Perl interpreter. */
 static int init_pi (int argc, char **argv)
 {
-       if (NULL != perl)
+       dTHXa (NULL);
+
+       if (NULL != perl_threads)
                return 0;
 
        log_info ("Initializing Perl interpreter...");
@@ -817,27 +1357,50 @@ static int init_pi (int argc, char **argv)
        }
 #endif /* COLLECT_DEBUG */
 
+       if (0 != pthread_key_create (&perl_thr_key, c_ithread_destructor)) {
+               log_err ("init_pi: pthread_key_create failed");
+
+               /* this must not happen - cowardly giving up if it does */
+               exit (1);
+       }
+
        PERL_SYS_INIT3 (&argc, &argv, &environ);
 
-       if (NULL == (perl = perl_alloc ())) {
-               log_err ("module_register: Not enough memory.");
+       perl_threads = (c_ithread_list_t *)smalloc (sizeof (c_ithread_list_t));
+       memset (perl_threads, 0, sizeof (c_ithread_list_t));
+
+       pthread_mutex_init (&perl_threads->mutex, NULL);
+       /* locking the mutex should not be necessary at this point
+        * but let's just do it for the sake of completeness */
+       pthread_mutex_lock (&perl_threads->mutex);
+
+       perl_threads->head = c_ithread_create (NULL);
+       perl_threads->tail = perl_threads->head;
+
+       if (NULL == (perl_threads->head->interp = perl_alloc ())) {
+               log_err ("init_pi: Not enough memory.");
                exit (3);
        }
-       perl_construct (perl);
+
+       aTHX = perl_threads->head->interp;
+       pthread_mutex_unlock (&perl_threads->mutex);
+
+       perl_construct (aTHX);
 
        PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
 
-       if (0 != perl_parse (perl, xs_init, argc, argv, NULL)) {
-               log_err ("module_register: Unable to bootstrap Collectd.");
+       if (0 != perl_parse (aTHX_ xs_init, argc, argv, NULL)) {
+               log_err ("init_pi: Unable to bootstrap Collectd.");
                exit (1);
        }
 
        /* Set $0 to "collectd" because perl_parse() has to set it to "-e". */
-       Perl_sv_setpv (perl, Perl_get_sv (perl, "0", 0), "collectd");
+       sv_setpv (get_sv ("0", 0), "collectd");
 
-       perl_run (perl);
+       perl_run (aTHX);
 
        plugin_register_log ("perl", perl_log);
+       plugin_register_notification ("perl", perl_notify);
        plugin_register_init ("perl", perl_init);
 
        plugin_register_read ("perl", perl_read);
@@ -850,15 +1413,17 @@ static int init_pi (int argc, char **argv)
 /*
  * LoadPlugin "<Plugin>"
  */
-static int perl_config_loadplugin (oconfig_item_t *ci)
+static int perl_config_loadplugin (pTHX_ oconfig_item_t *ci)
 {
        char module_name[DATA_MAX_NAME_LEN];
 
        char *value = NULL;
 
        if ((0 != ci->children_num) || (1 != ci->values_num)
-                       || (OCONFIG_TYPE_STRING != ci->values[0].type))
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("LoadPlugin expects a single string argument.");
                return 1;
+       }
 
        value = ci->values[0].value.string;
 
@@ -868,24 +1433,29 @@ static int perl_config_loadplugin (oconfig_item_t *ci)
        }
 
        init_pi (perl_argc, perl_argv);
+       assert (NULL != perl_threads);
+       assert (NULL != perl_threads->head);
+
+       aTHX = perl_threads->head->interp;
 
        log_debug ("perl_config: loading perl plugin \"%s\"", value);
-       Perl_load_module (perl, PERL_LOADMOD_NOIMPORT,
-                       Perl_newSVpv (perl, module_name, strlen (module_name)),
-                       Nullsv);
+       load_module (PERL_LOADMOD_NOIMPORT,
+                       newSVpv (module_name, strlen (module_name)), Nullsv);
        return 0;
 } /* static int perl_config_loadplugin (oconfig_item_it *) */
 
 /*
  * BaseName "<Name>"
  */
-static int perl_config_basename (oconfig_item_t *ci)
+static int perl_config_basename (pTHX_ oconfig_item_t *ci)
 {
        char *value = NULL;
 
        if ((0 != ci->children_num) || (1 != ci->values_num)
-                       || (OCONFIG_TYPE_STRING != ci->values[0].type))
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("BaseName expects a single string argument.");
                return 1;
+       }
 
        value = ci->values[0].value.string;
 
@@ -898,13 +1468,20 @@ static int perl_config_basename (oconfig_item_t *ci)
 /*
  * EnableDebugger "<Package>"|""
  */
-static int perl_config_enabledebugger (oconfig_item_t *ci)
+static int perl_config_enabledebugger (pTHX_ oconfig_item_t *ci)
 {
        char *value = NULL;
 
        if ((0 != ci->children_num) || (1 != ci->values_num)
-                       || (OCONFIG_TYPE_STRING != ci->values[0].type))
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("EnableDebugger expects a single string argument.");
+               return 1;
+       }
+
+       if (NULL != perl_threads) {
+               log_warn ("EnableDebugger has no effects if used after LoadPlugin.");
                return 1;
+       }
 
        value = ci->values[0].value.string;
 
@@ -932,22 +1509,19 @@ static int perl_config_enabledebugger (oconfig_item_t *ci)
 /*
  * IncludeDir "<Dir>"
  */
-static int perl_config_includedir (oconfig_item_t *ci)
+static int perl_config_includedir (pTHX_ oconfig_item_t *ci)
 {
        char *value = NULL;
 
        if ((0 != ci->children_num) || (1 != ci->values_num)
-                       || (OCONFIG_TYPE_STRING != ci->values[0].type))
-               return 1;
-
-       if (NULL == aTHX) {
-               log_warn ("EnableDebugger has no effects if used after LoadPlugin.");
+                       || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+               log_err ("IncludeDir expects a single string argument.");
                return 1;
        }
 
        value = ci->values[0].value.string;
 
-       if (NULL == perl) {
+       if (NULL == aTHX) {
                perl_argv = (char **)realloc (perl_argv,
                                (++perl_argc + 1) * sizeof (char *));
 
@@ -964,9 +1538,8 @@ static int perl_config_includedir (oconfig_item_t *ci)
        }
        else {
                /* prepend the directory to @INC */
-               Perl_av_unshift (perl, GvAVn (PL_incgv), 1);
-               Perl_av_store (perl, GvAVn (PL_incgv),
-                               0, Perl_newSVpv (perl, value, strlen (value)));
+               av_unshift (GvAVn (PL_incgv), 1);
+               av_store (GvAVn (PL_incgv), 0, newSVpv (value, strlen (value)));
        }
        return 0;
 } /* static int perl_config_includedir (oconfig_item_it *) */
@@ -975,17 +1548,24 @@ 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;
+
        for (i = 0; i < ci->children_num; ++i) {
                oconfig_item_t *c = ci->children + i;
 
                if (0 == strcasecmp (c->key, "LoadPlugin"))
-                       perl_config_loadplugin (c);
+                       perl_config_loadplugin (aTHX_ c);
                else if (0 == strcasecmp (c->key, "BaseName"))
-                       perl_config_basename (c);
+                       perl_config_basename (aTHX_ c);
                else if (0 == strcasecmp (c->key, "EnableDebugger"))
-                       perl_config_enabledebugger (c);
+                       perl_config_enabledebugger (aTHX_ c);
                else if (0 == strcasecmp (c->key, "IncludeDir"))
-                       perl_config_includedir (c);
+                       perl_config_includedir (aTHX_ c);
                else
                        log_warn ("Ignoring unknown config key \"%s\".", c->key);
        }
index 7d2bb57..8b2803d 100644 (file)
 #include "common.h"
 #include "plugin.h"
 #include "configfile.h"
+#include "utils_avltree.h"
 #include "utils_llist.h"
+#include "utils_cache.h"
+#include "utils_threshold.h"
 
 /*
  * Private structures
@@ -51,8 +54,10 @@ static llist_t *list_init;
 static llist_t *list_read;
 static llist_t *list_write;
 static llist_t *list_shutdown;
-static llist_t *list_data_set;
 static llist_t *list_log;
+static llist_t *list_notification;
+
+static c_avl_tree_t *data_sets;
 
 static char *plugindir = NULL;
 
@@ -76,6 +81,7 @@ static const char *plugin_get_dir (void)
 static int register_callback (llist_t **list, const char *name, void *callback)
 {
        llentry_t *le;
+       char *key;
 
        if ((*list == NULL)
                        && ((*list = llist_create ()) == NULL))
@@ -84,9 +90,16 @@ static int register_callback (llist_t **list, const char *name, void *callback)
        le = llist_search (*list, name);
        if (le == NULL)
        {
-               le = llentry_create (name, callback);
+               key = strdup (name);
+               if (key == NULL)
+                       return (-1);
+
+               le = llentry_create (key, callback);
                if (le == NULL)
+               {
+                       free (key);
                        return (-1);
+               }
 
                llist_append (*list, le);
        }
@@ -108,6 +121,7 @@ static int plugin_unregister (llist_t *list, const char *name)
                return (-1);
 
        llist_remove (list, e);
+       free (e->key);
        llentry_destroy (e);
 
        return (0);
@@ -220,6 +234,7 @@ static void *plugin_read_thread (void *args)
        pthread_mutex_unlock (&read_lock);
 
        pthread_exit (NULL);
+       return ((void *) 0);
 } /* void *plugin_read_thread */
 
 static void start_threads (int num)
@@ -433,12 +448,18 @@ int plugin_register_data_set (const data_set_t *ds)
        data_set_t *ds_copy;
        int i;
 
-       if ((list_data_set != NULL)
-                       && (llist_search (list_data_set, ds->type) != NULL))
+       if ((data_sets != NULL)
+                       && (c_avl_get (data_sets, ds->type, NULL) == 0))
        {
                NOTICE ("Replacing DS `%s' with another version.", ds->type);
                plugin_unregister_data_set (ds->type);
        }
+       else if (data_sets == NULL)
+       {
+               data_sets = c_avl_create ((int (*) (const void *, const void *)) strcmp);
+               if (data_sets == NULL)
+                       return (-1);
+       }
 
        ds_copy = (data_set_t *) malloc (sizeof (data_set_t));
        if (ds_copy == NULL)
@@ -456,7 +477,7 @@ int plugin_register_data_set (const data_set_t *ds)
        for (i = 0; i < ds->ds_num; i++)
                memcpy (ds_copy->ds + i, ds->ds + i, sizeof (data_source_t));
 
-       return (register_callback (&list_data_set, ds->type, (void *) ds_copy));
+       return (c_avl_insert (data_sets, (void *) ds_copy->type, (void *) ds_copy));
 } /* int plugin_register_data_set */
 
 int plugin_register_log (char *name,
@@ -465,6 +486,12 @@ int plugin_register_log (char *name,
        return (register_callback (&list_log, name, (void *) callback));
 } /* int plugin_register_log */
 
+int plugin_register_notification (const char *name,
+               int (*callback) (const notification_t *notif))
+{
+       return (register_callback (&list_notification, name, (void *) callback));
+} /* int plugin_register_log */
+
 int plugin_unregister_config (const char *name)
 {
        cf_unregister (name);
@@ -493,6 +520,7 @@ int plugin_unregister_read (const char *name)
 
        llist_remove (list_read, e);
        free (e->value);
+       free (e->key);
        llentry_destroy (e);
 
        return (0);
@@ -510,21 +538,14 @@ int plugin_unregister_shutdown (const char *name)
 
 int plugin_unregister_data_set (const char *name)
 {
-       llentry_t  *e;
        data_set_t *ds;
 
-       if (list_data_set == NULL)
+       if (data_sets == NULL)
                return (-1);
 
-       e = llist_search (list_data_set, name);
-
-       if (e == NULL)
+       if (c_avl_remove (data_sets, name, NULL, (void *) &ds) != 0)
                return (-1);
 
-       llist_remove (list_data_set, e);
-       ds = (data_set_t *) e->value;
-       llentry_destroy (e);
-
        sfree (ds->ds);
        sfree (ds);
 
@@ -536,6 +557,11 @@ int plugin_unregister_log (const char *name)
        return (plugin_unregister (list_log, name));
 }
 
+int plugin_unregister_notification (const char *name)
+{
+       return (plugin_unregister (list_notification, name));
+}
+
 void plugin_init_all (void)
 {
        int (*callback) (void);
@@ -552,6 +578,9 @@ void plugin_init_all (void)
                start_threads ((num > 0) ? num : 5);
        }
 
+       /* Init the value cache */
+       uc_init ();
+
        if (list_init == NULL)
                return;
 
@@ -575,11 +604,13 @@ void plugin_init_all (void)
        }
 } /* void plugin_init_all */
 
-void plugin_read_all (const int *loop)
+void plugin_read_all (void)
 {
        llentry_t   *le;
        read_func_t *rf;
 
+       uc_check_timeout ();
+
        if (list_read == NULL)
                return;
 
@@ -651,7 +682,7 @@ int plugin_dispatch_values (const char *name, value_list_t *vl)
                return (-1);
        }
 
-       if (list_data_set == NULL)
+       if (data_sets == NULL)
        {
                ERROR ("plugin_dispatch_values: No data sets registered. "
                                "Could the types database be read? Check "
@@ -659,15 +690,12 @@ int plugin_dispatch_values (const char *name, value_list_t *vl)
                return (-1);
        }
 
-       le = llist_search (list_data_set, name);
-       if (le == NULL)
+       if (c_avl_get (data_sets, name, (void *) &ds) != 0)
        {
                INFO ("plugin_dispatch_values: Dataset not found: %s", name);
                return (-1);
        }
 
-       ds = (data_set_t *) le->value;
-
        DEBUG ("plugin_dispatch_values: time = %u; interval = %i; "
                        "host = %s; "
                        "plugin = %s; plugin_instance = %s; "
@@ -695,6 +723,10 @@ int plugin_dispatch_values (const char *name, value_list_t *vl)
        escape_slashes (vl->plugin_instance, sizeof (vl->plugin_instance));
        escape_slashes (vl->type_instance, sizeof (vl->type_instance));
 
+       /* Update the value cache */
+       uc_update (ds, vl);
+       ut_check_threshold (ds, vl);
+
        le = llist_head (list_write);
        while (le != NULL)
        {
@@ -707,6 +739,33 @@ int plugin_dispatch_values (const char *name, value_list_t *vl)
        return (0);
 } /* int plugin_dispatch_values */
 
+int plugin_dispatch_notification (const notification_t *notif)
+{
+       int (*callback) (const notification_t *);
+       llentry_t *le;
+       /* Possible TODO: Add flap detection here */
+
+       DEBUG ("plugin_dispatch_notification: severity = %i; message = %s; "
+                       "time = %u; host = %s;",
+                       notif->severity, notif->message,
+                       (unsigned int) notif->time, notif->host);
+
+       /* Nobody cares for notifications */
+       if (list_notification == NULL)
+               return (-1);
+
+       le = llist_head (list_notification);
+       while (le != NULL)
+       {
+               callback = (int (*) (const notification_t *)) le->value;
+               (*callback) (notif);
+
+               le = le->next;
+       }
+
+       return (0);
+} /* int plugin_dispatch_notification */
+
 void plugin_log (int level, const char *format, ...)
 {
        char msg[512];
@@ -738,66 +797,15 @@ void plugin_log (int level, const char *format, ...)
        }
 } /* void plugin_log */
 
-void plugin_complain (int level, complain_t *c, const char *format, ...)
-{
-       char message[512];
-       va_list ap;
-
-       if (c->delay > 0)
-       {
-               c->delay--;
-               return;
-       }
-
-       if (c->interval < interval_g)
-               c->interval = interval_g;
-       else
-               c->interval *= 2;
-
-       if (c->interval > 86400)
-               c->interval = 86400;
-
-       c->delay = c->interval / interval_g;
-
-       va_start (ap, format);
-       vsnprintf (message, 512, format, ap);
-       message[511] = '\0';
-       va_end (ap);
-
-       plugin_log (level, message);
-}
-
-void plugin_relief (int level, complain_t *c, const char *format, ...)
-{
-       char message[512];
-       va_list ap;
-
-       if (c->interval == 0)
-               return;
-
-       c->interval = 0;
-
-       va_start (ap, format);
-       vsnprintf (message, 512, format, ap);
-       message[511] = '\0';
-       va_end (ap);
-
-       plugin_log (level, message);
-}
-
 const data_set_t *plugin_get_ds (const char *name)
 {
        data_set_t *ds;
-       llentry_t *le;
 
-       le = llist_search (list_data_set, name);
-       if (le == NULL)
+       if (c_avl_get (data_sets, name, (void *) &ds) != 0)
        {
                DEBUG ("No such dataset registered: %s", name);
                return (NULL);
        }
 
-       ds = (data_set_t *) le->value;
-
        return (ds);
 } /* data_set_t *plugin_get_ds */
index 7692ebd..25c745c 100644 (file)
 # define LOG_DEBUG 7
 #endif
 
+#define NOTIF_MAX_MSG_LEN 256
+
+#define NOTIF_FAILURE 1
+#define NOTIF_WARNING 2
+#define NOTIF_OKAY    4
+
 /*
  * Public data types
  */
@@ -91,11 +97,17 @@ struct data_set_s
 };
 typedef struct data_set_s data_set_t;
 
-typedef struct complain_s
+typedef struct notification_s
 {
-       unsigned int interval; /* how long we wait for reporting this error again */
-       unsigned int delay;    /* how many more iterations we still need to wait */
-} complain_t;
+       int    severity;
+       time_t time;
+       char   message[NOTIF_MAX_MSG_LEN];
+       char   host[DATA_MAX_NAME_LEN];
+       char   plugin[DATA_MAX_NAME_LEN];
+       char   plugin_instance[DATA_MAX_NAME_LEN];
+       char   type[DATA_MAX_NAME_LEN];
+       char   type_instance[DATA_MAX_NAME_LEN];
+} notification_t;
 
 /*
  * NAME
@@ -136,7 +148,7 @@ void plugin_set_dir (const char *dir);
 int plugin_load (const char *name);
 
 void plugin_init_all (void);
-void plugin_read_all (const int *loop);
+void plugin_read_all (void);
 void plugin_shutdown_all (void);
 
 /*
@@ -160,6 +172,8 @@ int plugin_register_shutdown (char *name,
 int plugin_register_data_set (const data_set_t *ds);
 int plugin_register_log (char *name,
                void (*callback) (int, const char *));
+int plugin_register_notification (const char *name,
+               int (*callback) (const notification_t *notif));
 
 int plugin_unregister_config (const char *name);
 int plugin_unregister_complex_config (const char *name);
@@ -169,6 +183,7 @@ int plugin_unregister_write (const char *name);
 int plugin_unregister_shutdown (const char *name);
 int plugin_unregister_data_set (const char *name);
 int plugin_unregister_log (const char *name);
+int plugin_unregister_notification (const char *name);
 
 
 /*
@@ -188,6 +203,8 @@ int plugin_unregister_log (const char *name);
  */
 int plugin_dispatch_values (const char *name, value_list_t *vl);
 
+int plugin_dispatch_notification (const notification_t *notif);
+
 void plugin_log (int level, const char *format, ...);
 #define ERROR(...)   plugin_log (LOG_ERR,     __VA_ARGS__)
 #define WARNING(...) plugin_log (LOG_WARNING, __VA_ARGS__)
@@ -199,10 +216,6 @@ void plugin_log (int level, const char *format, ...);
 # define DEBUG(...)  /* noop */
 #endif /* ! COLLECT_DEBUG */
 
-/* TODO: Move plugin_{complain,relief} into `utils_complain.[ch]'. -octo */
-void plugin_complain (int level, complain_t *c, const char *format, ...);
-void plugin_relief (int level, complain_t *c, const char *format, ...);
-
 const data_set_t *plugin_get_ds (const char *name);
 
 #endif /* PLUGIN_H */
index bfd400c..a19f26d 100644 (file)
@@ -40,6 +40,11 @@ ipt_bytes            value:COUNTER:0:134217728
 ipt_packets            value:COUNTER:0:134217728
 irq                    value:COUNTER:U:65535
 load                   shortterm:GAUGE:0:100, midterm:GAUGE:0:100, longterm:GAUGE:0:100
+memcached_command      value:COUNTER:0:U
+memcached_connections  value:GAUGE:0:U
+memcached_items                value:GAUGE:0:U
+memcached_octets       rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
+memcached_ops          value:COUNTER:0:134217728
 memory                 value:GAUGE:0:281474976710656
 multimeter             value:GAUGE:U:U
 mysql_commands         value:COUNTER:0:U
@@ -48,11 +53,6 @@ mysql_octets         rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
 mysql_qcache           hits:COUNTER:0:U, inserts:COUNTER:0:U, not_cached:COUNTER:0:U, lowmem_prunes:COUNTER:0:U, queries_in_cache:GAUGE:0:U
 mysql_threads          running:GAUGE:0:U, connected:GAUGE:0:U, cached:GAUGE:0:U, created:COUNTER:0:U
 nfs_procedure          value:COUNTER:0:4294967295
-memcached_command      value:COUNTER:0:U
-memcached_connections  value:GAUGE:0:U
-memcached_items                value:GAUGE:0:U
-memcached_octets       rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
-memcached_ops          value:COUNTER:0:134217728
 nginx_connections      value:GAUGE:0:U
 nginx_requests         value:COUNTER:0:134217728
 percent                        percent:GAUGE:0:100.1
@@ -75,6 +75,8 @@ time_dispersion               seconds:GAUGE:-1000000:1000000
 timeleft               timeleft:GAUGE:0:3600
 time_offset            seconds:GAUGE:-1000000:1000000
 users                  users:GAUGE:0:65535
+virt_cpu_total         ns:COUNTER:0:256000000000
+virt_vcpu              ns:COUNTER:0:1000000000
 voltage_threshold      value:GAUGE:U:U, threshold:GAUGE:U:U
 voltage                        value:GAUGE:U:U
 vs_memory              value:GAUGE:0:9223372036854775807
diff --git a/src/types.db.pod b/src/types.db.pod
new file mode 100644 (file)
index 0000000..f0a49f6
--- /dev/null
@@ -0,0 +1,51 @@
+=head1 NAME
+
+types.db - Data-set specifications for the system statistics collection daemon
+B<collectd>
+
+=head1 SYNOPSIS
+
+  bitrate    value:GAUGE:0:4294967295
+  counter    value:COUNTER:U:U
+  if_octets  rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
+
+=head1 DESCRIPTION
+
+The types.db file contains one line for each data-set specification. Each line
+consists of two fields delimited by spaces and/or horizontal tabs. The first
+field defines the name of the data-set, while the second field defines a list
+of data-source specifications, delimited by spaces and, optionally, a comma
+(",") right after each list-entry.
+
+The format of the data-source specification has been inspired by RRDtool's
+data-source specification. Each data-source is defined by a quadruple made up
+of the data-source name, type, minimal and maximal values, delimited by colons
+(":"): I<ds-name>:I<ds-type>:I<min>:I<max>. I<ds-type> may be either
+B<COUNTER> or B<GAUGE>. I<min> and I<max> define the range of valid values for
+data stored for this data-source. If B<U> is specified for either the min or
+max value, it will be set to unknown, meaning that no range checks will
+happen. See L<rrdcreate(1)> for more details.
+
+=head1 FILES
+
+The location of the types.db file is defined by the B<TypesDB> configuration
+option (see L<collectd.conf(5)>). If you want to specify custom data-sets, you
+should do so by using a custom file specified as an additional argument to the
+B<TypesDB> option.
+
+=head1 SEE ALSO
+
+L<collectd(1)>,
+L<collectd.conf(5)>,
+L<rrdcreate(1)>
+
+=head1 AUTHOR
+
+B<collectd> has been written by Florian Forster
+E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>.
+
+This manpage has been written by Sebastian Harl
+E<lt>shE<nbsp>atE<nbsp>tokkee.orgE<gt>.
+
+=cut
+
index 002761c..ff84262 100644 (file)
@@ -91,7 +91,7 @@ static int parse_ds (data_source_t *dsrc, char *buf, size_t buf_len)
   return (0);
 } /* int parse_ds */
 
-static void parse_line (char *buf, size_t buf_len)
+static void parse_line (char *buf)
 {
   char  *fields[64];
   size_t fields_num;
@@ -165,21 +165,16 @@ static void parse_file (FILE *fh)
     if (buf_len == 0)
       continue;
 
-    parse_line (buf, buf_len);
+    parse_line (buf);
   } /* while (fgets) */
 } /* void parse_file */
 
-int read_types_list (void)
+int read_types_list (const char *file)
 {
-  const char *file;
   FILE *fh;
 
-  file = global_option_get ("TypesDB");
   if (file == NULL)
-  {
-    ERROR ("global_option_get (\"TypesDB\") returned NULL.");
     return (-1);
-  }
 
   fh = fopen (file, "r");
   if (fh == NULL)
index c7e6aa0..8fe6ce8 100644 (file)
@@ -22,6 +22,6 @@
  *   Florian octo Forster <octo at verplant.org>
  **/
 
-int read_types_list (void);
+int read_types_list (const char *file);
 
 #endif /* TYPES_LIST_H */
index f8c9fbf..45ed9c6 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * collectd - src/unixsock.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,7 +23,9 @@
 #include "common.h"
 #include "plugin.h"
 #include "configfile.h"
+
 #include "utils_cmd_putval.h"
+#include "utils_cmd_putnotif.h"
 
 /* Folks without pthread will need to disable this plugin. */
 #include <pthread.h>
@@ -81,7 +83,7 @@ static pthread_t listen_thread = (pthread_t) 0;
 /* Linked list and auxilliary variables for saving values */
 static value_cache_t   *cache_head = NULL;
 static pthread_mutex_t  cache_lock = PTHREAD_MUTEX_INITIALIZER;
-static unsigned int     cache_oldest = UINT_MAX;
+static time_t           cache_oldest = -1;
 
 /*
  * Functions
@@ -188,7 +190,7 @@ static int cache_insert (const data_set_t *ds, const value_list_t *vl)
        cache_head = vc;
 
        vc->time = vl->time;
-       if (vc->time < cache_oldest)
+       if ((vc->time < cache_oldest) || (-1 == cache_oldest))
                cache_oldest = vc->time;
 
        pthread_mutex_unlock (&cache_lock);
@@ -275,7 +277,7 @@ static int cache_update (const data_set_t *ds, const value_list_t *vl)
        vc->ds = ds;
        vc->time = vl->time;
 
-       if (vc->time < cache_oldest)
+       if ((vc->time < cache_oldest) || (-1 == cache_oldest))
                cache_oldest = vc->time;
 
        pthread_mutex_unlock (&cache_lock);
@@ -611,6 +613,10 @@ static void *us_handle_client (void *arg)
                {
                        us_handle_listval (fhout, fields, fields_num);
                }
+               else if (strcasecmp (fields[0], "putnotif") == 0)
+               {
+                       handle_putnotif (fhout, fields, fields_num);
+               }
                else
                {
                        fprintf (fhout, "-1 Unknown command: %s\n", fields[0]);
@@ -623,6 +629,7 @@ static void *us_handle_client (void *arg)
        fclose (fhout);
 
        pthread_exit ((void *) 0);
+       return ((void *) 0);
 } /* void *us_handle_client */
 
 static void *us_server_thread (void *arg)
index 83821fe..9f0b796 100644 (file)
@@ -264,7 +264,7 @@ static void rebalance (c_avl_tree_t *t, c_avl_node_t *n)
        } /* while (n != NULL) */
 } /* void rebalance */
 
-static c_avl_node_t *c_avl_node_next (c_avl_tree_t *t, c_avl_node_t *n)
+static c_avl_node_t *c_avl_node_next (c_avl_node_t *n)
 {
        c_avl_node_t *r; /* return node */
 
@@ -309,7 +309,7 @@ static c_avl_node_t *c_avl_node_next (c_avl_tree_t *t, c_avl_node_t *n)
        return (r);
 } /* c_avl_node_t *c_avl_node_next */
 
-static c_avl_node_t *c_avl_node_prev (c_avl_tree_t *t, c_avl_node_t *n)
+static c_avl_node_t *c_avl_node_prev (c_avl_node_t *n)
 {
        c_avl_node_t *r; /* return node */
 
@@ -364,13 +364,13 @@ static int _remove (c_avl_tree_t *t, c_avl_node_t *n)
                if (BALANCE (n) > 0) /* left subtree is higher */
                {
                        assert (n->left != NULL);
-                       r = c_avl_node_prev (t, n);
+                       r = c_avl_node_prev (n);
                        
                }
                else /* right subtree is higher */
                {
                        assert (n->right != NULL);
-                       r = c_avl_node_next (t, n);
+                       r = c_avl_node_next (n);
                }
 
                assert ((r->left == NULL) || (r->right == NULL));
@@ -518,7 +518,7 @@ int c_avl_insert (c_avl_tree_t *t, void *key, void *value)
                if (cmp == 0)
                {
                        free_node (new);
-                       return (-1);
+                       return (1);
                }
                else if (cmp < 0)
                {
@@ -662,7 +662,7 @@ int c_avl_iterator_next (c_avl_iterator_t *iter, void **key, void **value)
        }
        else
        {
-               n = c_avl_node_next (iter->tree, iter->node);
+               n = c_avl_node_next (iter->node);
        }
 
        if (n == NULL)
@@ -691,7 +691,7 @@ int c_avl_iterator_prev (c_avl_iterator_t *iter, void **key, void **value)
        }
        else
        {
-               n = c_avl_node_prev (iter->tree, iter->node);
+               n = c_avl_node_prev (iter->node);
        }
 
        if (n == NULL)
index 5d35af1..355ced2 100644 (file)
@@ -70,12 +70,15 @@ void c_avl_destroy (c_avl_tree_t *t);
  * PARAMETERS
  *   `t'        AVL-tree to store the data in.
  *   `key'      Key used to store the value under. This is used to get back to
- *              the value again.
+ *              the value again. The pointer is stored in an internal structure
+ *              and _not_ copied. So the memory pointed to may _not_ be freed
+ *              before this entry is removed. You can use the `rkey' argument
+ *              to `avl_remove' to get the original pointer back and free it.
  *   `value'    Value to be stored.
  *
  * RETURN VALUE
- *   Zero upon success and non-zero upon failure and if the key is already
- *   stored in the tree.
+ *   Zero upon success, non-zero otherwise. It's less than zero if an error
+ *   occurred or greater than zero if the key is already stored in the tree.
  */
 int c_avl_insert (c_avl_tree_t *t, void *key, void *value);
 
@@ -91,6 +94,10 @@ int c_avl_insert (c_avl_tree_t *t, void *key, void *value);
  *   `t'       AVL-tree to remove key-value-pair from.
  *   `key'      Key to identify the entry.
  *   `rkey'     Pointer to a pointer in which to store the key. May be NULL.
+ *              Since the `key' pointer is not copied when creating an entry,
+ *              the pointer may not be available anymore from outside the tree.
+ *              You can use this argument to get the actual pointer back and
+ *              free the memory pointed to by it.
  *   `rvalue'   Pointer to a pointer in which to store the value. May be NULL.
  *
  * RETURN VALUE
diff --git a/src/utils_cache.c b/src/utils_cache.c
new file mode 100644 (file)
index 0000000..b9b8962
--- /dev/null
@@ -0,0 +1,548 @@
+/**
+ * collectd - src/utils_cache.c
+ * Copyright (C) 2007  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 verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_avltree.h"
+#include "utils_cache.h"
+#include "utils_threshold.h"
+
+#include <assert.h>
+#include <pthread.h>
+
+typedef struct cache_entry_s
+{
+       char name[6 * DATA_MAX_NAME_LEN];
+       int        values_num;
+       gauge_t   *values_gauge;
+       counter_t *values_counter;
+       /* Time contained in the package
+        * (for calculating rates) */
+       time_t last_time;
+       /* Time according to the local clock
+        * (for purging old entries) */
+       time_t last_update;
+       /* Interval in which the data is collected
+        * (for purding old entries) */
+       int interval;
+       int state;
+} cache_entry_t;
+
+static c_avl_tree_t   *cache_tree = NULL;
+static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int cache_compare (const cache_entry_t *a, const cache_entry_t *b)
+{
+  assert ((a != NULL) && (b != NULL));
+  return (strcmp (a->name, b->name));
+} /* int cache_compare */
+
+static cache_entry_t *cache_alloc (int values_num)
+{
+  cache_entry_t *ce;
+
+  ce = (cache_entry_t *) malloc (sizeof (cache_entry_t));
+  if (ce == NULL)
+  {
+    ERROR ("utils_cache: cache_alloc: malloc failed.");
+    return (NULL);
+  }
+  memset (ce, '\0', sizeof (cache_entry_t));
+  ce->values_num = values_num;
+
+  ce->values_gauge = (gauge_t *) calloc (values_num, sizeof (gauge_t));
+  ce->values_counter = (counter_t *) calloc (values_num, sizeof (counter_t));
+  if ((ce->values_gauge == NULL) || (ce->values_counter == NULL))
+  {
+    sfree (ce->values_gauge);
+    sfree (ce->values_counter);
+    sfree (ce);
+    ERROR ("utils_cache: cache_alloc: calloc failed.");
+    return (NULL);
+  }
+
+  return (ce);
+} /* cache_entry_t *cache_alloc */
+
+static void cache_free (cache_entry_t *ce)
+{
+  if (ce == NULL)
+    return;
+
+  sfree (ce->values_gauge);
+  sfree (ce->values_counter);
+  sfree (ce);
+} /* void cache_free */
+
+static int uc_send_notification (const char *name)
+{
+  cache_entry_t *ce = NULL;
+  int status;
+
+  char *name_copy;
+  char *host;
+  char *plugin;
+  char *plugin_instance;
+  char *type;
+  char *type_instance;
+
+  notification_t n;
+
+  name_copy = strdup (name);
+  if (name_copy == NULL)
+  {
+    ERROR ("uc_send_notification: strdup failed.");
+    return (-1);
+  }
+
+  status = parse_identifier (name_copy, &host,
+      &plugin, &plugin_instance,
+      &type, &type_instance);
+  if (status != 0)
+  {
+    ERROR ("uc_send_notification: Cannot parse name `%s'", name);
+    return (-1);
+  }
+
+  /* Copy the associative members */
+  notification_init (&n, NOTIF_FAILURE, /* host = */ NULL,
+      host, plugin, plugin_instance, type, type_instance);
+
+  sfree (name_copy);
+  name_copy = host = plugin = plugin_instance = type = type_instance = NULL;
+
+  pthread_mutex_lock (&cache_lock);
+
+  /*
+   * Set the time _after_ getting the lock because we don't know how long
+   * acquiring the lock takes and we will use this time later to decide
+   * whether or not the state is OKAY.
+   */
+  n.time = time (NULL);
+
+  status = c_avl_get (cache_tree, name, (void *) &ce);
+  if (status != 0)
+  {
+    pthread_mutex_unlock (&cache_lock);
+    sfree (name_copy);
+    return (-1);
+  }
+    
+  /* Check if the entry has been updated in the meantime */
+  if ((n.time - ce->last_update) < (2 * ce->interval))
+  {
+    ce->state = STATE_OKAY;
+    pthread_mutex_unlock (&cache_lock);
+    sfree (name_copy);
+    return (-1);
+  }
+
+  snprintf (n.message, sizeof (n.message),
+      "%s has not been updated for %i seconds.", name,
+      (int) (n.time - ce->last_update));
+
+  pthread_mutex_unlock (&cache_lock);
+
+  n.message[sizeof (n.message) - 1] = '\0';
+  plugin_dispatch_notification (&n);
+
+  return (0);
+} /* int uc_send_notification */
+
+static int uc_insert (const data_set_t *ds, const value_list_t *vl,
+    const char *key)
+{
+  int i;
+  char *key_copy;
+  cache_entry_t *ce;
+
+  /* `cache_lock' has been locked by `uc_update' */
+
+  key_copy = strdup (key);
+  if (key_copy == NULL)
+  {
+    ERROR ("uc_insert: strdup failed.");
+    return (-1);
+  }
+
+  ce = cache_alloc (ds->ds_num);
+  if (ce == NULL)
+  {
+    ERROR ("uc_insert: cache_alloc (%i) failed.", ds->ds_num);
+    return (-1);
+  }
+
+  sstrncpy (ce->name, key, sizeof (ce->name));
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+    {
+      ce->values_gauge[i] = NAN;
+      ce->values_counter[i] = vl->values[i].counter;
+    }
+    else /* if (ds->ds[i].type == DS_TYPE_GAUGE) */
+    {
+      ce->values_gauge[i] = vl->values[i].gauge;
+    }
+  } /* for (i) */
+
+  ce->last_time = vl->time;
+  ce->last_update = time (NULL);
+  ce->interval = vl->interval;
+  ce->state = STATE_OKAY;
+
+  if (c_avl_insert (cache_tree, key_copy, ce) != 0)
+  {
+    sfree (key_copy);
+    ERROR ("uc_insert: c_avl_insert failed.");
+    return (-1);
+  }
+
+  DEBUG ("uc_insert: Added %s to the cache.", key);
+  return (0);
+} /* int uc_insert */
+
+int uc_init (void)
+{
+  if (cache_tree == NULL)
+    cache_tree = c_avl_create ((int (*) (const void *, const void *))
+       cache_compare);
+
+  return (0);
+} /* int uc_init */
+
+int uc_check_timeout (void)
+{
+  time_t now;
+  cache_entry_t *ce;
+
+  char **keys = NULL;
+  int keys_len = 0;
+
+  char *key;
+  c_avl_iterator_t *iter;
+  int i;
+  
+  pthread_mutex_lock (&cache_lock);
+
+  now = time (NULL);
+
+  /* Build a list of entries to be flushed */
+  iter = c_avl_get_iterator (cache_tree);
+  while (c_avl_iterator_next (iter, (void *) &key, (void *) &ce) == 0)
+  {
+    /* If entry has not been updated, add to `keys' array */
+    if ((now - ce->last_update) >= (2 * ce->interval))
+    {
+      char **tmp;
+
+      tmp = (char **) realloc ((void *) keys,
+         (keys_len + 1) * sizeof (char *));
+      if (tmp == NULL)
+      {
+       ERROR ("uc_purge: realloc failed.");
+       c_avl_iterator_destroy (iter);
+       return (-1);
+      }
+
+      keys = tmp;
+      keys[keys_len] = strdup (key);
+      if (keys[keys_len] == NULL)
+      {
+       ERROR ("uc_check_timeout: strdup failed.");
+       continue;
+      }
+      keys_len++;
+    }
+  } /* while (c_avl_iterator_next) */
+
+  for (i = 0; i < keys_len; i++)
+  {
+    int status;
+
+    status = ut_check_interesting (keys[i]);
+
+    if (status < 0)
+    {
+      ERROR ("uc_check_timeout: ut_check_interesting failed.");
+      sfree (keys[i]);
+    }
+    else if (status == 0) /* ``service'' is uninteresting */
+    {
+      ce = NULL;
+      DEBUG ("uc_check_timeout: %s is missing but ``uninteresting''",
+         keys[i]);
+      status = c_avl_remove (cache_tree, keys[i],
+         (void *) &key, (void *) &ce);
+      if (status != 0)
+      {
+       ERROR ("uc_check_timeout: c_avl_remove (%s) failed.", keys[i]);
+      }
+      sfree (keys[i]);
+      cache_free (ce);
+    }
+    else if (status == 1) /* persist */
+    {
+      DEBUG ("uc_check_timeout: %s is missing, sending notification.",
+         keys[i]);
+      ce->state = STATE_MISSING;
+    }
+    else if (status == 2) /* do not persist */
+    {
+      if (ce->state == STATE_MISSING)
+      {
+       DEBUG ("uc_check_timeout: %s is missing but "
+           "notification has already been sent.",
+           keys[i]);
+       sfree (keys[i]);
+      }
+      else /* (ce->state != STATE_MISSING) */
+      {
+       DEBUG ("uc_check_timeout: %s is missing, sending one notification.",
+           keys[i]);
+       ce->state = STATE_MISSING;
+      }
+    }
+    else
+    {
+      WARNING ("uc_check_timeout: ut_check_interesting (%s) returned ",
+         "invalid status %i.",
+         keys[i], status);
+    }
+  } /* for (keys[i]) */
+
+  c_avl_iterator_destroy (iter);
+
+  pthread_mutex_unlock (&cache_lock);
+
+  for (i = 0; i < keys_len; i++)
+  {
+    if (keys[i] == NULL)
+      continue;
+
+    uc_send_notification (keys[i]);
+    sfree (keys[i]);
+  }
+
+  sfree (keys);
+
+  return (0);
+} /* int uc_check_timeout */
+
+int uc_update (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int send_okay_notification = 0;
+  time_t update_delay = 0;
+  notification_t n;
+  int status;
+  int i;
+
+  if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
+  {
+    ERROR ("uc_update: FORMAT_VL failed.");
+    return (-1);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  status = c_avl_get (cache_tree, name, (void *) &ce);
+  if (status != 0) /* entry does not yet exist */
+  {
+    status = uc_insert (ds, vl, name);
+    pthread_mutex_unlock (&cache_lock);
+    return (status);
+  }
+
+  assert (ce != NULL);
+  assert (ce->values_num == ds->ds_num);
+
+  if (ce->last_time >= vl->time)
+  {
+    pthread_mutex_unlock (&cache_lock);
+    NOTICE ("uc_update: Value too old: name = %s; value time = %u; "
+       "last cache update = %u;",
+       name, (unsigned int) vl->time, (unsigned int) ce->last_time);
+    return (-1);
+  }
+
+  /* Send a notification (after the lock has been released) if we switch the
+   * state from something else to `okay'. */
+  if (ce->state == STATE_MISSING)
+  {
+    send_okay_notification = 1;
+    ce->state = STATE_OKAY;
+    update_delay = time (NULL) - ce->last_update;
+  }
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    if (ds->ds[i].type == DS_TYPE_COUNTER)
+    {
+      counter_t diff;
+
+      /* check if the counter has wrapped around */
+      if (vl->values[i].counter < ce->values_counter[i])
+      {
+       if (ce->values_counter[i] <= 4294967295U)
+         diff = (4294967295U - ce->values_counter[i])
+           + vl->values[i].counter;
+       else
+         diff = (18446744073709551615ULL - ce->values_counter[i])
+           + vl->values[i].counter;
+      }
+      else /* counter has NOT wrapped around */
+      {
+       diff = vl->values[i].counter - ce->values_counter[i];
+      }
+
+      ce->values_gauge[i] = ((double) diff)
+       / ((double) (vl->time - ce->last_time));
+      ce->values_counter[i] = vl->values[i].counter;
+    }
+    else /* if (ds->ds[i].type == DS_TYPE_GAUGE) */
+    {
+      ce->values_gauge[i] = vl->values[i].gauge;
+    }
+    DEBUG ("uc_update: %s: ds[%i] = %lf", name, i, ce->values_gauge[i]);
+  } /* for (i) */
+
+  ce->last_time = vl->time;
+  ce->last_update = time (NULL);
+  ce->interval = vl->interval;
+
+  pthread_mutex_unlock (&cache_lock);
+
+  if (send_okay_notification == 0)
+    return (0);
+
+  /* Do not send okay notifications for uninteresting values, i. e. values for
+   * which no threshold is configured. */
+  status = ut_check_interesting (name);
+  if (status <= 0)
+    return (0);
+
+  /* Initialize the notification */
+  memset (&n, '\0', sizeof (n));
+  NOTIFICATION_INIT_VL (&n, vl, ds);
+
+  n.severity = NOTIF_OKAY;
+  n.time = vl->time;
+
+  snprintf (n.message, sizeof (n.message),
+      "Received a value for %s. It was missing for %u seconds.",
+      name, (unsigned int) update_delay);
+  n.message[sizeof (n.message) - 1] = '\0';
+
+  plugin_dispatch_notification (&n);
+
+  return (0);
+} /* int uc_update */
+
+gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  gauge_t *ret = NULL;
+  cache_entry_t *ce = NULL;
+
+  if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
+  {
+    ERROR ("uc_get_rate: FORMAT_VL failed.");
+    return (NULL);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    assert (ce->values_num == ds->ds_num);
+
+    ret = (gauge_t *) malloc (ce->values_num * sizeof (gauge_t));
+    if (ret == NULL)
+    {
+      ERROR ("uc_get_rate: malloc failed.");
+    }
+    else
+    {
+      memcpy (ret, ce->values_gauge, ce->values_num * sizeof (gauge_t));
+    }
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* gauge_t *uc_get_rate */
+
+int uc_get_state (const data_set_t *ds, const value_list_t *vl)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int ret = STATE_ERROR;
+
+  if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
+  {
+    ERROR ("uc_get_state: FORMAT_VL failed.");
+    return (STATE_ERROR);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    ret = ce->state;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* int uc_get_state */
+
+int uc_set_state (const data_set_t *ds, const value_list_t *vl, int state)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  cache_entry_t *ce = NULL;
+  int ret = -1;
+
+  if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
+  {
+    ERROR ("uc_get_state: FORMAT_VL failed.");
+    return (STATE_ERROR);
+  }
+
+  pthread_mutex_lock (&cache_lock);
+
+  if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
+  {
+    assert (ce != NULL);
+    ret = ce->state;
+    ce->state = state;
+  }
+
+  pthread_mutex_unlock (&cache_lock);
+
+  return (ret);
+} /* int uc_set_state */
+/* vim: set sw=2 ts=8 sts=2 tw=78 : */
diff --git a/src/utils_cache.h b/src/utils_cache.h
new file mode 100644 (file)
index 0000000..51d9c03
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * collectd - src/utils_cache.h
+ * Copyright (C) 2007  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 verplant.org>
+ **/
+
+#ifndef UTILS_CACHE_H
+#define UTILS_CACHE_H 1
+
+#include "plugin.h"
+
+#define STATE_OKAY     0
+#define STATE_WARNING  1
+#define STATE_ERROR    2
+#define STATE_MISSING 15
+
+int uc_init (void);
+int uc_check_timeout (void);
+int uc_update (const data_set_t *ds, const value_list_t *vl);
+gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl);
+
+int uc_get_state (const data_set_t *ds, const value_list_t *vl);
+int uc_set_state (const data_set_t *ds, const value_list_t *vl, int state);
+
+/* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
+#endif /* !UTILS_CACHE_H */
diff --git a/src/utils_cmd_putnotif.c b/src/utils_cmd_putnotif.c
new file mode 100644 (file)
index 0000000..18c1ece
--- /dev/null
@@ -0,0 +1,171 @@
+/**
+ * collectd - src/utils_cms_putnotif.c
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+static int parse_option_severity (notification_t *n, char *value)
+{
+  if (strcasecmp (value, "Failure") == 0)
+    n->severity = NOTIF_FAILURE;
+  else if (strcasecmp (value, "Warning") == 0)
+    n->severity = NOTIF_WARNING;
+  else if (strcasecmp (value, "Okay") == 0)
+    n->severity = NOTIF_OKAY;
+  else
+    return (-1);
+
+  return (0);
+} /* int parse_option_severity */
+
+static int parse_option_time (notification_t *n, char *value)
+{
+  time_t tmp;
+  
+  tmp = (time_t) atoi (value);
+  if (tmp <= 0)
+    return (-1);
+
+  n->time = tmp;
+
+  return (0);
+} /* int parse_option_time */
+
+static int parse_option (notification_t *n, char *buffer)
+{
+  char *option = buffer;
+  char *value;
+
+  if ((n == NULL) || (option == NULL))
+    return (-1);
+
+  value = strchr (option, '=');
+  if (value == NULL)
+    return (-1);
+  *value = '\0'; value++;
+
+  if (strcasecmp ("severity", option) == 0)
+    return (parse_option_severity (n, value));
+  else if (strcasecmp ("time", option) == 0)
+    return (parse_option_time (n, value));
+  else if (strcasecmp ("host", option) == 0)
+    sstrncpy (n->host, value, sizeof (n->host));
+  else if (strcasecmp ("plugin", option) == 0)
+    sstrncpy (n->plugin, value, sizeof (n->plugin));
+  else if (strcasecmp ("plugin_instance", option) == 0)
+    sstrncpy (n->plugin_instance, value, sizeof (n->plugin_instance));
+  else if (strcasecmp ("type", option) == 0)
+    sstrncpy (n->type, value, sizeof (n->type));
+  else if (strcasecmp ("type_instance", option) == 0)
+    sstrncpy (n->type_instance, value, sizeof (n->type_instance));
+  else
+    return (1);
+
+  return (0);
+} /* int parse_option */
+
+static int parse_message (notification_t *n, char **fields, int fields_num)
+{
+  int status;
+
+  /* Strip off the leading `message=' */
+  fields[0] += strlen ("message=");
+
+  status = strjoin (n->message, sizeof (n->message), fields, fields_num, " ");
+  if (status < 0)
+    return (-1);
+
+  return (0);
+} /* int parse_message */
+
+int handle_putnotif (FILE *fh, char **fields, int fields_num)
+{
+  notification_t n;
+  int status;
+  int i;
+
+  /* Required fields: `PUTNOTIF', severity, time, message */
+  if (fields_num < 4)
+  {
+    DEBUG ("cmd putnotif: Wrong number of fields: %i", fields_num);
+    fprintf (fh, "-1 Wrong number of fields: Got %i, expected at least 4.\n",
+       fields_num);
+    fflush (fh);
+    return (-1);
+  }
+
+  memset (&n, '\0', sizeof (n));
+
+  status = 0;
+  for (i = 1; i < fields_num; i++)
+  {
+    if (strncasecmp (fields[i], "message=", strlen ("message=")) == 0)
+    {
+      status = parse_message (&n, fields + i, fields_num - i);
+      if (status != 0)
+      {
+       fprintf (fh, "-1 Error parsing the message. Have you hit the "
+           "limit of %u bytes?\n", (unsigned int) sizeof (n.message));
+      }
+      break;
+    }
+    else
+    {
+      status = parse_option (&n, fields[i]);
+      if (status != 0)
+      {
+       fprintf (fh, "-1 Error parsing option `%s'\n", fields[i]);
+       break;
+      }
+    }
+  } /* for (i) */
+
+  /* Check for required fields and complain if anything is missing. */
+  if ((status == 0) && (n.severity == 0))
+  {
+    fprintf (fh, "-1 Option `severity' missing.\n");
+    status = -1;
+  }
+  if ((status == 0) && (n.time == 0))
+  {
+    fprintf (fh, "-1 Option `time' missing.\n");
+    status = -1;
+  }
+  if ((status == 0) && (strlen (n.message) == 0))
+  {
+    fprintf (fh, "-1 No message or message of length 0 given.\n");
+    status = -1;
+  }
+
+  /* If status is still zero the notification is fine and we can finally
+   * dispatch it. */
+  if (status == 0)
+  {
+    plugin_dispatch_notification (&n);
+    fprintf (fh, "0 Success\n");
+  }
+  fflush (fh);
+
+  return (0);
+} /* int handle_putnotif */
+
+/* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
diff --git a/src/utils_cmd_putnotif.h b/src/utils_cmd_putnotif.h
new file mode 100644 (file)
index 0000000..a953172
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * collectd - src/utils_cms_putnotif.h
+ * Copyright (C) 2008  Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ *
+ * Author:
+ *   Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_PUTNOTIF_H
+#define UTILS_CMD_PUTNOTIF_H 1
+
+#include "plugin.h"
+
+int handle_putnotif (FILE *fh, char **fields, int fields_num);
+
+/* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
+
+#endif /* UTILS_CMD_PUTNOTIF_H */
index 7cf0b6d..98b5043 100644 (file)
@@ -36,7 +36,7 @@ static int parse_value (const data_set_t *ds, value_list_t *vl,
        char *value_str = strchr (time_str, ':');
        if (value_str == NULL)
        {
-               fprintf (fh, "-1 No time found.");
+               fprintf (fh, "-1 No time found.\n");
                return (-1);
        }
        *value_str = '\0'; value_str++;
@@ -121,6 +121,8 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
        int   status;
        int   i;
 
+       char *identifier_copy;
+
        const data_set_t *ds;
        value_list_t vl = VALUE_LIST_INIT;
 
@@ -135,7 +137,11 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
                return (-1);
        }
 
-       status = parse_identifier (fields[1], &hostname,
+       /* parse_identifier() modifies its first argument,
+        * returning pointers into it */
+       identifier_copy = sstrdup (fields[1]);
+
+       status = parse_identifier (identifier_copy, &hostname,
                        &plugin, &plugin_instance,
                        &type, &type_instance);
        if (status != 0)
@@ -143,6 +149,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
                DEBUG ("cmd putval: Cannot parse `%s'", fields[1]);
                fprintf (fh, "-1 Cannot parse identifier.\n");
                fflush (fh);
+               sfree (identifier_copy);
                return (-1);
        }
 
@@ -153,7 +160,9 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
                        || ((type_instance != NULL)
                                && (strlen (type_instance) >= sizeof (vl.type_instance))))
        {
-               fprintf (fh, "-1 Identifier too long.");
+               fprintf (fh, "-1 Identifier too long.\n");
+               fflush (fh);
+               sfree (identifier_copy);
                return (-1);
        }
 
@@ -165,14 +174,18 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
                strcpy (vl.type_instance, type_instance);
 
        ds = plugin_get_ds (type);
-       if (ds == NULL)
+       if (ds == NULL) {
+               sfree (identifier_copy);
                return (-1);
+       }
 
        vl.values_len = ds->ds_num;
        vl.values = (value_t *) malloc (vl.values_len * sizeof (value_t));
        if (vl.values == NULL)
        {
-               fprintf (fh, "-1 malloc failed.");
+               fprintf (fh, "-1 malloc failed.\n");
+               fflush (fh);
+               sfree (identifier_copy);
                return (-1);
        }
 
@@ -191,7 +204,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
                {
                        if (parse_option (&vl, fields[i]) != 0)
                        {
-                               fprintf (fh, "-1 Error parsing option `%s'",
+                               fprintf (fh, "-1 Error parsing option `%s'\n",
                                                fields[i]);
                                break;
                        }
@@ -211,6 +224,7 @@ int handle_putval (FILE *fh, char **fields, int fields_num)
        fflush (fh);
 
        sfree (vl.values); 
+       sfree (identifier_copy);
 
        return (0);
 } /* int handle_putval */
index a412809..25ef189 100644 (file)
@@ -429,13 +429,16 @@ static int
 handle_ipv6 (struct ip6_hdr *ipv6, int len)
 {
     char buf[PCAP_SNAPLEN];
-    int offset;
+    unsigned int offset;
     int nexthdr;
 
     struct in6_addr s_addr;
     struct in6_addr d_addr;
     uint16_t payload_len;
 
+    if (0 > len)
+       return (0);
+
     offset = sizeof (struct ip6_hdr);
     nexthdr = ipv6->ip6_nxt;
     s_addr = ipv6->ip6_src;
@@ -459,7 +462,7 @@ handle_ipv6 (struct ip6_hdr *ipv6, int len)
        uint16_t ext_hdr_len;
 
        /* Catch broken packets */
-       if ((offset + sizeof (struct ip6_ext)) > len)
+       if ((offset + sizeof (struct ip6_ext)) > (unsigned int)len)
            return (0);
 
        /* Cannot handle fragments. */
@@ -479,7 +482,7 @@ handle_ipv6 (struct ip6_hdr *ipv6, int len)
     } /* while */
 
     /* Catch broken and empty packets */
-    if (((offset + payload_len) > len)
+    if (((offset + payload_len) > (unsigned int)len)
            || (payload_len == 0)
            || (payload_len > PCAP_SNAPLEN))
        return (0);
@@ -620,7 +623,7 @@ handle_linux_sll (const u_char *pkt, int len)
     } *hdr;
     uint16_t etype;
 
-    if (len < sizeof (struct sll_header))
+    if ((0 > len) || ((unsigned int)len < sizeof (struct sll_header)))
        return (0);
 
     hdr  = (struct sll_header *) pkt;
index bf39597..94d6bda 100644 (file)
@@ -217,10 +217,6 @@ ignorelist_t *ignorelist_create (int invert)
 
        /* smalloc exits if it failes */
        il = (ignorelist_t *) smalloc (sizeof (ignorelist_t));
-       DEBUG("Ignorelist created 0x%p, default is %s",
-                       (void *) il,
-                       invert ? "collect" : "ignore");
-
        memset (il, '\0', sizeof (ignorelist_t));
 
        /*
index d5db9dc..7fae025 100644 (file)
@@ -35,6 +35,7 @@ struct llist_s
 {
        llentry_t *head;
        llentry_t *tail;
+       int size;
 };
 
 /*
@@ -67,22 +68,16 @@ void llist_destroy (llist_t *l)
        free (l);
 }
 
-llentry_t *llentry_create (const char *key, void *value)
+llentry_t *llentry_create (char *key, void *value)
 {
        llentry_t *e;
 
        e = (llentry_t *) malloc (sizeof (llentry_t));
-       if (e == NULL)
-               return (NULL);
-
-       e->key   = strdup (key);
-       e->value = value;
-       e->next  = NULL;
-
-       if (e->key == NULL)
+       if (e)
        {
-               free (e);
-               return (NULL);
+               e->key   = key;
+               e->value = value;
+               e->next  = NULL;
        }
 
        return (e);
@@ -90,7 +85,6 @@ llentry_t *llentry_create (const char *key, void *value)
 
 void llentry_destroy (llentry_t *e)
 {
-       free (e->key);
        free (e);
 }
 
@@ -104,6 +98,8 @@ void llist_append (llist_t *l, llentry_t *e)
                l->tail->next = e;
 
        l->tail = e;
+
+       ++(l->size);
 }
 
 void llist_prepend (llist_t *l, llentry_t *e)
@@ -113,6 +109,8 @@ void llist_prepend (llist_t *l, llentry_t *e)
 
        if (l->tail == NULL)
                l->tail = e;
+
+       ++(l->size);
 }
 
 void llist_remove (llist_t *l, llentry_t *e)
@@ -129,6 +127,13 @@ void llist_remove (llist_t *l, llentry_t *e)
                l->head = e->next;
        if (l->tail == e)
                l->tail = prev;
+
+       --(l->size);
+}
+
+int llist_size (llist_t *l)
+{
+       return (l ? l->size : 0);
 }
 
 llentry_t *llist_search (llist_t *l, const char *key)
index 603fc87..c3753d8 100644 (file)
@@ -44,13 +44,15 @@ typedef struct llist_s llist_t;
 llist_t *llist_create (void);
 void llist_destroy (llist_t *l);
 
-llentry_t *llentry_create (const char *key, void *value);
+llentry_t *llentry_create (char *key, void *value);
 void llentry_destroy (llentry_t *e);
 
 void llist_append (llist_t *l, llentry_t *e);
 void llist_prepend (llist_t *l, llentry_t *e);
 void llist_remove (llist_t *l, llentry_t *e);
 
+int llist_size (llist_t *l);
+
 llentry_t *llist_search (llist_t *l, const char *key);
 
 llentry_t *llist_head (llist_t *l);
diff --git a/src/utils_threshold.c b/src/utils_threshold.c
new file mode 100644 (file)
index 0000000..778b40b
--- /dev/null
@@ -0,0 +1,748 @@
+/**
+ * collectd - src/utils_threshold.c
+ * 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
+ * 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_avltree.h"
+#include "utils_cache.h"
+
+#include <assert.h>
+#include <pthread.h>
+
+/*
+ * Private data structures
+ * {{{ */
+#define UT_FLAG_INVERT  0x01
+#define UT_FLAG_PERSIST 0x02
+
+typedef struct threshold_s
+{
+  char host[DATA_MAX_NAME_LEN];
+  char plugin[DATA_MAX_NAME_LEN];
+  char plugin_instance[DATA_MAX_NAME_LEN];
+  char type[DATA_MAX_NAME_LEN];
+  char type_instance[DATA_MAX_NAME_LEN];
+  gauge_t warning_min;
+  gauge_t warning_max;
+  gauge_t failure_min;
+  gauge_t failure_max;
+  int flags;
+} threshold_t;
+/* }}} */
+
+/*
+ * Private (static) variables
+ * {{{ */
+static c_avl_tree_t   *threshold_tree = NULL;
+static pthread_mutex_t threshold_lock = PTHREAD_MUTEX_INITIALIZER;
+/* }}} */
+
+/*
+ * Threshold management
+ * ====================
+ * The following functions add, delete, search, etc. configured thresholds to
+ * the underlying AVL trees.
+ * {{{ */
+static int ut_threshold_add (const threshold_t *th)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  char *name_copy;
+  threshold_t *th_copy;
+  int status = 0;
+
+  if (format_name (name, sizeof (name), th->host,
+       th->plugin, th->plugin_instance,
+       th->type, th->type_instance) != 0)
+  {
+    ERROR ("ut_threshold_add: format_name failed.");
+    return (-1);
+  }
+
+  name_copy = strdup (name);
+  if (name_copy == NULL)
+  {
+    ERROR ("ut_threshold_add: strdup failed.");
+    return (-1);
+  }
+
+  th_copy = (threshold_t *) malloc (sizeof (threshold_t));
+  if (th_copy == NULL)
+  {
+    sfree (name_copy);
+    ERROR ("ut_threshold_add: malloc failed.");
+    return (-1);
+  }
+  memcpy (th_copy, th, sizeof (threshold_t));
+
+  DEBUG ("ut_threshold_add: Adding entry `%s'", name);
+
+  pthread_mutex_lock (&threshold_lock);
+  status = c_avl_insert (threshold_tree, name_copy, th_copy);
+  pthread_mutex_unlock (&threshold_lock);
+
+  if (status != 0)
+  {
+    ERROR ("ut_threshold_add: c_avl_insert (%s) failed.", name);
+    sfree (name_copy);
+    sfree (th_copy);
+  }
+
+  return (status);
+} /* int ut_threshold_add */
+/*
+ * End of the threshold management functions
+ * }}} */
+
+/*
+ * Configuration
+ * =============
+ * The following approximately two hundred functions are used to handle the
+ * configuration and fill the threshold list.
+ * {{{ */
+static int ut_config_type_instance (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Instance' option needs exactly one "
+       "string argument.");
+    return (-1);
+  }
+
+  strncpy (th->type_instance, ci->values[0].value.string,
+      sizeof (th->type_instance));
+  th->type_instance[sizeof (th->type_instance) - 1] = '\0';
+
+  return (0);
+} /* int ut_config_type_instance */
+
+static int ut_config_type_max (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+       "number argument.", ci->key);
+    return (-1);
+  }
+
+  if (strcasecmp (ci->key, "WarningMax") == 0)
+    th->warning_max = ci->values[0].value.number;
+  else
+    th->failure_max = ci->values[0].value.number;
+
+  return (0);
+} /* int ut_config_type_max */
+
+static int ut_config_type_min (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+  {
+    WARNING ("threshold values: The `%s' option needs exactly one "
+       "number argument.", ci->key);
+    return (-1);
+  }
+
+  if (strcasecmp (ci->key, "WarningMin") == 0)
+    th->warning_min = ci->values[0].value.number;
+  else
+    th->failure_min = ci->values[0].value.number;
+
+  return (0);
+} /* int ut_config_type_min */
+
+static int ut_config_type_invert (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("threshold values: The `Invert' option needs exactly one "
+       "boolean argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    th->flags |= UT_FLAG_INVERT;
+  else
+    th->flags &= ~UT_FLAG_INVERT;
+
+  return (0);
+} /* int ut_config_type_invert */
+
+static int ut_config_type_persist (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_BOOLEAN))
+  {
+    WARNING ("threshold values: The `Persist' option needs exactly one "
+       "boolean argument.");
+    return (-1);
+  }
+
+  if (ci->values[0].value.boolean)
+    th->flags |= UT_FLAG_PERSIST;
+  else
+    th->flags &= ~UT_FLAG_PERSIST;
+
+  return (0);
+} /* int ut_config_type_persist */
+
+static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci)
+{
+  int i;
+  threshold_t th;
+  int status = 0;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Type' block needs exactly one string "
+       "argument.");
+    return (-1);
+  }
+
+  if (ci->children_num < 1)
+  {
+    WARNING ("threshold values: The `Type' block needs at least one option.");
+    return (-1);
+  }
+
+  memcpy (&th, th_orig, sizeof (th));
+  strncpy (th.type, ci->values[0].value.string, sizeof (th.type));
+  th.type[sizeof (th.type) - 1] = '\0';
+
+  th.warning_min = NAN;
+  th.warning_max = NAN;
+  th.failure_min = NAN;
+  th.failure_max = NAN;
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Instance", option->key) == 0)
+      status = ut_config_type_instance (&th, option);
+    else if ((strcasecmp ("WarningMax", option->key) == 0)
+       || (strcasecmp ("FailureMax", option->key) == 0))
+      status = ut_config_type_max (&th, option);
+    else if ((strcasecmp ("WarningMin", option->key) == 0)
+       || (strcasecmp ("FailureMin", option->key) == 0))
+      status = ut_config_type_min (&th, option);
+    else if (strcasecmp ("Invert", option->key) == 0)
+      status = ut_config_type_invert (&th, option);
+    else if (strcasecmp ("Persist", option->key) == 0)
+      status = ut_config_type_persist (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed inside a `Type' "
+         "block.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  if (status == 0)
+  {
+    status = ut_threshold_add (&th);
+  }
+
+  return (status);
+} /* int ut_config_type */
+
+static int ut_config_plugin_instance (threshold_t *th, oconfig_item_t *ci)
+{
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Instance' option needs exactly one "
+       "string argument.");
+    return (-1);
+  }
+
+  strncpy (th->plugin_instance, ci->values[0].value.string,
+      sizeof (th->plugin_instance));
+  th->plugin_instance[sizeof (th->plugin_instance) - 1] = '\0';
+
+  return (0);
+} /* int ut_config_plugin_instance */
+
+static int ut_config_plugin (const threshold_t *th_orig, oconfig_item_t *ci)
+{
+  int i;
+  threshold_t th;
+  int status = 0;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Plugin' block needs exactly one string "
+       "argument.");
+    return (-1);
+  }
+
+  if (ci->children_num < 1)
+  {
+    WARNING ("threshold values: The `Plugin' block needs at least one nested "
+       "block.");
+    return (-1);
+  }
+
+  memcpy (&th, th_orig, sizeof (th));
+  strncpy (th.plugin, ci->values[0].value.string, sizeof (th.plugin));
+  th.plugin[sizeof (th.plugin) - 1] = '\0';
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", option->key) == 0)
+      status = ut_config_type (&th, option);
+    else if (strcasecmp ("Instance", option->key) == 0)
+      status = ut_config_plugin_instance (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed inside a `Plugin' "
+         "block.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  return (status);
+} /* int ut_config_plugin */
+
+static int ut_config_host (const threshold_t *th_orig, oconfig_item_t *ci)
+{
+  int i;
+  threshold_t th;
+  int status = 0;
+
+  if ((ci->values_num != 1)
+      || (ci->values[0].type != OCONFIG_TYPE_STRING))
+  {
+    WARNING ("threshold values: The `Host' block needs exactly one string "
+       "argument.");
+    return (-1);
+  }
+
+  if (ci->children_num < 1)
+  {
+    WARNING ("threshold values: The `Host' block needs at least one nested "
+       "block.");
+    return (-1);
+  }
+
+  memcpy (&th, th_orig, sizeof (th));
+  strncpy (th.host, ci->values[0].value.string, sizeof (th.host));
+  th.host[sizeof (th.host) - 1] = '\0';
+
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", option->key) == 0)
+      status = ut_config_type (&th, option);
+    else if (strcasecmp ("Plugin", option->key) == 0)
+      status = ut_config_plugin (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed inside a `Host' "
+         "block.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  return (status);
+} /* int ut_config_host */
+
+int ut_config (const oconfig_item_t *ci)
+{
+  int i;
+  int status = 0;
+
+  threshold_t th;
+
+  if (ci->values_num != 0)
+  {
+    ERROR ("threshold values: The `Threshold' block may not have any "
+       "arguments.");
+    return (-1);
+  }
+
+  if (threshold_tree == NULL)
+  {
+    threshold_tree = c_avl_create ((void *) strcmp);
+    if (threshold_tree == NULL)
+    {
+      ERROR ("ut_config: c_avl_create failed.");
+      return (-1);
+    }
+  }
+
+  memset (&th, '\0', sizeof (th));
+  th.warning_min = NAN;
+  th.warning_max = NAN;
+  th.failure_min = NAN;
+  th.failure_max = NAN;
+    
+  for (i = 0; i < ci->children_num; i++)
+  {
+    oconfig_item_t *option = ci->children + i;
+    status = 0;
+
+    if (strcasecmp ("Type", option->key) == 0)
+      status = ut_config_type (&th, option);
+    else if (strcasecmp ("Plugin", option->key) == 0)
+      status = ut_config_plugin (&th, option);
+    else if (strcasecmp ("Host", option->key) == 0)
+      status = ut_config_host (&th, option);
+    else
+    {
+      WARNING ("threshold values: Option `%s' not allowed here.", option->key);
+      status = -1;
+    }
+
+    if (status != 0)
+      break;
+  }
+
+  return (status);
+} /* int um_config */
+/*
+ * End of the functions used to configure threshold values.
+ */
+/* }}} */
+
+static threshold_t *threshold_get (const char *hostname,
+    const char *plugin, const char *plugin_instance,
+    const char *type, const char *type_instance)
+{
+  char name[6 * DATA_MAX_NAME_LEN];
+  threshold_t *th = NULL;
+
+  format_name (name, sizeof (name),
+      (hostname == NULL) ? "" : hostname,
+      (plugin == NULL) ? "" : plugin, plugin_instance,
+      (type == NULL) ? "" : type, type_instance);
+  name[sizeof (name) - 1] = '\0';
+
+  if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
+    return (th);
+  else
+    return (NULL);
+} /* threshold_t *threshold_get */
+
+static threshold_t *threshold_search (const data_set_t *ds,
+    const value_list_t *vl)
+{
+  threshold_t *th;
+
+  if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
+         ds->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, vl->plugin, vl->plugin_instance,
+         ds->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, vl->plugin, NULL,
+         ds->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, vl->plugin, NULL,
+         ds->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, "", NULL,
+         ds->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get (vl->host, "", NULL,
+         ds->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
+         ds->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, vl->plugin_instance,
+         ds->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, NULL,
+         ds->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", vl->plugin, NULL,
+         ds->type, NULL)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", "", NULL,
+         ds->type, vl->type_instance)) != NULL)
+    return (th);
+  else if ((th = threshold_get ("", "", NULL,
+         ds->type, NULL)) != NULL)
+    return (th);
+
+  return (NULL);
+} /* threshold_t *threshold_search */
+
+int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
+{
+  notification_t n;
+  threshold_t *th;
+  gauge_t *values;
+  int i;
+
+  int state_orig;
+  int state_new = STATE_OKAY;
+  int ds_index = 0;
+
+  char *buf;
+  size_t bufsize;
+  int status;
+
+  if (threshold_tree == NULL)
+    return (0);
+
+  /* Is this lock really necessary? So far, thresholds are only inserted at
+   * startup. -octo */
+  pthread_mutex_lock (&threshold_lock);
+  th = threshold_search (ds, vl);
+  pthread_mutex_unlock (&threshold_lock);
+  if (th == NULL)
+    return (0);
+
+  DEBUG ("ut_check_threshold: Found matching threshold");
+
+  values = uc_get_rate (ds, vl);
+  if (values == NULL)
+    return (0);
+
+  state_orig = uc_get_state (ds, vl);
+
+  for (i = 0; i < ds->ds_num; i++)
+  {
+    int is_inverted = 0;
+    int is_warning = 0;
+    int is_failure = 0;
+
+    if ((th->flags & UT_FLAG_INVERT) != 0)
+    {
+      is_inverted = 1;
+      is_warning--;
+      is_failure--;
+    }
+    if ((!isnan (th->failure_min) && (th->failure_min > values[i]))
+       || (!isnan (th->failure_max) && (th->failure_max < values[i])))
+      is_failure++;
+    if ((!isnan (th->warning_min) && (th->warning_min > values[i]))
+       || (!isnan (th->warning_max) && (th->warning_max < values[i])))
+      is_warning++;
+
+    if ((is_failure != 0) && (state_new != STATE_ERROR))
+    {
+      state_new = STATE_ERROR;
+      ds_index = i;
+    }
+    else if ((is_warning != 0)
+       && (state_new != STATE_ERROR)
+       && (state_new != STATE_WARNING))
+    {
+      state_new = STATE_WARNING;
+      ds_index = i;
+    }
+  }
+
+  if (state_new != state_orig)
+    uc_set_state (ds, vl, state_new);
+
+  /* Return here if we're not going to send a notification */
+  if ((state_new == state_orig)
+      && ((state_new == STATE_OKAY)
+       || ((th->flags & UT_FLAG_PERSIST) == 0)))
+  {
+    sfree (values);
+    return (0);
+  }
+
+  NOTIFICATION_INIT_VL (&n, vl, ds);
+  {
+    /* Copy the associative members */
+    if (state_new == STATE_OKAY)
+      n.severity = NOTIF_OKAY;
+    else if (state_new == STATE_WARNING)
+      n.severity = NOTIF_WARNING;
+    else
+      n.severity = NOTIF_FAILURE;
+
+    n.time = vl->time;
+
+    buf = n.message;
+    bufsize = sizeof (n.message);
+
+    status = snprintf (buf, bufsize, "Host %s, plugin %s",
+       vl->host, vl->plugin);
+    buf += status;
+    bufsize -= status;
+
+    if (vl->plugin_instance[0] != '\0')
+    {
+      status = snprintf (buf, bufsize, " (instance %s)",
+         vl->plugin_instance);
+      buf += status;
+      bufsize -= status;
+    }
+
+    status = snprintf (buf, bufsize, " type %s", ds->type);
+    buf += status;
+    bufsize -= status;
+
+    if (vl->type_instance[0] != '\0')
+    {
+      status = snprintf (buf, bufsize, " (instance %s)",
+         vl->type_instance);
+      buf += status;
+      bufsize -= status;
+    }
+  }
+
+  /* Send a okay notification */
+  if (state_new == STATE_OKAY)
+  {
+    status = snprintf (buf, bufsize, ": All data sources are within range again.");
+    buf += status;
+    bufsize -= status;
+  }
+  else
+  {
+    double min;
+    double max;
+
+    min = (state_new == STATE_ERROR) ? th->failure_min : th->warning_min;
+    max = (state_new == STATE_ERROR) ? th->failure_max : th->warning_max;
+
+    if (th->flags & UT_FLAG_INVERT)
+    {
+      if (!isnan (min) && !isnan (max))
+      {
+       status = snprintf (buf, bufsize, ": Data source \"%s\" is currently "
+           "%f. That is within the %s region of %f and %f.",
+           ds->ds[ds_index].name, values[ds_index],
+           (state_new == STATE_ERROR) ? "failure" : "warning",
+           min, min);
+      }
+      else
+      {
+       status = snprintf (buf, bufsize, ": Data source \"%s\" is currently "
+           "%f. That is %s the %s threshold of %f.",
+           ds->ds[ds_index].name, values[ds_index],
+           isnan (min) ? "below" : "above",
+           (state_new == STATE_ERROR) ? "failure" : "warning",
+           isnan (min) ? max : min);
+      }
+    }
+    else /* is not inverted */
+    {
+      status = snprintf (buf, bufsize, ": Data source \"%s\" is currently "
+         "%f. That is %s the %s threshold of %f.",
+         ds->ds[ds_index].name, values[ds_index],
+         (values[ds_index] < min) ? "below" : "above",
+         (state_new == STATE_ERROR) ? "failure" : "warning",
+         (values[ds_index] < min) ? min : max);
+    }
+    buf += status;
+    bufsize -= status;
+  }
+
+  plugin_dispatch_notification (&n);
+
+  sfree (values);
+
+  return (0);
+} /* int ut_check_threshold */
+
+int ut_check_interesting (const char *name)
+{
+  char *name_copy = NULL;
+  char *host = NULL;
+  char *plugin = NULL;
+  char *plugin_instance = NULL;
+  char *type = NULL;
+  char *type_instance = NULL;
+  int status;
+  data_set_t ds;
+  value_list_t vl;
+  threshold_t *th;
+
+  /* If there is no tree nothing is interesting. */
+  if (threshold_tree == NULL)
+    return (0);
+
+  name_copy = strdup (name);
+  if (name_copy == NULL)
+  {
+    ERROR ("ut_check_interesting: strdup failed.");
+    return (-1);
+  }
+
+  status = parse_identifier (name_copy, &host,
+      &plugin, &plugin_instance, &type, &type_instance);
+  if (status != 0)
+  {
+    ERROR ("ut_check_interesting: parse_identifier failed.");
+    return (-1);
+  }
+
+  memset (&ds, '\0', sizeof (ds));
+  memset (&vl, '\0', sizeof (vl));
+
+  strncpy (vl.host, host, sizeof (vl.host));
+  vl.host[sizeof (vl.host) - 1] = '\0';
+  strncpy (vl.plugin, plugin, sizeof (vl.plugin));
+  vl.plugin[sizeof (vl.plugin) - 1] = '\0';
+  if (plugin_instance != NULL)
+  {
+    strncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+    vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
+  }
+  strncpy (ds.type, type, sizeof (ds.type));
+  ds.type[sizeof (ds.type) - 1] = '\0';
+  if (type_instance != NULL)
+  {
+    strncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+    vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+  }
+
+  sfree (name_copy);
+  host = plugin = plugin_instance = type = type_instance = NULL;
+
+  th = threshold_search (&ds, &vl);
+  if (th == NULL)
+    return (0);
+  if ((th->flags & UT_FLAG_PERSIST) == 0)
+    return (1);
+  return (2);
+} /* int ut_check_interesting */
+
+/* vim: set sw=2 ts=8 sts=2 tw=78 fdm=marker : */
diff --git a/src/utils_threshold.h b/src/utils_threshold.h
new file mode 100644 (file)
index 0000000..a42c412
--- /dev/null
@@ -0,0 +1,57 @@
+/**
+ * collectd - src/utils_threshold.h
+ * 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
+ * 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_THRESHOLD_H
+#define UTILS_THRESHOLD_H 1
+
+#include "collectd.h"
+#include "liboconfig/oconfig.h"
+#include "plugin.h"
+
+/*
+ * ut_config
+ *
+ * Parses the configuration and sets up the module. This is called from
+ * `src/configfile.c'.
+ */
+int ut_config (const oconfig_item_t *ci);
+
+/*
+ * ut_check_threshold
+ *
+ * Checks if a threshold is defined for this value and if such a threshold is
+ * configured, check if the value within the acceptable range. If it is not, a
+ * notification is dispatched to inform the user that a problem exists. This is
+ * called from `plugin_read_all'.
+ */
+int ut_check_threshold (const data_set_t *ds, const value_list_t *vl);
+
+/*
+ * Given an identification returns
+ * 0: No threshold is defined.
+ * 1: A threshold has been found. The flag `persist' is off.
+ * 2: A threshold has been found. The flag `persist' is on.
+ *    (That is, it is expected that many notifications are sent until the
+ *    problem disappears.)
+ */
+int ut_check_interesting (const char *name);
+
+#endif /* UTILS_THRESHOLD_H */
diff --git a/src/uuid.c b/src/uuid.c
new file mode 100644 (file)
index 0000000..d54301a
--- /dev/null
@@ -0,0 +1,289 @@
+/**
+ * collectd - src/uuid.c
+ * Copyright (C) 2007  Red Hat Inc.
+ *
+ * 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:
+ *   Dan Berrange <berrange@redhat.com>
+ *   Richard W.M. Jones <rjones@redhat.com>
+ *
+ * Derived from UUID detection code by Dan Berrange <berrange@redhat.com>
+ * http://hg.et.redhat.com/virt/daemons/spectre--devel?f=f6e3a1b06433;file=lib/uuid.c
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "configfile.h"
+#include "plugin.h"
+
+#if HAVE_LIBHAL
+#include <libhal.h>
+#endif
+
+#define UUID_RAW_LENGTH 16
+#define UUID_PRINTABLE_COMPACT_LENGTH  (UUID_RAW_LENGTH * 2)
+#define UUID_PRINTABLE_NORMAL_LENGTH  (UUID_PRINTABLE_COMPACT_LENGTH + 4)
+
+#define HANDLE_PREFIX "Handle"
+#define SYSINFO_PREFIX "System Information"
+#define ALT_SYSINFO_PREFIX "\tSystem Information"
+#define UUID_PREFIX "\tUUID:"
+#define ALT_UUID_PREFIX "\t\tUUID:"
+
+static int
+looks_like_a_uuid (const char *uuid)
+{
+    int len;
+
+    if (!uuid) return 0;
+
+    len = strlen (uuid);
+
+    if (len < UUID_PRINTABLE_COMPACT_LENGTH)
+        return 0;
+
+    while (*uuid) {
+        if (!isxdigit (*uuid) && *uuid != '-') return 0;
+        uuid++;
+    }
+    return 1;
+}
+
+static char *
+uuid_parse_dmidecode(FILE *file)
+{
+    char line[1024];
+    int inSysInfo = 0;
+
+    for (;;) {
+        if (!fgets(line, sizeof(line)/sizeof(char), file)) {
+            return NULL;
+        }
+        if (strncmp(line, HANDLE_PREFIX,
+                    (sizeof(HANDLE_PREFIX)/sizeof(char))-1) == 0) {
+            /*printf("Got handle %s\n", line);*/
+            inSysInfo = 0;
+        } else if (strncmp(line, SYSINFO_PREFIX,
+                           (sizeof(SYSINFO_PREFIX)/sizeof(char))-1) == 0) {
+            /*printf("Got system info %s\n", line);*/
+            inSysInfo = 1;
+        } else if (strncmp(line, ALT_SYSINFO_PREFIX,
+                           (sizeof(ALT_SYSINFO_PREFIX)/sizeof(char))-1) == 0) {
+            /*printf("Got alt system info %s\n", line);*/
+            inSysInfo = 1;
+        }
+        
+        if (inSysInfo) {
+            if (strncmp(line, UUID_PREFIX,
+                        (sizeof(UUID_PREFIX)/sizeof(char))-1) == 0) {
+                char *uuid = line + (sizeof(UUID_PREFIX)/sizeof(char));
+                /*printf("Got uuid [%s]\n", uuid);*/
+                if (looks_like_a_uuid (uuid))
+                    return strdup (uuid);
+            }
+            if (strncmp(line, ALT_UUID_PREFIX,
+                        (sizeof(ALT_UUID_PREFIX)/sizeof(char))-1) == 0) {
+                char *uuid = line + (sizeof(ALT_UUID_PREFIX)/sizeof(char));
+                /*printf("Got alt uuid [%s]\n", uuid);*/
+                if (looks_like_a_uuid (uuid))
+                    return strdup (uuid);
+            }
+        }
+    }
+    return NULL;
+}
+
+static char *
+uuid_get_from_dmidecode(void)
+{
+    FILE *dmidecode = popen("dmidecode 2>/dev/null", "r");
+    char *uuid;
+
+    if (!dmidecode) {
+        return NULL;
+    }
+    
+    uuid = uuid_parse_dmidecode(dmidecode);
+
+    pclose(dmidecode);
+    return uuid;
+}
+
+#if HAVE_LIBHAL
+
+#define UUID_PATH "/org/freedesktop/Hal/devices/computer"
+#define UUID_PROPERTY "smbios.system.uuid"
+
+static char *
+uuid_get_from_hal(void)
+{
+    LibHalContext *ctx;
+
+    DBusError error;
+    DBusConnection *con;
+
+    dbus_error_init(&error);
+
+    if (!(con = dbus_bus_get(DBUS_BUS_SYSTEM, &error)) ) {
+        goto bailout_nobus;
+    }
+
+    ctx = libhal_ctx_new();
+    libhal_ctx_set_dbus_connection(ctx, con);
+
+    if (!libhal_ctx_init(ctx, &error)) {
+        goto bailout;
+    }
+
+    if (!libhal_device_property_exists(ctx,
+                                       UUID_PATH,
+                                       UUID_PROPERTY,
+                                       &error)) {
+        goto bailout;
+    }
+
+    char *uuid  = libhal_device_get_property_string(ctx,
+                                                    UUID_PATH,
+                                                    UUID_PROPERTY,
+                                                    &error);
+    if (looks_like_a_uuid (uuid)) {
+        return uuid;
+    }
+
+ bailout:
+    {
+        DBusError ctxerror;
+        dbus_error_init(&ctxerror);
+        if (!(libhal_ctx_shutdown(ctx, &ctxerror))) {
+            dbus_error_free(&ctxerror);
+        }
+    }
+
+    libhal_ctx_free(ctx);
+    //dbus_connection_unref(con);
+
+ bailout_nobus:
+    if (dbus_error_is_set(&error)) {
+        /*printf("Error %s\n", error.name);*/
+        dbus_error_free(&error);
+    }
+    return NULL;
+}
+#endif
+
+static char *
+uuid_get_from_file(const char *path)
+{
+    FILE *file;
+    char uuid[UUID_PRINTABLE_NORMAL_LENGTH+1];
+
+    if (!(file = fopen(path, "r"))) {
+        return NULL;
+    }
+
+    if (!fgets(uuid, sizeof(uuid), file)) {
+        fclose(file);
+        return NULL;
+    }
+    fclose(file);
+
+    return strdup (uuid);
+}
+
+static char *uuidfile = NULL;
+
+static char *
+uuid_get_local(void)
+{
+    char *uuid;
+
+    /* Check /etc/uuid / UUIDFile before any other method. */
+    if ((uuid = uuid_get_from_file(uuidfile ? uuidfile : "/etc/uuid")) != NULL){
+        return uuid;
+    }
+
+#if HAVE_LIBHAL
+    if ((uuid = uuid_get_from_hal()) != NULL) {
+        return uuid;
+    }
+#endif
+
+    if ((uuid = uuid_get_from_dmidecode()) != NULL) {
+        return uuid;
+    }
+
+    if ((uuid = uuid_get_from_file("/sys/hypervisor/uuid")) != NULL) {
+        return uuid;
+    }
+
+    return NULL;
+}
+
+static const char *config_keys[] = {
+    "UUIDFile",
+    NULL
+};
+#define NR_CONFIG_KEYS ((sizeof config_keys / sizeof config_keys[0]) - 1)
+
+static int
+uuid_config (const char *key, const char *value)
+{
+    if (strcasecmp (key, "UUIDFile") == 0) {
+        if (uuidfile) {
+            ERROR ("UUIDFile given twice in configuration file");
+            return 1;
+        }
+        uuidfile = strdup (value);
+        return 0;
+    }
+    return 0;
+}
+
+static int
+uuid_init (void)
+{
+    char *uuid = uuid_get_local ();
+
+    if (uuid) {
+        strncpy (hostname_g, uuid, DATA_MAX_NAME_LEN);
+        hostname_g[DATA_MAX_NAME_LEN-1] = '\0';
+        sfree (uuid);
+        return 0;
+    }
+
+    WARNING ("uuid: could not read UUID using any known method");
+    return 0;
+}
+
+void module_register (void)
+{
+       plugin_register_config ("uuid", uuid_config,
+                            config_keys, NR_CONFIG_KEYS);
+       plugin_register_init ("uuid", uuid_init);
+}
+
+/*
+ * vim: set tabstop=4:
+ * vim: set shiftwidth=4:
+ * vim: set expandtab:
+ */
+/*
+ * Local variables:
+ *  indent-tabs-mode: nil
+ *  c-indent-level: 4
+ *  c-basic-offset: 4
+ *  tab-width: 4
+ * End:
+ */
index e2edec0..9f006e8 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-DEFAULT_VERSION="4.2.7.git"
+DEFAULT_VERSION="4.3.2.git"
 
 VERSION="$( git describe 2> /dev/null | sed -e 's/^collectd-//' )"