+yyyy-mm-dd, Version 4.2.0
+ * collectd: The new config option `Include' lets you include other
+ configfiles and thus split up your config into smaller parts. This
+ may be especially interresting for the snmp plugin to keep the data
+ definitions seperate from the host definitions.
+ * perl plugin: Many changes, including the added `EnableDebugger'
+ config option which lets you debug your Perl plugins more easily.
+ * snmp plugin: Added the options `Scale' and `Shift' to Data-blocks to
+ correct the values returned by SNMP-agents.
+ * ipvs plugin: The new `ipvs' plugin collects IPVS connection statistics
+ (number of connections, octets and packets for each service and
+ destination).
+ * rrdtool plugin: Use the threadsafe RRD-library if available. Try to
+ be more threadsafe otherwise by locking calls to the library.
+
+yyyy-mm-dd, Version 4.1.3
* rrdtool plugin: Use the threadsafe RRD-library if available. Try to
be more threadsafe otherwise by locking calls to the library.
-SUBDIRS = libltdl src
+SUBDIRS = libltdl src bindings
INCLUDES = $(LTDLINCL)
-EXTRA_DIST = contrib
+EXTRA_DIST = contrib version-gen.sh
dist-hook:
find $(distdir) -type d -name '.svn' | xargs rm -rf
Iptables' counters: Number of bytes that were matched by a certain
iptables rule.
+ - ipvs
+ IPVS connection statistics (number of connections, octets and packets
+ for each service and destination).
+ See http://www.linuxvirtualserver.org/software/index.html.
+
- irq
IRQ counters: Frequency in which certain interrupts occur.
- tape
Bytes and operations read and written on tape devices. Solaris only.
+ - tcpconns
+ Number of TCP connections to specific local and remote ports.
+
- users
Users currently logged in.
--- /dev/null
+EXTRA_DIST = perl/Collectd.pm perl/Makefile.PL perl/Collectd/Makefile.PL perl/Collectd/Unixsock.pm
+
+all-local: @PERL_BINDINGS@
+
+install-exec-local:
+ [ ! -f perl/Makefile ] || $(MAKE) -C perl install
+
+clean-local:
+ [ ! -f perl/Makefile ] || $(MAKE) -C perl realclean
+
+perl: perl/Makefile
+ $(MAKE) -C perl
+
+perl/Makefile: perl/Makefile.PL perl/Collectd/Makefile.PL
+ cd perl && @PERL@ Makefile.PL PREFIX=$(prefix) @PERL_BINDINGS_OPTIONS@
+
+.PHONY: perl
+
--- /dev/null
+# collectd - Collectd.pm
+# 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>
+
+package Collectd;
+
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw( Exporter );
+
+our %EXPORT_TAGS = (
+ 'plugin' => [ qw(
+ plugin_register
+ plugin_unregister
+ plugin_dispatch_values
+ plugin_log
+ ) ],
+ 'types' => [ qw(
+ TYPE_INIT
+ TYPE_READ
+ TYPE_WRITE
+ TYPE_SHUTDOWN
+ TYPE_LOG
+ TYPE_DATASET
+ ) ],
+ 'ds_types' => [ qw(
+ DS_TYPE_COUNTER
+ DS_TYPE_GAUGE
+ ) ],
+ 'log' => [ qw(
+ ERROR
+ WARNING
+ NOTICE
+ INFO
+ DEBUG
+ LOG_ERR
+ LOG_WARNING
+ LOG_NOTICE
+ LOG_INFO
+ LOG_DEBUG
+ ) ],
+);
+
+{
+ my %seen;
+ push @{$EXPORT_TAGS{'all'}}, grep {! $seen{$_}++ } @{$EXPORT_TAGS{$_}}
+ foreach keys %EXPORT_TAGS;
+}
+
+Exporter::export_ok_tags ('all');
+
+my @plugins = ();
+my @datasets = ();
+
+my %types = (
+ TYPE_INIT, "init",
+ TYPE_READ, "read",
+ TYPE_WRITE, "write",
+ TYPE_SHUTDOWN, "shutdown",
+ TYPE_LOG, "log"
+);
+
+foreach my $type (keys %types) {
+ $plugins[$type] = {};
+}
+
+sub _log {
+ my $caller = shift;
+ my $lvl = shift;
+ my $msg = shift;
+
+ if ("Collectd" eq $caller) {
+ $msg = "perl: $msg";
+ }
+ return plugin_log ($lvl, $msg);
+}
+
+sub ERROR { _log (scalar caller, LOG_ERR, shift); }
+sub WARNING { _log (scalar caller, LOG_WARNING, shift); }
+sub NOTICE { _log (scalar caller, LOG_NOTICE, shift); }
+sub INFO { _log (scalar caller, LOG_INFO, shift); }
+sub DEBUG { _log (scalar caller, LOG_DEBUG, shift); }
+
+sub plugin_call_all {
+ my $type = shift;
+
+ if (! defined $type) {
+ return;
+ }
+
+ if (TYPE_LOG != $type) {
+ DEBUG ("Collectd::plugin_call: type = \"$type\", args=\"@_\"");
+ }
+
+ if (! defined $plugins[$type]) {
+ ERROR ("Collectd::plugin_call: unknown type \"$type\"");
+ return;
+ }
+
+ foreach my $plugin (keys %{$plugins[$type]}) {
+ my $p = $plugins[$type]->{$plugin};
+
+ if ($p->{'wait_left'} > 0) {
+ # TODO: use interval_g
+ $p->{'wait_left'} -= 10;
+ }
+
+ next if ($p->{'wait_left'} > 0);
+
+ if (my $status = $p->{'code'}->(@_)) {
+ $p->{'wait_left'} = 0;
+ $p->{'wait_time'} = 10;
+ }
+ elsif (TYPE_READ == $type) {
+ $p->{'wait_left'} = $p->{'wait_time'};
+ $p->{'wait_time'} *= 2;
+
+ if ($p->{'wait_time'} > 86400) {
+ $p->{'wait_time'} = 86400;
+ }
+
+ WARNING ("${plugin}->read() failed with status $status. "
+ . "Will suspend it for $p->{'wait_left'} seconds.");
+ }
+ elsif (TYPE_INIT == $type) {
+ 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.");
+ }
+ }
+ return 1;
+}
+
+# Collectd::plugin_register (type, name, data).
+#
+# type:
+# init, read, write, shutdown, data set
+#
+# name:
+# name of the plugin
+#
+# data:
+# reference to the plugin's subroutine that does the work or the data set
+# definition
+sub plugin_register {
+ my $type = shift;
+ my $name = shift;
+ my $data = shift;
+
+ DEBUG ("Collectd::plugin_register: "
+ . "type = \"$type\", name = \"$name\", data = \"$data\"");
+
+ if (! ((defined $type) && (defined $name) && (defined $data))) {
+ ERROR ("Usage: Collectd::plugin_register (type, name, data)");
+ return;
+ }
+
+ if ((! defined $plugins[$type]) && (TYPE_DATASET != $type)) {
+ ERROR ("Collectd::plugin_register: Invalid type \"$type\"");
+ return;
+ }
+
+ 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,
+ };
+ }
+ else {
+ ERROR ("Collectd::plugin_register: Invalid data.");
+ return;
+ }
+ return 1;
+}
+
+sub plugin_unregister {
+ my $type = shift;
+ my $name = shift;
+
+ DEBUG ("Collectd::plugin_unregister: type = \"$type\", name = \"$name\"");
+
+ if (! ((defined $type) && (defined $name))) {
+ ERROR ("Usage: Collectd::plugin_unregister (type, name)");
+ return;
+ }
+
+ if (TYPE_DATASET == $type) {
+ return plugin_unregister_data_set ($name);
+ }
+ elsif (defined $plugins[$type]) {
+ delete $plugins[$type]->{$name};
+ }
+ else {
+ ERROR ("Collectd::plugin_unregister: Invalid type.");
+ return;
+ }
+}
+
+1;
+
+# vim: set sw=4 ts=4 tw=78 noexpandtab :
+
--- /dev/null
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ 'NAME' => 'Collectd::Unixsock',
+ 'AUTHOR' => 'Florian Forster <octo@verplant.org>',
+);
+
+# vim: set sw=4 ts=4 tw=78 noexpandtab :
--- /dev/null
+package Collectd::Unixsock;
+
+=head1 NAME
+
+Collectd::Unixsock - Abstraction layer for accessing the functionality by collectd's unixsock plugin.
+
+=head1 SYNOPSIS
+
+ use Collectd::Unixsock ();
+
+ my $sock = Collectd::Unixsock->new ($path);
+
+ my $value = $sock->getval (%identifier);
+ $sock->putval (%identifier,
+ time => time (),
+ values => [123, 234, 345]);
+
+ $sock->destroy ();
+
+=head1 DESCRIPTION
+
+collectd's unixsock plugin allows external programs to access the values it has
+collected or received and to submit own values. This Perl-module is simply a
+little abstraction layer over this interface to make it even easier for
+programmers to interact with the daemon.
+
+=cut
+
+use strict;
+use warnings;
+
+use Carp (qw(cluck confess));
+use IO::Socket::UNIX;
+use Regexp::Common (qw(number));
+
+return (1);
+
+sub _create_socket
+{
+ my $path = shift;
+ my $sock = IO::Socket::UNIX->new (Type => SOCK_STREAM, Peer => $path);
+ if (!$sock)
+ {
+ cluck ("Cannot open UNIX-socket $path: $!");
+ return;
+ }
+ return ($sock);
+} # _create_socket
+
+=head1 VALUE IDENTIFIER
+
+The values in the collectd are identified using an five-tupel (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
+B<type>, possibly completed by B<plugin_instance> and B<type_instance>.
+
+Usually you can pass this hash as follows:
+
+ $obj->method (host => $host, plugin => $plugin, type => $type, %other_args);
+
+=cut
+
+sub _create_identifier
+{
+ my $args = shift;
+ my $host;
+ my $plugin;
+ my $type;
+
+ if (!$args->{'host'} || !$args->{'plugin'} || !$args->{'type'})
+ {
+ cluck ("Need `host', `plugin' and `type'");
+ return;
+ }
+
+ $host = $args->{'host'};
+ $plugin = $args->{'plugin'};
+ $plugin .= '-' . $args->{'plugin_instance'} if (defined ($args->{'plugin_instance'}));
+ $type = $args->{'type'};
+ $type .= '-' . $args->{'type_instance'} if (defined ($args->{'type_instance'}));
+
+ return ("$host/$plugin/$type");
+} # _create_identifier
+
+sub _parse_identifier
+{
+ my $string = shift;
+ my $host;
+ my $plugin;
+ my $plugin_instance;
+ my $type;
+ my $type_instance;
+ my $ident;
+
+ ($host, $plugin, $type) = split ('/', $string);
+
+ ($plugin, $plugin_instance) = split ('-', $plugin, 2);
+ ($type, $type_instance) = split ('-', $type, 2);
+
+ $ident =
+ {
+ host => $host,
+ plugin => $plugin,
+ type => $type
+ };
+ $ident->{'plugin_instance'} = $plugin_instance if (defined ($plugin_instance));
+ $ident->{'type_instance'} = $type_instance if (defined ($type_instance));
+
+ return ($ident);
+} # _parse_identifier
+
+=head1 PUBLIC METHODS
+
+=over 4
+
+=item I<$obj> = Collectd::Unixsock->B<new> ([I<$path>]);
+
+Creates a new connection to the daemon. The optional I<$path> argument gives
+the path to the UNIX socket of the C<unixsock plugin> and defaults to
+F</var/run/collectd-unixsock>. Returns the newly created object on success and
+false on error.
+
+=cut
+
+sub new
+{
+ my $pkg = shift;
+ my $path = @_ ? shift : '/var/run/collectd-unixsock';
+ my $sock = _create_socket ($path) or return;
+ my $obj = bless (
+ {
+ path => $path,
+ sock => $sock,
+ error => 'No error'
+ }, $pkg);
+ return ($obj);
+} # new
+
+=item I<$res> = I<$obj>-E<gt>B<getval> (I<%identifier>);
+
+Requests a value-list from the daemon. On success a hash-ref is returned with
+the name of each data-source as the key and the according value as, well, the
+value. On error false is returned.
+
+=cut
+
+sub getval
+{
+ my $obj = shift;
+ my %args = @_;
+
+ my $status;
+ my $fh = $obj->{'sock'} or confess;
+ my $msg;
+ my $identifier;
+
+ my $ret = {};
+
+ $identifier = _create_identifier (\%args) or return;
+
+ $msg = "GETVAL $identifier\n";
+ #print "-> $msg";
+ send ($fh, $msg, 0) or confess ("send: $!");
+
+ $msg = undef;
+ recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
+ #print "<- $msg";
+
+ ($status, $msg) = split (' ', $msg, 2);
+ if ($status <= 0)
+ {
+ $obj->{'error'} = $msg;
+ return;
+ }
+
+ for (split (' ', $msg))
+ {
+ my $entry = $_;
+ if ($entry =~ m/^(\w+)=NaN$/)
+ {
+ $ret->{$1} = undef;
+ }
+ elsif ($entry =~ m/^(\w+)=($RE{num}{real})$/)
+ {
+ $ret->{$1} = 0.0 + $2;
+ }
+ }
+
+ return ($ret);
+} # getval
+
+=item I<$obj>-E<gt>B<putval> (I<%identifier>, B<time> => I<$time>, B<values> => [...]);
+
+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
+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
+(e.E<nbsp>g. system-load) may be C<undef>. Returns true upon success and false
+otherwise.
+
+=cut
+
+sub putval
+{
+ my $obj = shift;
+ my %args = @_;
+
+ my $status;
+ my $fh = $obj->{'sock'} or confess;
+ my $msg;
+ my $identifier;
+ my $values;
+
+ $identifier = _create_identifier (\%args) or return;
+ if (!$args{'values'})
+ {
+ cluck ("Need argument `values'");
+ return;
+ }
+
+ if (!ref ($args{'values'}))
+ {
+ $values = $args{'values'};
+ }
+ else
+ {
+ my $time = $args{'time'} ? $args{'time'} : time ();
+ $values = join (':', $time, map { defined ($_) ? $_ : 'U' } (@{$args{'values'}}));
+ }
+
+ $msg = "PUTVAL $identifier $values\n";
+ #print "-> $msg";
+ send ($fh, $msg, 0) or confess ("send: $!");
+ $msg = undef;
+ recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
+ #print "<- $msg";
+
+ ($status, $msg) = split (' ', $msg, 2);
+ return (1) if ($status == 0);
+
+ $obj->{'error'} = $msg;
+ return;
+} # putval
+
+=item I<$res> = I<$obj>-E<gt>B<listval> ()
+
+Queries a list of values from the daemon. The list is returned as an array of
+hash references, where each hash reference is a valid identifier. The C<time>
+member of each hash holds the epoch value of the last update of that value.
+
+=cut
+
+sub listval
+{
+ my $obj = shift;
+ my $msg;
+ my @ret = ();
+ my $status;
+ my $fh = $obj->{'sock'} or confess;
+
+ $msg = "LISTVAL\n";
+ send ($fh, $msg, 0) or confess ("send: $!");
+
+ $msg = <$fh>;
+ ($status, $msg) = split (' ', $msg, 2);
+ if ($status < 0)
+ {
+ $obj->{'error'} = $msg;
+ return;
+ }
+
+ for (my $i = 0; $i < $status; $i++)
+ {
+ my $time;
+ my $ident;
+
+ $msg = <$fh>;
+ chomp ($msg);
+
+ ($time, $ident) = split (' ', $msg, 2);
+
+ $ident = _parse_identifier ($ident);
+ $ident->{'time'} = int ($time);
+
+ push (@ret, $ident);
+ } # for (i = 0 .. $status)
+
+ return (@ret);
+} # listval
+
+=item I<$obj>-E<gt>destroy ();
+
+Closes the socket before the object is destroyed. This function is also
+automatically called then the object goes out of scope.
+
+=back
+
+=cut
+
+sub destroy
+{
+ my $obj = shift;
+ if ($obj->{'sock'})
+ {
+ close ($obj->{'sock'});
+ delete ($obj->{'sock'});
+ }
+}
+
+sub DESTROY
+{
+ my $obj = shift;
+ $obj->destroy ();
+}
+
+=head1 AUTHOR
+
+Florian octo Forster E<lt>octo@verplant.orgE<gt>
+
+=cut
--- /dev/null
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+ 'NAME' => 'Collectd',
+ 'AUTHOR' => 'Sebastian Harl <sh@tokkee.org>',
+);
+
+# vim: set sw=4 ts=4 tw=78 noexpandtab :
#endif
])
+# For ipvs module
+have_net_ip_vs_h="no"
+have_ip_vs_h="no"
+if test "x$ac_system" = "xLinux"
+then
+ AC_CHECK_HEADERS(net/ip_vs.h, [have_net_ip_vs_h="yes"])
+ AC_CHECK_HEADERS(ip_vs.h, [have_ip_vs_h="yes"])
+fi
+
# For quota module
AC_CHECK_HEADERS(sys/ucred.h, [], [],
[
[
with_libperl="yes"
])
+
+AC_SUBST(PERL, "$perl_interpreter")
+
if test "x$with_libperl" = "xyes"
then
SAVE_CFLAGS=$CFLAGS
plugin_disk="no"
plugin_entropy="no"
plugin_interface="no"
+plugin_ipvs="no"
plugin_irq="no"
plugin_load="no"
plugin_memory="no"
plugin_serial="no"
plugin_swap="no"
plugin_tape="no"
+plugin_tcpconns="no"
plugin_users="no"
plugin_vserver="no"
plugin_wireless="no"
plugin_processes="yes"
plugin_serial="yes"
plugin_swap="yes"
+ plugin_tcpconns="yes"
plugin_vserver="yes"
plugin_wireless="yes"
+
+ if test "x$have_net_ip_vs_h" = "xyes" -o "x$have_ip_vs_h" = "xyes"
+ then
+ plugin_ipvs="yes"
+ fi
fi
# Mac OS X devices
AC_PLUGIN([hddtemp], [yes], [Query hddtempd])
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([load], [$plugin_load], [System load])
AC_PLUGIN([logfile], [yes], [File logging plugin])
AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics])
AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin])
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([vserver], [$plugin_vserver], [Linux VServer statistics])
AC_PLUGIN([wireless], [$plugin_wireless], [Wireless statistics])
AC_PLUGIN([xmms], [$with_libxmms], [XMMS statistics])
-AC_OUTPUT(Makefile src/Makefile src/collectd.conf src/liboconfig/Makefile src/liboping/Makefile)
+dnl ip_vs.h
+if test "x$ac_system" = "xLinux" -a "x$have_net_ip_vs_h$have_ip_vs_h" = "xnono"
+then
+ enable_ipvs="$enable_ipvs (ip_vs.h not found)"
+fi
+
+dnl Perl bindings
+AC_ARG_WITH(perl-bindings, [AS_HELP_STRING([--with-perl-bindings@<:@=OPTIONS@:>@], [Options passed to "perl Makefile.PL".])],
+[
+ if test "x$withval" != "xno" && test "x$withval" != "xyes"
+ then
+ PERL_BINDINGS_OPTIONS="$withval"
+ with_perl_bindings="yes"
+ fi
+],
+[
+ PERL_BINDINGS_OPTIONS=""
+ with_perl_bindings="yes"
+])
+if test "x$with_perl_bindings" = "xyes"
+then
+ PERL_BINDINGS="perl"
+else
+ PERL_BINDINGS=""
+fi
+AC_SUBST(PERL_BINDINGS)
+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_liboping" = "xyes" -a "x$with_own_liboping" = "xyes"
then
if test "x$with_libperl" = "xyes"
then
- with_libperl="yes (version `perl -MConfig -e 'print $Config{version};'`)"
+ with_libperl="yes (version `$perl_interpreter -MConfig -e 'print $Config{version};'`)"
else
enable_perl="no (needs libperl)"
fi
+if test "x$with_perl_bindings" = "xyes" -a "x$PERL_BINDINGS_OPTIONS" != "x"
+then
+ with_perl_bindings="yes ($PERL_BINDINGS_OPTIONS)"
+fi
+
cat <<EOF;
Configuration:
daemon mode . . . . $enable_daemon
debug . . . . . . . $enable_debug
+ Bindings:
+ perl . . . . . . . $with_perl_bindings
+
Modules:
apache . . . . . . $enable_apache
apcups . . . . . . $enable_apcups
hddtemp . . . . . . $enable_hddtemp
interface . . . . . $enable_interface
iptables . . . . . $enable_iptables
+ ipvs . . . . . . . $enable_ipvs
irq . . . . . . . . $enable_irq
load . . . . . . . $enable_load
logfile . . . . . . $enable_logfile
swap . . . . . . . $enable_swap
syslog . . . . . . $enable_syslog
tape . . . . . . . $enable_tape
+ tcpconns . . . . . $enable_tcpconns
unixsock . . . . . $enable_unixsock
users . . . . . . . $enable_users
vserver . . . . . . $enable_vserver
+++ /dev/null
-# collectd - Collectd.pm
-# 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>
-
-package Collectd;
-
-use strict;
-use warnings;
-
-require Exporter;
-
-our @ISA = qw( Exporter );
-
-our %EXPORT_TAGS = (
- 'funcs' => [ qw( plugin_register plugin_unregister
- plugin_dispatch_values plugin_log ) ],
- 'types' => [ qw( TYPE_INIT TYPE_READ TYPE_WRITE TYPE_SHUTDOWN TYPE_LOG
- TYPE_DATASET ) ],
- 'ds_types' => [ qw( DS_TYPE_COUNTER DS_TYPE_GAUGE ) ],
- 'log' => [ qw( LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG ) ],
-);
-
-{
- my %seen;
-
- push @{$EXPORT_TAGS{'all'}}, grep {! $seen{$_}++ } @{$EXPORT_TAGS{$_}}
- foreach keys %EXPORT_TAGS;
-}
-
-Exporter::export_ok_tags('all');
-
-bootstrap Collectd "4.1.2";
-
-1;
-
-# vim: set sw=4 ts=4 tw=78 noexpandtab :
-
+++ /dev/null
-package Collectd::Unixsock;
-
-=head1 NAME
-
-Collectd::Unixsock - Abstraction layer for accessing the functionality by collectd's unixsock plugin.
-
-=head1 SYNOPSIS
-
- use Collectd::Unixsock ();
-
- my $sock = Collectd::Unixsock->new ($path);
-
- my $value = $sock->getval (%identifier);
- $sock->putval (%identifier,
- time => time (),
- values => [123, 234, 345]);
-
- $sock->destroy ();
-
-=head1 DESCRIPTION
-
-collectd's unixsock plugin allows external programs to access the values it has
-collected or received and to submit own values. This Perl-module is simply a
-little abstraction layer over this interface to make it even easier for
-programmers to interact with the daemon.
-
-=cut
-
-use strict;
-use warnings;
-
-use Carp (qw(cluck confess));
-use IO::Socket::UNIX;
-use Regexp::Common (qw(number));
-
-return (1);
-
-sub _create_socket
-{
- my $path = shift;
- my $sock = IO::Socket::UNIX->new (Type => SOCK_STREAM, Peer => $path);
- if (!$sock)
- {
- cluck ("Cannot open UNIX-socket $path: $!");
- return;
- }
- return ($sock);
-} # _create_socket
-
-=head1 VALUE IDENTIFIER
-
-The values in the collectd are identified using an five-tupel (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
-B<type>, possibly completed by B<plugin_instance> and B<type_instance>.
-
-Usually you can pass this hash as follows:
-
- $obj->method (host => $host, plugin => $plugin, type => $type, %other_args);
-
-=cut
-
-sub _create_identifier
-{
- my $args = shift;
- my $host;
- my $plugin;
- my $type;
-
- if (!$args->{'host'} || !$args->{'plugin'} || !$args->{'type'})
- {
- cluck ("Need `host', `plugin' and `type'");
- return;
- }
-
- $host = $args->{'host'};
- $plugin = $args->{'plugin'};
- $plugin .= '-' . $args->{'plugin_instance'} if (defined ($args->{'plugin_instance'}));
- $type = $args->{'type'};
- $type .= '-' . $args->{'type_instance'} if (defined ($args->{'type_instance'}));
-
- return ("$host/$plugin/$type");
-} # _create_identifier
-
-sub _parse_identifier
-{
- my $string = shift;
- my $host;
- my $plugin;
- my $plugin_instance;
- my $type;
- my $type_instance;
- my $ident;
-
- ($host, $plugin, $type) = split ('/', $string);
-
- ($plugin, $plugin_instance) = split ('-', $plugin, 2);
- ($type, $type_instance) = split ('-', $type, 2);
-
- $ident =
- {
- host => $host,
- plugin => $plugin,
- type => $type
- };
- $ident->{'plugin_instance'} = $plugin_instance if (defined ($plugin_instance));
- $ident->{'type_instance'} = $type_instance if (defined ($type_instance));
-
- return ($ident);
-} # _parse_identifier
-
-=head1 PUBLIC METHODS
-
-=over 4
-
-=item I<$obj> = Collectd::Unixsock->B<new> ([I<$path>]);
-
-Creates a new connection to the daemon. The optional I<$path> argument gives
-the path to the UNIX socket of the C<unixsock plugin> and defaults to
-F</var/run/collectd-unixsock>. Returns the newly created object on success and
-false on error.
-
-=cut
-
-sub new
-{
- my $pkg = shift;
- my $path = @_ ? shift : '/var/run/collectd-unixsock';
- my $sock = _create_socket ($path) or return;
- my $obj = bless (
- {
- path => $path,
- sock => $sock,
- error => 'No error'
- }, $pkg);
- return ($obj);
-} # new
-
-=item I<$res> = I<$obj>-E<gt>B<getval> (I<%identifier>);
-
-Requests a value-list from the daemon. On success a hash-ref is returned with
-the name of each data-source as the key and the according value as, well, the
-value. On error false is returned.
-
-=cut
-
-sub getval
-{
- my $obj = shift;
- my %args = @_;
-
- my $status;
- my $fh = $obj->{'sock'} or confess;
- my $msg;
- my $identifier;
-
- my $ret = {};
-
- $identifier = _create_identifier (\%args) or return;
-
- $msg = "GETVAL $identifier\n";
- #print "-> $msg";
- send ($fh, $msg, 0) or confess ("send: $!");
-
- $msg = undef;
- recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
- #print "<- $msg";
-
- ($status, $msg) = split (' ', $msg, 2);
- if ($status <= 0)
- {
- $obj->{'error'} = $msg;
- return;
- }
-
- for (split (' ', $msg))
- {
- my $entry = $_;
- if ($entry =~ m/^(\w+)=NaN$/)
- {
- $ret->{$1} = undef;
- }
- elsif ($entry =~ m/^(\w+)=($RE{num}{real})$/)
- {
- $ret->{$1} = 0.0 + $2;
- }
- }
-
- return ($ret);
-} # getval
-
-=item I<$obj>-E<gt>B<putval> (I<%identifier>, B<time> => I<$time>, B<values> => [...]);
-
-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
-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
-(e.E<nbsp>g. system-load) may be C<undef>. Returns true upon success and false
-otherwise.
-
-=cut
-
-sub putval
-{
- my $obj = shift;
- my %args = @_;
-
- my $status;
- my $fh = $obj->{'sock'} or confess;
- my $msg;
- my $identifier;
- my $values;
-
- $identifier = _create_identifier (\%args) or return;
- if (!$args{'values'})
- {
- cluck ("Need argument `values'");
- return;
- }
-
- if (!ref ($args{'values'}))
- {
- $values = $args{'values'};
- }
- else
- {
- my $time = $args{'time'} ? $args{'time'} : time ();
- $values = join (':', $time, map { defined ($_) ? $_ : 'U' } (@{$args{'values'}}));
- }
-
- $msg = "PUTVAL $identifier $values\n";
- #print "-> $msg";
- send ($fh, $msg, 0) or confess ("send: $!");
- $msg = undef;
- recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
- #print "<- $msg";
-
- ($status, $msg) = split (' ', $msg, 2);
- return (1) if ($status == 0);
-
- $obj->{'error'} = $msg;
- return;
-} # putval
-
-=item I<$res> = I<$obj>-E<gt>B<listval> ()
-
-Queries a list of values from the daemon. The list is returned as an array of
-hash references, where each hash reference is a valid identifier. The C<time>
-member of each hash holds the epoch value of the last update of that value.
-
-=cut
-
-sub listval
-{
- my $obj = shift;
- my $msg;
- my @ret = ();
- my $status;
- my $fh = $obj->{'sock'} or confess;
-
- $msg = "LISTVAL\n";
- send ($fh, $msg, 0) or confess ("send: $!");
-
- $msg = <$fh>;
- ($status, $msg) = split (' ', $msg, 2);
- if ($status < 0)
- {
- $obj->{'error'} = $msg;
- return;
- }
-
- for (my $i = 0; $i < $status; $i++)
- {
- my $time;
- my $ident;
-
- $msg = <$fh>;
- chomp ($msg);
-
- ($time, $ident) = split (' ', $msg, 2);
-
- $ident = _parse_identifier ($ident);
- $ident->{'time'} = int ($time);
-
- push (@ret, $ident);
- } # for (i = 0 .. $status)
-
- return (@ret);
-} # listval
-
-=item I<$obj>-E<gt>destroy ();
-
-Closes the socket before the object is destroyed. This function is also
-automatically called then the object goes out of scope.
-
-=back
-
-=cut
-
-sub destroy
-{
- my $obj = shift;
- if ($obj->{'sock'})
- {
- close ($obj->{'sock'});
- delete ($obj->{'sock'});
- }
-}
-
-sub DESTROY
-{
- my $obj = shift;
- $obj->destroy ();
-}
-
-=head1 AUTHOR
-
-Florian octo Forster E<lt>octo@verplant.orgE<gt>
-
-=cut
'GPRINT:max:MAX:%6.2lf%sByte Max,',
'GPRINT:avg:LAST:%6.2lf%sByte Last\l'
],
- ols_swap => [
+ old_swap => [
'DEF:used_avg={file}:used:AVERAGE',
'DEF:used_min={file}:used:MIN',
'DEF:used_max={file}:used:MAX',
'GPRINT:used_max:MAX:%5.1lf%s Max,',
'GPRINT:used_avg:LAST:%5.1lf%s Last\l'
],
+ tcp_connections => ['-v', 'Connections',
+ 'DEF:avg={file}:value:AVERAGE',
+ 'DEF:min={file}:value:MIN',
+ 'DEF:max={file}:value:MAX',
+ "AREA:max#$HalfBlue",
+ "AREA:min#$Canvas",
+ "LINE1:avg#$FullBlue:Connections",
+ 'GPRINT:min:MIN:%4.1lf Min,',
+ 'GPRINT:avg:AVERAGE:%4.1lf Avg,',
+ 'GPRINT:max:MAX:%4.1lf Max,',
+ 'GPRINT:avg:LAST:%4.1lf Last\l'
+ ],
temperature => ['-v', 'Celsius',
'DEF:temp_avg={file}:value:AVERAGE',
'DEF:temp_min={file}:value:MIN',
$MetaGraphDefs->{'swap'} = \&meta_graph_swap;
$MetaGraphDefs->{'mysql_commands'} = \&meta_graph_mysql_commands;
$MetaGraphDefs->{'mysql_handler'} = \&meta_graph_mysql_commands;
+ $MetaGraphDefs->{'tcp_connections'} = \&meta_graph_tcp_connections;
} # load_graph_definitions
sub meta_graph_generic_stack
return (meta_graph_generic_stack ($opts, $sources));
} # meta_graph_swap
+sub meta_graph_tcp_connections
+{
+ confess ("Wrong number of arguments") if (@_ != 5);
+
+ my $host = shift;
+ my $plugin = shift;
+ my $plugin_instance = shift;
+ my $type = shift;
+ my $type_instances = shift;
+
+ my $opts = {};
+ my $sources = [];
+
+ $opts->{'title'} = "$host/$plugin"
+ . (defined ($plugin_instance) ? "-$plugin_instance" : '') . "/$type";
+ $opts->{'number_format'} = '%6.2lf';
+
+ $opts->{'rrd_opts'} = ['-v', 'Connections'];
+
+ my @files = ();
+
+ $opts->{'colors'} =
+ {
+ ESTABLISHED => '00e000',
+ SYN_SENT => '00e0ff',
+ SYN_RECV => '00e0a0',
+ FIN_WAIT1 => 'f000f0',
+ FIN_WAIT2 => 'f000a0',
+ TIME_WAIT => 'ffb000',
+ CLOSE => '0000f0',
+ CLOSE_WAIT => '0000a0',
+ LAST_ACK => '000080',
+ LISTEN => 'ff0000',
+ CLOSING => '000000'
+ };
+
+ _custom_sort_arrayref ($type_instances,
+ [qw(ESTABLISHED SYN_SENT SYN_RECV FIN_WAIT1 FIN_WAIT2 TIME_WAIT CLOSE
+ CLOSE_WAIT LAST_ACK CLOSING LISTEN)]);
+
+ for (@$type_instances)
+ {
+ my $inst = $_;
+ my $file = '';
+ my $title = $opts->{'title'};
+
+ for (@DataDirs)
+ {
+ if (-e "$_/$title-$inst.rrd")
+ {
+ $file = "$_/$title-$inst.rrd";
+ last;
+ }
+ }
+ confess ("No file found for $title") if ($file eq '');
+
+ push (@$sources,
+ {
+ name => $inst,
+ file => $file
+ }
+ );
+ } # for (@$type_instances)
+
+ return (meta_graph_generic_stack ($opts, $sources));
+} # meta_graph_tcp_connections
# vim: shiftwidth=2:softtabstop=2:tabstop=8
collectd_DEPENDENCIES += iptables.la
endif
+if BUILD_PLUGIN_IPVS
+pkglib_LTLIBRARIES += ipvs.la
+ipvs_la_SOURCES = ipvs.c
+ipvs_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" ipvs.la
+collectd_DEPENDENCIES += ipvs.la
+endif
+
if BUILD_PLUGIN_IRQ
pkglib_LTLIBRARIES += irq.la
irq_la_SOURCES = irq.c
collectd_DEPENDENCIES += tape.la
endif
+if BUILD_PLUGIN_TCPCONNS
+pkglib_LTLIBRARIES += tcpconns.la
+tcpconns_la_SOURCES = tcpconns.c
+tcpconns_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" tcpconns.la
+collectd_DEPENDENCIES += tcpconns.la
+endif
+
if BUILD_PLUGIN_UNIXSOCK
pkglib_LTLIBRARIES += unixsock.la
unixsock_la_SOURCES = unixsock.c utils_cmd_putval.h utils_cmd_putval.c
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
+
.pod.1:
- pod2man --release=$(VERSION) --center=$(PACKAGE) $< >$@
+ pod2man --release=$(VERSION) --center=$(PACKAGE) $< >.pod2man.tmp 2>/dev/null && mv -f .pod2man.tmp $@ || true
.pod.5:
- pod2man --section=5 --release=$(VERSION) --center=$(PACKAGE) $< >$@
+ pod2man --section=5 --release=$(VERSION) --center=$(PACKAGE) $< >.pod2man.tmp 2>/dev/null && mv -f .pod2man.tmp $@ || true
install-exec-hook:
$(mkinstalldirs) $(DESTDIR)$(sysconfdir)
Type "voltage"
Table false
Instance "input_line1"
+ Scale 0.1
Values "SNMPv2-SMI::enterprises.6050.5.4.1.1.2.1"
</Data>
<Data "hr_users">
Type "users"
Table false
Instance ""
+ Shift -1
Values "HOST-RESOURCES-MIB::hrSystemNumUsers.0"
</Data>
<Data "std_traffic">
setting depends on whether B<Table> is set to I<true> or I<false>:
If B<Table> is set to I<true>, I<Instance> is interpreted as an SNMP-prefix
-that will return a list of strings. Those strings are then used as the actual
+that will return a list of values. Those values are then used as the actual
type-instance. An example would be the C<IF-MIB::ifDescr> subtree.
L<variables(5)> from the SNMP distribution describes the format of OIDs.
+If B<Table> is set to I<true> and B<Instance> is omitted, then "SUBID" will be
+used as the instance.
+
If B<Table> is set to I<false> the actual string configured for I<Instance> is
copied into the value-list. In this case I<Instance> may be empty, i.E<nbsp>e.
"".
+=item B<InstancePrefix> I<String>
+
+If B<Table> is set to I<true>, you may feel the need to add something to the
+instance of the files. If set, I<String> is prepended to the instance as
+determinded by querying the agent. When B<Table> is set to I<false> this option
+has no effect.
+
+The C<UPS-MIB> is an example where you need this setting: It has voltages of
+the inlets, outlets and the battery of an UPS. However, it doesn't provide a
+descriptive column for these voltages. In this case having 1, 2,E<nbsp>... as
+instances is not enough, because the inlet voltages and outlet voltages may
+both have the subids 1, 2,E<nbsp>... You can use this setting to distinguish
+between the different voltages.
+
=item B<Values> I<OID> [I<OID> ...]
Configures the values to be queried from the SNMP host. The meaning slightly
value, e.E<nbsp>g. C<IF-MIB::ifInOctets.3> for the third counter of incoming
traffic.
+=item B<Scale> I<Value>
+
+The gauge-values returned by the SNMP-agent are multiplied by I<Value>. This
+is useful when values are transfered as a fixed point real number. For example,
+thermometers may transfer B<243> but actually mean B<24.3>, so you can specify
+a scale value of B<0.1> to correct this. The default value is of course B<1.0>.
+
+This value is not applied to counter-values.
+
+=item B<Shift> I<Value>
+
+I<Value> is added to gauge-values returned by the SNMP-agent after they have
+been multiplied by any B<Scale> value. If, for example, a thermometer returns
+degrees Kelvin you could specify a shift of B<273.15> here to store values in
+degrees Celsius. The default value is is course B<0.0>.
+
+This value is not applied to counter-values.
+
=back
=head2 The Host block
@BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
@BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
@BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
+@BUILD_PLUGIN_IPVS_TRUE@LoadPlugin ipvs
@BUILD_PLUGIN_IRQ_TRUE@LoadPlugin irq
@BUILD_PLUGIN_LOAD_TRUE@LoadPlugin load
@BUILD_PLUGIN_LOGFILE_TRUE@LoadPlugin logfile
@BUILD_PLUGIN_SWAP_TRUE@LoadPlugin swap
@BUILD_PLUGIN_SYSLOG_TRUE@LoadPlugin syslog
@BUILD_PLUGIN_TAPE_TRUE@LoadPlugin tape
+@BUILD_PLUGIN_TCPCONNS_TRUE@Plugin tcpconns
@BUILD_PLUGIN_UNIXSOCK_TRUE@LoadPlugin unixsock
@BUILD_PLUGIN_USERS_TRUE@LoadPlugin users
@BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver
# LogLevel info
#</Plugin>
+#<Plugin tcpconns>
+# ListeningPorts false
+# LocalPort "25"
+# RemotePort "25"
+#</Plugin>
+
#<Plugin unixsock>
# SocketFile "@prefix@/var/run/@PACKAGE_NAME@-unixsock"
# SocketGroup "collectd"
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>
+
+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.
+
+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.
+
=item B<PIDFile> I<File>
Sets where to write the PID file to. This file is overwritten when it exists
=back
+=head2 Plugin C<tcpconns>
+
+The C<tcpconns plugin> counts the number of currently established TCP
+connections based on the local port and/or the remote port. Since there may be
+a lot of connections the default if to count all connections with a local port,
+for which a listening socket is opened. You can use the following options to
+fine-tune the ports you are interested in:
+
+=over 4
+
+=item B<ListeningPorts> I<true>|I<false>
+
+If this option is set to I<true>, statistics for all local ports for which a
+listening socket exists are collected. The default depends on B<LocalPort> and
+B<RemotePort> (see below): If no port at all is specifically selected, the
+default is to collect listening ports. If specific ports (no matter if local or
+remote ports) are selected, this option defaults to I<false>, i.E<nbsp>e. only
+the selected ports will be collected unless this option is set to I<true>
+specifically.
+
+=item B<LocalPort> I<Port>
+
+Count the connections to a specific local port. This can be used to see how
+many connections are handeled by a specific daemon, e.E<nbsp>g. the mailserver.
+You have to specify the port in numeric form, so for the mailserver example
+you'd need to set B<25>.
+
+=item B<RemotePort> I<Port>
+
+Count the connections to a specific remote port. This is usefull to see how
+much a remote service is used. This is most useful if you want to know how many
+connections a local service has opened to remote services, e.E<nbsp>g. how many
+connections a mail server or news server has to other mail or news servers, or
+how many connections a web proxy holds to web servers. You have to give the
+port in numeric form.
+
+=back
+
=head2 Plugin C<unixsock>
=over 4
return (0);
}
+#define CF_MAX_DEPTH 8
+static oconfig_item_t *cf_read_file (const char *file, int depth);
+
+static int cf_include_all (oconfig_item_t *root, int depth)
+{
+ int i;
+
+ for (i = 0; i < root->children_num; i++)
+ {
+ oconfig_item_t *new;
+ oconfig_item_t *old;
+
+ /* Ignore all blocks, including `Include' blocks. */
+ if (root->children[i].children_num != 0)
+ continue;
+
+ if (strcasecmp (root->children[i].key, "Include") != 0)
+ continue;
+
+ old = root->children + i;
+
+ if ((old->values_num != 1)
+ || (old->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ ERROR ("configfile: `Include' needs exactly one string argument.");
+ continue;
+ }
+
+ new = cf_read_file (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)
+ {
+ 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;
+ }
+
+ /* 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)));
+ }
+
+ /* 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);
+ }
+
+ /* 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;
+
+ /* 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++) */
+
+ return (0);
+} /* int cf_include_all */
+
+static oconfig_item_t *cf_read_file (const char *file, int depth)
+{
+ oconfig_item_t *root;
+
+ if (depth >= CF_MAX_DEPTH)
+ {
+ ERROR ("configfile: Not including `%s' because the maximum nesting depth has been reached.",
+ file);
+ return (NULL);
+ }
+
+ 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 */
+
/*
* Public functions
*/
oconfig_item_t *conf;
int i;
- conf = oconfig_parse_file (filename);
+ conf = cf_read_file (filename, 0 /* depth */);
if (conf == NULL)
{
ERROR ("Unable to read config file %s.", filename);
--- /dev/null
+/**
+ * collectd - src/ipvs.c (based on ipvsadm and libipvs)
+ * Copyright (C) 1997 Steven Clarke <steven@monmouth.demon.co.uk>
+ * Copyright (C) 1998-2004 Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Copyright (C) 2003-2004 Peter Kese <peter.kese@ijs.si>
+ * 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
+ *
+ * Authors:
+ * Sebastian Harl <sh at tokkee.org>
+ **/
+
+/*
+ * This plugin collects statistics about IPVS connections. It requires Linux
+ * kernels >= 2.6.
+ *
+ * See http://www.linuxvirtualserver.org/software/index.html for more
+ * information about IPVS.
+ */
+
+#include "collectd.h"
+#include "plugin.h"
+#include "common.h"
+
+#if HAVE_ARPA_INET_H
+# include <arpa/inet.h>
+#endif /* HAVE_ARPA_INET_H */
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif /* HAVE_SYS_SOCKET_H */
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif /* HAVE_NETINET_IN_H */
+
+/* this can probably only be found in the kernel sources */
+#if HAVE_NET_IP_VS_H
+# include <net/ip_vs.h>
+#elif HAVE_IP_VS_H
+# include <ip_vs.h>
+#endif /* HAVE_IP_VS_H */
+
+#define log_err(...) ERROR ("ipvs: " __VA_ARGS__)
+#define log_info(...) INFO ("ipvs: " __VA_ARGS__)
+
+/*
+ * private variables
+ */
+static int sockfd = -1;
+
+/*
+ * libipvs API
+ */
+static struct ip_vs_get_services *ipvs_get_services (void)
+{
+ struct ip_vs_getinfo ipvs_info;
+ struct ip_vs_get_services *ret;
+
+ socklen_t len;
+
+ len = sizeof (ipvs_info);
+
+ if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,
+ (void *)&ipvs_info, &len)) {
+ char errbuf[1024];
+ log_err ("ip_vs_get_services: getsockopt() failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return NULL;
+ }
+
+ len = sizeof (*ret) +
+ sizeof (struct ip_vs_service_entry) * ipvs_info.num_services;
+
+ if (NULL == (ret = malloc (len))) {
+ log_err ("ipvs_get_services: Out of memory.");
+ exit (3);
+ }
+
+ ret->num_services = ipvs_info.num_services;
+
+ if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_SERVICES,
+ (void *)ret, &len)) {
+ char errbuf[1024];
+ log_err ("ipvs_get_services: getsockopt failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+
+ free(ret);
+ return NULL;
+ }
+ return ret;
+} /* ipvs_get_services */
+
+static struct ip_vs_get_dests *ipvs_get_dests (struct ip_vs_service_entry *se)
+{
+ struct ip_vs_get_dests *ret;
+ socklen_t len;
+
+ len = sizeof (*ret) + sizeof (struct ip_vs_dest_entry) * se->num_dests;
+
+ if (NULL == (ret = malloc (len))) {
+ log_err ("ipvs_get_dests: Out of memory.");
+ exit (3);
+ }
+
+ ret->fwmark = se->fwmark;
+ ret->protocol = se->protocol;
+ ret->addr = se->addr;
+ ret->port = se->port;
+ ret->num_dests = se->num_dests;
+
+ if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_DESTS,
+ (void *)ret, &len)) {
+ char errbuf[1024];
+ log_err ("ipvs_get_dests: getsockopt() failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ free (ret);
+ return NULL;
+ }
+ return ret;
+} /* ip_vs_get_dests */
+
+/*
+ * collectd plugin API and helper functions
+ */
+static int cipvs_init (void)
+{
+ struct ip_vs_getinfo ipvs_info;
+
+ socklen_t len;
+
+ if (-1 == (sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW))) {
+ char errbuf[1024];
+ log_err ("cipvs_init: socket() failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return -1;
+ }
+
+ len = sizeof (ipvs_info);
+
+ if (0 != getsockopt (sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,
+ (void *)&ipvs_info, &len)) {
+ char errbuf[1024];
+ log_err ("cipvs_init: getsockopt() failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ close (sockfd);
+ sockfd = -1;
+ return -1;
+ }
+
+ /* we need IPVS >= 1.1.4 */
+ if (ipvs_info.version < ((1 << 16) + (1 << 8) + 4)) {
+ log_err ("cipvs_init: IPVS version too old (%d.%d.%d < %d.%d.%d)",
+ NVERSION (ipvs_info.version), 1, 1, 4);
+ close (sockfd);
+ sockfd = -1;
+ return -1;
+ }
+ else {
+ log_info ("Successfully connected to IPVS %d.%d.%d",
+ NVERSION (ipvs_info.version));
+ }
+ return 0;
+} /* cipvs_init */
+
+/*
+ * ipvs-<virtual IP>_{UDP,TCP}<port>/<type>-total
+ * ipvs-<virtual IP>_{UDP,TCP}<port>/<type>-<real IP>_<port>
+ */
+
+/* plugin instance */
+static int get_pi (struct ip_vs_service_entry *se, char *pi, size_t size)
+{
+ struct in_addr addr;
+ int len = 0;
+
+ if ((NULL == se) || (NULL == pi))
+ return 0;
+
+ addr.s_addr = se->addr;
+
+ /* inet_ntoa() returns a pointer to a statically allocated buffer
+ * I hope non-glibc systems behave the same */
+ len = snprintf (pi, size, "%s_%s%u", inet_ntoa (addr),
+ (se->protocol == IPPROTO_TCP) ? "TCP" : "UDP",
+ ntohs (se->port));
+
+ if ((0 > len) || (size <= len)) {
+ log_err ("plugin instance truncated: %s", pi);
+ return -1;
+ }
+ return 0;
+} /* get_pi */
+
+/* type instance */
+static int get_ti (struct ip_vs_dest_entry *de, char *ti, size_t size)
+{
+ struct in_addr addr;
+ int len = 0;
+
+ if ((NULL == de) || (NULL == ti))
+ return 0;
+
+ addr.s_addr = de->addr;
+
+ /* inet_ntoa() returns a pointer to a statically allocated buffer
+ * I hope non-glibc systems behave the same */
+ len = snprintf (ti, size, "%s_%u", inet_ntoa (addr),
+ ntohs (de->port));
+
+ if ((0 > len) || (size <= len)) {
+ log_err ("type instance truncated: %s", ti);
+ return -1;
+ }
+ return 0;
+} /* get_ti */
+
+static void cipvs_submit_connections (char *pi, char *ti, counter_t value)
+{
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+
+ values[0].counter = value;
+
+ vl.values = values;
+ vl.values_len = 1;
+
+ vl.time = time (NULL);
+ vl.interval = interval_g;
+
+ strcpy (vl.host, hostname_g);
+ strcpy (vl.plugin, "ipvs");
+ strcpy (vl.plugin_instance, pi);
+ strcpy (vl.type_instance, (NULL != ti) ? ti : "total");
+
+ plugin_dispatch_values ("connections", &vl);
+ return;
+} /* cipvs_submit_connections */
+
+static void cipvs_submit_if (char *pi, char *t, char *ti,
+ counter_t rx, counter_t tx)
+{
+ value_t values[2];
+ value_list_t vl = VALUE_LIST_INIT;
+
+ values[0].counter = rx;
+ values[1].counter = tx;
+
+ vl.values = values;
+ vl.values_len = 2;
+
+ vl.time = time (NULL);
+ vl.interval = interval_g;
+
+ strcpy (vl.host, hostname_g);
+ strcpy (vl.plugin, "ipvs");
+ strcpy (vl.plugin_instance, pi);
+ strcpy (vl.type_instance, (NULL != ti) ? ti : "total");
+
+ plugin_dispatch_values (t, &vl);
+ return;
+} /* cipvs_submit_if */
+
+static void cipvs_submit_dest (char *pi, struct ip_vs_dest_entry *de) {
+ struct ip_vs_stats_user stats = de->stats;
+
+ char ti[DATA_MAX_NAME_LEN];
+
+ if (0 != get_ti (de, ti, DATA_MAX_NAME_LEN))
+ return;
+
+ cipvs_submit_connections (pi, ti, stats.conns);
+ cipvs_submit_if (pi, "if_packets", ti, stats.inpkts, stats.outpkts);
+ cipvs_submit_if (pi, "if_octets", ti, stats.inbytes, stats.outbytes);
+ return;
+} /* cipvs_submit_dest */
+
+static void cipvs_submit_service (struct ip_vs_service_entry *se)
+{
+ struct ip_vs_stats_user stats = se->stats;
+ struct ip_vs_get_dests *dests = ipvs_get_dests (se);
+
+ char pi[DATA_MAX_NAME_LEN];
+
+ int i = 0;
+
+ if (0 != get_pi (se, pi, DATA_MAX_NAME_LEN))
+ return;
+
+ cipvs_submit_connections (pi, NULL, stats.conns);
+ cipvs_submit_if (pi, "if_packets", NULL, stats.inpkts, stats.outpkts);
+ cipvs_submit_if (pi, "if_octets", NULL, stats.inbytes, stats.outbytes);
+
+ for (i = 0; i < dests->num_dests; ++i)
+ cipvs_submit_dest (pi, &dests->entrytable[i]);
+
+ free (dests);
+ return;
+} /* cipvs_submit_service */
+
+static int cipvs_read (void)
+{
+ struct ip_vs_get_services *services = NULL;
+ int i = 0;
+
+ if (sockfd < 0)
+ return (-1);
+
+ if (NULL == (services = ipvs_get_services ()))
+ return -1;
+
+ for (i = 0; i < services->num_services; ++i)
+ cipvs_submit_service (&services->entrytable[i]);
+
+ free (services);
+ return 0;
+} /* cipvs_read */
+
+static int cipvs_shutdown (void)
+{
+ if (sockfd >= 0)
+ close (sockfd);
+ sockfs = -1;
+
+ return 0;
+} /* cipvs_shutdown */
+
+void module_register (void)
+{
+ plugin_register_init ("ipvs", cipvs_init);
+ plugin_register_read ("ipvs", cipvs_read);
+ plugin_register_shutdown ("ipvs", cipvs_shutdown);
+ return;
+} /* module_register */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
#define PLUGIN_DATASET 255
#define log_debug(...) DEBUG ("perl: " __VA_ARGS__)
+#define log_info(...) INFO ("perl: " __VA_ARGS__)
#define log_warn(...) WARNING ("perl: " __VA_ARGS__)
#define log_err(...) ERROR ("perl: " __VA_ARGS__)
/* this is defined in DynaLoader.a */
void boot_DynaLoader (PerlInterpreter *, CV *);
-static XS (Collectd_plugin_register);
-static XS (Collectd_plugin_unregister);
+static XS (Collectd_plugin_register_ds);
+static XS (Collectd_plugin_unregister_ds);
static XS (Collectd_plugin_dispatch_values);
static XS (Collectd_plugin_log);
int *values;
} ds_types_t;
-typedef struct {
- int wait_time;
- int wait_left;
-
- SV *sub;
-} pplugin_t;
-
/*
* private variables
*/
-/* valid configuration file keys */
-static const char *config_keys[] =
-{
- "LoadPlugin",
- "BaseName",
- "IncludeDir"
-};
-static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
-
static PerlInterpreter *perl = NULL;
+static int perl_argc = 0;
+static char **perl_argv = NULL;
+
static char base_name[DATA_MAX_NAME_LEN] = "";
-static char *plugin_types[] = { "init", "read", "write", "shutdown" };
-static HV *plugins[PLUGIN_TYPES];
static HV *data_sets;
static struct {
XS ((*f));
} api[] =
{
- { "Collectd::plugin_register", Collectd_plugin_register },
- { "Collectd::plugin_unregister", Collectd_plugin_unregister },
- { "Collectd::plugin_dispatch_values", Collectd_plugin_dispatch_values },
- { "Collectd::plugin_log", Collectd_plugin_log },
+ { "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_log", Collectd_plugin_log },
{ "", NULL }
};
+struct {
+ char name[64];
+ int value;
+} constants[] =
+{
+ { "Collectd::TYPE_INIT", PLUGIN_INIT },
+ { "Collectd::TYPE_READ", PLUGIN_READ },
+ { "Collectd::TYPE_WRITE", PLUGIN_WRITE },
+ { "Collectd::TYPE_SHUTDOWN", PLUGIN_SHUTDOWN },
+ { "Collectd::TYPE_LOG", PLUGIN_LOG },
+ { "Collectd::TYPE_DATASET", PLUGIN_DATASET },
+ { "Collectd::DS_TYPE_COUNTER", DS_TYPE_COUNTER },
+ { "Collectd::DS_TYPE_GAUGE", DS_TYPE_GAUGE },
+ { "Collectd::LOG_ERR", LOG_ERR },
+ { "Collectd::LOG_WARNING", LOG_WARNING },
+ { "Collectd::LOG_NOTICE", LOG_NOTICE },
+ { "Collectd::LOG_INFO", LOG_INFO },
+ { "Collectd::LOG_DEBUG", LOG_DEBUG },
+ { "", 0 }
+};
+
/*
* Helper functions for data type conversion.
} /* char *get_module_name */
/*
- * Add a new plugin with the given name.
- */
-static int pplugin_register (int type, const char *name, SV *sub)
-{
- pplugin_t *p = NULL;
-
- if ((type < 0) || (type >= PLUGIN_TYPES))
- return -1;
-
- if (NULL == name)
- return -1;
-
- p = (pplugin_t *)smalloc (sizeof (pplugin_t));
- /* this happens during parsing of config file,
- * thus interval_g is not set correctly */
- p->wait_time = 10;
- p->wait_left = 0;
- p->sub = Perl_newSVsv (perl, sub);
-
- if (NULL == Perl_hv_store (perl, plugins[type], name, strlen (name),
- Perl_sv_setref_pv (perl, Perl_newSV (perl, 0), 0, p), 0)) {
- log_debug ("pplugin_register: Failed to add plugin \"%s\" (\"%s\")",
- name, SvPV_nolen (sub));
- Perl_sv_free (perl, p->sub);
- sfree (p);
- return -1;
- }
- return 0;
-} /* static int pplugin_register (int, char *, SV *) */
-
-/*
- * Removes the plugin with the given name and frees any ressources.
- */
-static int pplugin_unregister (int type, char *name)
-{
- SV *tmp = NULL;
-
- if ((type < 0) || (type >= PLUGIN_TYPES))
- return -1;
-
- if (NULL == name)
- return -1;
-
- /* freeing the allocated memory of the element itself (pplugin_t *) causes
- * a segfault during perl_destruct () thus I assume perl somehow takes
- * care of this... */
-
- tmp = Perl_hv_delete (perl, plugins[type], name, strlen (name), 0);
- if (NULL != tmp) {
- pplugin_t *p = (pplugin_t *)SvIV ((SV *)SvRV (tmp));
- Perl_sv_free (perl, p->sub);
- }
- return 0;
-} /* static int pplugin_unregister (char *) */
-
-/*
* Add a plugin's data set definition.
*/
static int pplugin_register_data_set (char *name, AV *dataset)
} /* static int pplugin_dispatch_values (char *, HV *) */
/*
- * Call a plugin's working function.
+ * Call all working functions of the given type.
*/
-static int pplugin_call (int type, char *name, SV *sub, va_list ap)
+static int pplugin_call_all (int type, ...)
{
int retvals = 0;
- I32 xflags = G_NOARGS;
+ va_list ap;
int ret = 0;
dSP;
if ((type < 0) || (type >= PLUGIN_TYPES))
return -1;
+ va_start (ap, type);
+
ENTER;
SAVETMPS;
PUSHMARK (SP);
+ XPUSHs (sv_2mortal (Perl_newSViv (perl, (IV)type)));
+
if (PLUGIN_WRITE == type) {
/*
* $_[0] = $plugin_type;
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)));
-
- xflags = 0;
}
else if (PLUGIN_LOG == type) {
/*
*/
XPUSHs (sv_2mortal (Perl_newSViv (perl, va_arg (ap, int))));
XPUSHs (sv_2mortal (Perl_newSVpv (perl, va_arg (ap, char *), 0)));
-
- xflags = 0;
}
PUTBACK;
- /* prevent an endless loop */
- if (PLUGIN_LOG != type)
- log_debug ("pplugin_call: executing %s::%s->%s()",
- base_name, name, plugin_types[type]);
-
- retvals = Perl_call_sv (perl, sub, G_SCALAR | xflags);
+ retvals = Perl_call_pv (perl, "Collectd::plugin_call_all", G_SCALAR);
SPAGAIN;
- if (1 > retvals) {
- if (PLUGIN_LOG != type)
- log_warn ("pplugin_call: "
- "%s::%s->%s() returned void - assuming true",
- base_name, name, plugin_types[type]);
- }
- else {
+ if (0 < retvals) {
SV *tmp = POPs;
if (! SvTRUE (tmp))
ret = -1;
PUTBACK;
FREETMPS;
LEAVE;
- return ret;
-} /* static int pplugin_call (int, char *, SV *, va_list) */
-
-/*
- * Call all working functions of the given type.
- */
-static int pplugin_call_all (int type, ...)
-{
- SV *tmp = NULL;
-
- char *plugin;
- I32 len;
-
- if ((type < 0) || (type >= PLUGIN_TYPES))
- return -1;
-
- if (0 == Perl_hv_iterinit (perl, plugins[type]))
- return 0;
-
- while (NULL != (tmp = Perl_hv_iternextsv (perl, plugins[type],
- &plugin, &len))) {
- pplugin_t *p;
- va_list ap;
-
- int status;
-
- va_start (ap, type);
-
- p = (pplugin_t *)SvIV ((SV *)SvRV (tmp));
-
- if (p->wait_left > 0)
- p->wait_left -= interval_g;
- if (p->wait_left > 0)
- continue;
-
- if (0 == (status = pplugin_call (type, plugin, p->sub, ap))) {
- p->wait_left = 0;
- p->wait_time = interval_g;
- }
- else if (PLUGIN_READ == type) {
- p->wait_left = p->wait_time;
- p->wait_time <<= 1;
-
- if (p->wait_time > 86400)
- p->wait_time = 86400;
-
- log_warn ("%s->read() failed. Will suspend it for %i seconds.",
- plugin, p->wait_left);
- }
- else if (PLUGIN_INIT == type) {
- int i = 0;
-
- log_err ("%s->init() failed. Plugin will be disabled.",
- plugin, status);
-
- for (i = 0; i < PLUGIN_TYPES; ++i)
- pplugin_unregister (i, plugin);
- }
- else if (PLUGIN_LOG != type) {
- log_warn ("%s->%s() failed with status %i.",
- plugin, plugin_types[type], status);
- }
-
- va_end (ap);
- }
- return 0;
+ va_end (ap);
+ return ret;
} /* static int pplugin_call_all (int, ...) */
*/
/*
- * Collectd::plugin_register (type, name, data).
+ * Collectd::plugin_register_data_set (type, dataset).
*
* type:
- * init, read, write, shutdown, data set
- *
- * name:
- * name of the plugin
+ * type of the dataset
*
- * data:
- * reference to the plugin's subroutine that does the work or the data set
- * definition
+ * dataset:
+ * dataset to be registered
*/
-static XS (Collectd_plugin_register)
+static XS (Collectd_plugin_register_ds)
{
- int type = 0;
SV *data = NULL;
-
- int ret = 0;
+ int ret = 0;
dXSARGS;
- if (3 != items) {
- log_err ("Usage: Collectd::plugin_register(type, name, data)");
+ if (2 != items) {
+ log_err ("Usage: Collectd::plugin_register_data_set(type, dataset)");
XSRETURN_EMPTY;
}
- log_debug ("Collectd::plugin_register: "
- "type = \"%i\", name = \"%s\", \"%s\"",
- (int)SvIV (ST (0)), SvPV_nolen (ST (1)), SvPV_nolen (ST (2)));
+ log_debug ("Collectd::plugin_register_data_set: "
+ "type = \"%s\", dataset = \"%s\"",
+ SvPV_nolen (ST (0)), SvPV_nolen (ST (1)));
- type = (int)SvIV (ST (0));
- data = ST (2);
+ data = ST (1);
- if ((type >= 0) && (type < PLUGIN_TYPES)
- && SvROK (data) && (SVt_PVCV == SvTYPE (SvRV (data)))) {
- ret = pplugin_register (type, SvPV_nolen (ST (1)), data);
- }
- else if ((type == PLUGIN_DATASET)
- && SvROK (data) && (SVt_PVAV == SvTYPE (SvRV (data)))) {
- ret = pplugin_register_data_set (SvPV_nolen (ST (1)),
+ if (SvROK (data) && (SVt_PVAV == SvTYPE (SvRV (data)))) {
+ ret = pplugin_register_data_set (SvPV_nolen (ST (0)),
(AV *)SvRV (data));
}
else {
- log_err ("Collectd::plugin_register: Invalid data.");
+ log_err ("Collectd::plugin_register_data_set: Invalid data.");
XSRETURN_EMPTY;
}
XSRETURN_YES;
else
XSRETURN_EMPTY;
-} /* static XS (Collectd_plugin_register) */
+} /* static XS (Collectd_plugin_register_ds) */
/*
- * Collectd::plugin_unregister (type, name).
+ * Collectd::plugin_unregister_data_set (type).
*
* type:
- * init, read, write, shutdown, data set
- *
- * name:
- * name of the plugin
+ * type of the dataset
*/
-static XS (Collectd_plugin_unregister)
+static XS (Collectd_plugin_unregister_ds)
{
- int type = 0;
- int ret = 0;
-
dXSARGS;
- if (2 != items) {
- log_err ("Usage: Collectd::plugin_unregister(type, name)");
+ if (1 != items) {
+ log_err ("Usage: Collectd::plugin_unregister_data_set(type)");
XSRETURN_EMPTY;
}
- log_debug ("Collectd::plugin_unregister: type = \"%i\", name = \"%s\"",
- (int)SvIV (ST (0)), SvPV_nolen (ST (1)));
-
- type = (int)SvIV (ST (0));
+ log_debug ("Collectd::plugin_unregister_data_set: type = \"%s\"",
+ SvPV_nolen (ST (0)));
- if ((type >= 0) && (type < PLUGIN_TYPES)) {
- ret = pplugin_unregister (type, SvPV_nolen (ST (1)));
- }
- else if (type == PLUGIN_DATASET) {
- ret = pplugin_unregister_data_set (SvPV_nolen (ST (1)));
- }
- else {
- log_err ("Collectd::plugin_unregister: Invalid type.");
- XSRETURN_EMPTY;
- }
-
- if (0 == ret)
+ if (0 == pplugin_unregister_data_set (SvPV_nolen (ST (1))))
XSRETURN_YES;
else
XSRETURN_EMPTY;
-} /* static XS (Collectd_plugin_unregister) */
+} /* static XS (Collectd_plugin_register_ds) */
/*
* Collectd::plugin_dispatch_values (name, values).
XSRETURN_EMPTY;
}
- log_debug ("Collectd::plugin_log: level = %i, message = \"%s\"",
- SvIV (ST (0)), SvPV_nolen (ST (1)));
plugin_log (SvIV (ST (0)), SvPV_nolen (ST (1)));
XSRETURN_YES;
} /* static XS (Collectd_plugin_log) */
-/*
- * Collectd::bootstrap ().
- */
-static XS (boot_Collectd)
-{
- HV *stash = NULL;
- char *file = __FILE__;
-
- struct {
- char name[64];
- SV *value;
- } consts[] =
- {
- { "Collectd::TYPE_INIT", Perl_newSViv (perl, PLUGIN_INIT) },
- { "Collectd::TYPE_READ", Perl_newSViv (perl, PLUGIN_READ) },
- { "Collectd::TYPE_WRITE", Perl_newSViv (perl, PLUGIN_WRITE) },
- { "Collectd::TYPE_SHUTDOWN", Perl_newSViv (perl, PLUGIN_SHUTDOWN) },
- { "Collectd::TYPE_LOG", Perl_newSViv (perl, PLUGIN_LOG) },
- { "Collectd::TYPE_DATASET", Perl_newSViv (perl, PLUGIN_DATASET) },
- { "Collectd::DS_TYPE_COUNTER", Perl_newSViv (perl, DS_TYPE_COUNTER) },
- { "Collectd::DS_TYPE_GAUGE", Perl_newSViv (perl, DS_TYPE_GAUGE) },
- { "Collectd::LOG_ERR", Perl_newSViv (perl, LOG_ERR) },
- { "Collectd::LOG_WARNING", Perl_newSViv (perl, LOG_WARNING) },
- { "Collectd::LOG_NOTICE", Perl_newSViv (perl, LOG_NOTICE) },
- { "Collectd::LOG_INFO", Perl_newSViv (perl, LOG_INFO) },
- { "Collectd::LOG_DEBUG", Perl_newSViv (perl, LOG_DEBUG) },
- { "", NULL }
- };
-
- int i = 0;
-
- dXSARGS;
-
- if ((1 > items) || (2 < items)) {
- log_err ("Usage: Collectd::bootstrap(name[, version])");
- XSRETURN_EMPTY;
- }
-
- XS_VERSION_BOOTCHECK;
-
- /* register API */
- for (i = 0; NULL != api[i].f; ++i)
- Perl_newXS (perl, api[i].name, api[i].f, file);
-
- stash = Perl_gv_stashpv (perl, "Collectd", 1);
-
- /* export "constants" */
- for (i = 0; NULL != consts[i].value; ++i)
- Perl_newCONSTSUB (perl, stash, consts[i].name, consts[i].value);
- XSRETURN_YES;
-} /* static XS (boot_Collectd) */
-
/*
* Interface to collectd.
*/
-static int perl_config (const char *key, const char *value)
-{
- assert (NULL != perl);
-
- log_debug ("perl_config: key = \"%s\", value=\"%s\"", key, value);
-
- if (0 == strcasecmp (key, "LoadPlugin")) {
- char module_name[DATA_MAX_NAME_LEN];
-
- if (get_module_name (module_name, sizeof (module_name), value)
- == NULL) {
- log_err ("Invalid module name %s", value);
- return (1);
- } /* if (get_module_name == NULL) */
-
- 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);
- }
- else if (0 == strcasecmp (key, "BaseName")) {
- log_debug ("perl_config: Setting plugin basename to \"%s\"", value);
- strncpy (base_name, value, sizeof (base_name));
- base_name[sizeof (base_name) - 1] = '\0';
- }
- else if (0 == strcasecmp (key, "IncludeDir")) {
- Perl_av_unshift (perl, GvAVn (PL_incgv), 1);
- Perl_av_store (perl, GvAVn (PL_incgv),
- 0, Perl_newSVpv (perl, value, strlen (value)));
- }
- else {
- return -1;
- }
- return 0;
-} /* static int perl_config (char *, char *) */
-
static int perl_init (void)
{
- assert (NULL != perl);
+ if (NULL == perl)
+ return 0;
PERL_SET_CONTEXT (perl);
return pplugin_call_all (PLUGIN_INIT);
static int perl_read (void)
{
- assert (NULL != perl);
+ if (NULL == perl)
+ return 0;
PERL_SET_CONTEXT (perl);
return pplugin_call_all (PLUGIN_READ);
static int perl_write (const data_set_t *ds, const value_list_t *vl)
{
- assert (NULL != perl);
+ if (NULL == perl)
+ return 0;
PERL_SET_CONTEXT (perl);
return pplugin_call_all (PLUGIN_WRITE, ds, vl);
static void perl_log (int level, const char *msg)
{
- assert (NULL != perl);
+ if (NULL == perl)
+ return;
PERL_SET_CONTEXT (perl);
pplugin_call_all (PLUGIN_LOG, level, msg);
static int perl_shutdown (void)
{
- int i = 0;
int ret = 0;
+ plugin_unregister_complex_config ("perl");
+
+ if (NULL == perl)
+ return 0;
+
plugin_unregister_log ("perl");
- plugin_unregister_config ("perl");
plugin_unregister_init ("perl");
plugin_unregister_read ("perl");
plugin_unregister_write ("perl");
- assert (NULL != perl);
-
PERL_SET_CONTEXT (perl);
ret = pplugin_call_all (PLUGIN_SHUTDOWN);
- for (i = 0; i < PLUGIN_TYPES; ++i) {
- if (0 < Perl_hv_iterinit (perl, plugins[i])) {
- char *k = NULL;
- I32 l = 0;
-
- while (NULL != Perl_hv_iternextsv (perl, plugins[i], &k, &l)) {
- pplugin_unregister (i, k);
- }
- }
-
- Perl_hv_undef (perl, plugins[i]);
- }
-
if (0 < Perl_hv_iterinit (perl, data_sets)) {
char *k = NULL;
I32 l = 0;
return ret;
} /* static void perl_shutdown (void) */
+/* bootstrap the Collectd module */
static void xs_init (pTHX)
{
- char *file = __FILE__;
+ HV *stash = NULL;
+ char *file = __FILE__;
- dXSUB_SYS;
+ int i = 0;
- /* build the Collectd module into the perl interpreter */
- Perl_newXS (perl, "Collectd::bootstrap", boot_Collectd, file);
+ dXSUB_SYS;
/* enable usage of Perl modules using shared libraries */
Perl_newXS (perl, "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);
+
+ stash = Perl_gv_stashpv (perl, "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));
return;
} /* static void xs_init (pTHX) */
-/*
- * Create the perl interpreter and register it with collectd.
- */
-void module_register (void)
+/* Initialize the global Perl interpreter. */
+static int init_pi (int argc, char **argv)
{
- char *embed_argv[] = { "", "-e", "bootstrap Collectd \""VERSION"\"", NULL };
- int embed_argc = 3;
+ if (NULL != perl)
+ return 0;
- int i = 0;
+ log_info ("Initializing Perl interpreter...");
+#if COLLECT_DEBUG
+ {
+ int i = 0;
- log_debug ("module_register: Registering perl plugin...");
+ for (i = 0; i < argc; ++i)
+ log_debug ("argv[%i] = \"%s\"", i, argv[i]);
+ }
+#endif /* COLLECT_DEBUG */
PERL_SYS_INIT3 (&argc, &argv, &environ);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
- if (0 != perl_parse (perl, xs_init, embed_argc, embed_argv, NULL)) {
+ if (0 != perl_parse (perl, xs_init, argc, argv, NULL)) {
log_err ("module_register: Unable to bootstrap Collectd.");
exit (1);
}
perl_run (perl);
- for (i = 0; i < PLUGIN_TYPES; ++i)
- plugins[i] = Perl_newHV (perl);
-
data_sets = Perl_newHV (perl);
plugin_register_log ("perl", perl_log);
- plugin_register_config ("perl", perl_config, config_keys, config_keys_num);
plugin_register_init ("perl", perl_init);
plugin_register_read ("perl", perl_read);
plugin_register_write ("perl", perl_write);
plugin_register_shutdown ("perl", perl_shutdown);
+ return 0;
+} /* static int init_pi (const char **, const int) */
+
+/*
+ * LoadPlugin "<Plugin>"
+ */
+static int perl_config_loadplugin (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)) {
+ }
+
+ value = ci->values[0].value.string;
+
+ if (NULL == get_module_name (module_name, sizeof (module_name), value)) {
+ log_err ("Invalid module name %s", value);
+ return (1);
+ }
+
+ init_pi (perl_argc, perl_argv);
+
+ 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);
+ return 0;
+} /* static int perl_config_loadplugin (oconfig_item_it *) */
+
+/*
+ * BaseName "<Name>"
+ */
+static int perl_config_basename (oconfig_item_t *ci)
+{
+ char *value = NULL;
+
+ if ((0 != ci->children_num) || (1 != ci->values_num)
+ || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+ }
+
+ value = ci->values[0].value.string;
+
+ log_debug ("perl_config: Setting plugin basename to \"%s\"", value);
+ strncpy (base_name, value, sizeof (base_name));
+ base_name[sizeof (base_name) - 1] = '\0';
+ return 0;
+} /* static int perl_config_basename (oconfig_item_it *) */
+
+/*
+ * EnableDebugger "<Package>"|""
+ */
+static int perl_config_enabledebugger (oconfig_item_t *ci)
+{
+ char *value = NULL;
+
+ if ((0 != ci->children_num) || (1 != ci->values_num)
+ || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+ }
+
+ value = ci->values[0].value.string;
+
+ perl_argv = (char **)realloc (perl_argv,
+ (++perl_argc + 1) * sizeof (char *));
+
+ if (NULL == perl_argv) {
+ log_err ("perl_config: Not enough memory.");
+ exit (3);
+ }
+
+ if ('\0' == value[0]) {
+ perl_argv[perl_argc - 1] = "-d";
+ }
+ else {
+ perl_argv[perl_argc - 1] = (char *)smalloc (strlen (value) + 4);
+ sstrncpy (perl_argv[perl_argc - 1], "-d:", 4);
+ sstrncpy (perl_argv[perl_argc - 1] + 3, value, strlen (value) + 1);
+ }
+
+ perl_argv[perl_argc] = NULL;
+ return 0;
+} /* static int perl_config_enabledebugger (oconfig_item_it *) */
+
+/*
+ * IncludeDir "<Dir>"
+ */
+static int perl_config_includedir (oconfig_item_t *ci)
+{
+ char *value = NULL;
+
+ if ((0 != ci->children_num) || (1 != ci->values_num)
+ || (OCONFIG_TYPE_STRING != ci->values[0].type)) {
+ }
+
+ value = ci->values[0].value.string;
+
+ perl_argv = (char **)realloc (perl_argv,
+ (++perl_argc + 1) * sizeof (char *));
+
+ if (NULL == perl_argv) {
+ log_err ("perl_config: Not enough memory.");
+ exit (3);
+ }
+
+ perl_argv[perl_argc - 1] = (char *)smalloc (strlen (value) + 3);
+ sstrncpy(perl_argv[perl_argc - 1], "-I", 3);
+ sstrncpy(perl_argv[perl_argc - 1] + 2, value, strlen (value) + 1);
+
+ perl_argv[perl_argc] = NULL;
+ return 0;
+} /* static int perl_config_includedir (oconfig_item_it *) */
+
+static int perl_config (oconfig_item_t *ci)
+{
+ int i = 0;
+
+ 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);
+ else if (0 == strcasecmp (c->key, "BaseName"))
+ perl_config_basename (c);
+ else if (0 == strcasecmp (c->key, "EnableDebugger"))
+ perl_config_enabledebugger (c);
+ else if (0 == strcasecmp (c->key, "IncludeDir"))
+ perl_config_includedir (c);
+ else
+ log_warn ("Ignoring unknown config key \"%s\".", c->key);
+ }
+ return 0;
+} /* static int perl_config (oconfig_item_t *) */
+
+void module_register (void)
+{
+ perl_argc = 4;
+ perl_argv = (char **)smalloc ((perl_argc + 1) * sizeof (char *));
+
+ /* default options for the Perl interpreter */
+ perl_argv[0] = "";
+ perl_argv[1] = "-MCollectd";
+ perl_argv[2] = "-e";
+ perl_argv[3] = "1";
+ perl_argv[4] = NULL;
+
+ plugin_register_complex_config ("perl", perl_config);
return;
} /* void module_register (void) */
char *type; /* used to find the data_set */
int is_table;
instance_t instance;
+ char *instance_prefix;
oid_t *values;
int values_len;
+ double scale;
+ double shift;
struct data_definition_s *next;
};
typedef struct data_definition_s data_definition_t;
* ! +-> csnmp_config_add_data_type
* ! +-> csnmp_config_add_data_table
* ! +-> csnmp_config_add_data_instance
+ * ! +-> csnmp_config_add_data_instance_prefix
* ! +-> csnmp_config_add_data_values
* +-> csnmp_config_add_host
* +-> csnmp_config_add_host_address
return (-1);
}
- if (dd->type != NULL)
- free (dd->type);
-
+ sfree (dd->type);
dd->type = strdup (ci->values[0].value.string);
if (dd->type == NULL)
return (-1);
return (0);
} /* int csnmp_config_add_data_instance */
+static int csnmp_config_add_data_instance_prefix (data_definition_t *dd,
+ oconfig_item_t *ci)
+{
+ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ WARNING ("snmp plugin: `InstancePrefix' needs exactly one string argument.");
+ return (-1);
+ }
+
+ if (!dd->is_table)
+ {
+ WARNING ("snmp plugin: data %s: InstancePrefix is ignored when `Table' "
+ "is set to `false'.", dd->name);
+ return (-1);
+ }
+
+ sfree (dd->instance_prefix);
+ dd->instance_prefix = strdup (ci->values[0].value.string);
+ if (dd->instance_prefix == NULL)
+ return (-1);
+
+ return (0);
+} /* int csnmp_config_add_data_instance_prefix */
+
static int csnmp_config_add_data_values (data_definition_t *dd, oconfig_item_t *ci)
{
int i;
return (-1);
}
- if (dd->values != NULL)
- free (dd->values);
+ sfree (dd->values);
+ dd->values_len = 0;
dd->values = (oid_t *) malloc (sizeof (oid_t) * ci->values_num);
if (dd->values == NULL)
return (-1);
return (0);
} /* int csnmp_config_add_data_instance */
+static int csnmp_config_add_data_shift (data_definition_t *dd, oconfig_item_t *ci)
+{
+ if ((ci->values_num != 1)
+ || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+ {
+ WARNING ("snmp plugin: The `Scale' config option needs exactly one number argument.");
+ return (-1);
+ }
+
+ dd->shift = ci->values[0].value.number;
+
+ return (0);
+} /* int csnmp_config_add_data_shift */
+
+static int csnmp_config_add_data_scale (data_definition_t *dd, oconfig_item_t *ci)
+{
+ if ((ci->values_num != 1)
+ || (ci->values[0].type != OCONFIG_TYPE_NUMBER))
+ {
+ WARNING ("snmp plugin: The `Scale' config option needs exactly one number argument.");
+ return (-1);
+ }
+
+ dd->scale = ci->values[0].value.number;
+
+ return (0);
+} /* int csnmp_config_add_data_scale */
+
static int csnmp_config_add_data (oconfig_item_t *ci)
{
data_definition_t *dd;
free (dd);
return (-1);
}
+ dd->scale = 1.0;
+ dd->shift = 0.0;
for (i = 0; i < ci->children_num; i++)
{
status = csnmp_config_add_data_table (dd, option);
else if (strcasecmp ("Instance", option->key) == 0)
status = csnmp_config_add_data_instance (dd, option);
+ else if (strcasecmp ("InstancePrefix", option->key) == 0)
+ status = csnmp_config_add_data_instance_prefix (dd, option);
else if (strcasecmp ("Values", option->key) == 0)
status = csnmp_config_add_data_values (dd, option);
+ else if (strcasecmp ("Shift", option->key) == 0)
+ status = csnmp_config_add_data_shift (dd, option);
+ else if (strcasecmp ("Scale", option->key) == 0)
+ status = csnmp_config_add_data_scale (dd, option);
else
{
WARNING ("snmp plugin: Option `%s' not allowed here.", option->key);
if (status != 0)
{
sfree (dd->name);
+ sfree (dd->instance_prefix);
sfree (dd->values);
sfree (dd);
return (-1);
}
} /* void csnmp_host_open_session */
-static value_t csnmp_value_list_to_value (struct variable_list *vl, int type)
+static value_t csnmp_value_list_to_value (struct variable_list *vl, int type,
+ double scale, double shift)
{
value_t ret;
uint64_t temp = 0;
{
ret.gauge = NAN;
if (defined != 0)
- ret.gauge = temp;
+ ret.gauge = (scale * temp) + shift;
}
return (ret);
} /* value_t csnmp_value_list_to_value */
+/* Returns true if all OIDs have left their subtree */
+static int csnmp_check_res_left_subtree (const host_definition_t *host,
+ const data_definition_t *data,
+ struct snmp_pdu *res)
+{
+ struct variable_list *vb;
+ int num_checked;
+ int num_left_subtree;
+ int i;
+
+ vb = res->variables;
+ if (vb == NULL)
+ return (-1);
+
+ num_checked = 0;
+ num_left_subtree = 0;
+
+ /* check all the variables and count how many have left their subtree */
+ for (vb = res->variables, i = 0;
+ (vb != NULL) && (i < data->values_len);
+ vb = vb->next_variable, i++)
+ {
+ num_checked++;
+ if (snmp_oid_ncompare (data->values[i].oid,
+ data->values[i].oid_len,
+ vb->name, vb->name_length,
+ data->values[i].oid_len) != 0)
+ num_left_subtree++;
+ }
+
+ /* check if enough variables have been returned */
+ if (i < data->values_len)
+ {
+ ERROR ("snmp plugin: host %s: Expected %i variables, but got only %i",
+ host->name, data->values_len, i);
+ return (-1);
+ }
+
+ if (data->instance.oid.oid_len > 0)
+ {
+ if (vb == NULL)
+ {
+ ERROR ("snmp plugin: host %s: Expected one more variable for "
+ "the instance..");
+ return (-1);
+ }
+
+ num_checked++;
+ if (snmp_oid_ncompare (data->instance.oid.oid,
+ data->instance.oid.oid_len,
+ vb->name, vb->name_length,
+ data->instance.oid.oid_len) != 0)
+ num_left_subtree++;
+ }
+
+ DEBUG ("snmp plugin: csnmp_check_res_left_subtree: %i of %i variables have "
+ "left their subtree",
+ num_left_subtree, num_checked);
+ if (num_left_subtree >= num_checked)
+ return (1);
+ return (0);
+} /* int csnmp_check_res_left_subtree */
+
+static int csnmp_instance_list_add (csnmp_list_instances_t **head,
+ csnmp_list_instances_t **tail,
+ const struct snmp_pdu *res)
+{
+ csnmp_list_instances_t *il;
+ struct variable_list *vb;
+
+ /* Set vb on the last variable */
+ for (vb = res->variables;
+ (vb != NULL) && (vb->next_variable != NULL);
+ vb = vb->next_variable)
+ /* do nothing */;
+ if (vb == NULL)
+ return (-1);
+
+ il = (csnmp_list_instances_t *) malloc (sizeof (csnmp_list_instances_t));
+ if (il == NULL)
+ {
+ ERROR ("snmp plugin: malloc failed.");
+ return (-1);
+ }
+ il->subid = vb->name[vb->name_length - 1];
+ il->next = NULL;
+
+ /* Get instance name */
+ if ((vb->type == ASN_OCTET_STR) || (vb->type == ASN_BIT_STR))
+ {
+ char *ptr;
+ size_t instance_len;
+
+ instance_len = sizeof (il->instance) - 1;
+ if (instance_len > vb->val_len)
+ instance_len = vb->val_len;
+
+ strncpy (il->instance, (char *) ((vb->type == ASN_OCTET_STR)
+ ? vb->val.string
+ : vb->val.bitstring),
+ instance_len);
+ il->instance[instance_len] = '\0';
+
+ for (ptr = il->instance; *ptr != '\0'; ptr++)
+ {
+ if ((*ptr > 0) && (*ptr < 32))
+ *ptr = ' ';
+ else if (*ptr == '/')
+ *ptr = '_';
+ }
+ DEBUG ("snmp plugin: il->instance = `%s';", il->instance);
+ }
+ else
+ {
+ value_t val = csnmp_value_list_to_value (vb, DS_TYPE_COUNTER, 1.0, 0.0);
+ snprintf (il->instance, sizeof (il->instance),
+ "%llu", val.counter);
+ }
+ il->instance[sizeof (il->instance) - 1] = '\0';
+
+ /* TODO: Debugging output */
+
+ if (*head == NULL)
+ *head = il;
+ else
+ (*tail)->next = il;
+ *tail = il;
+
+ return (0);
+} /* int csnmp_instance_list_add */
+
static int csnmp_dispatch_table (host_definition_t *host, data_definition_t *data,
csnmp_list_instances_t *instance_list,
csnmp_table_values_t **value_table)
csnmp_table_values_t **value_table_ptr;
int i;
+ oid subid;
+ int have_more;
ds = plugin_get_ds (data->type);
if (!ds)
}
assert (ds->ds_num == data->values_len);
+ instance_list_ptr = instance_list;
+
value_table_ptr = (csnmp_table_values_t **) malloc (sizeof (csnmp_table_values_t *)
* data->values_len);
if (value_table_ptr == NULL)
vl.values = (value_t *) malloc (sizeof (value_t) * vl.values_len);
if (vl.values == NULL)
{
+ ERROR ("snmp plugin: malloc failed.");
sfree (value_table_ptr);
return (-1);
}
vl.interval = host->interval;
vl.time = time (NULL);
- for (instance_list_ptr = instance_list;
- instance_list_ptr != NULL;
- instance_list_ptr = instance_list_ptr->next)
+ subid = 0;
+ have_more = 1;
+
+ while (have_more != 0)
{
- strncpy (vl.type_instance, instance_list_ptr->instance, sizeof (vl.type_instance));
- vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+ if (instance_list != NULL)
+ {
+ while ((instance_list_ptr != NULL)
+ && (instance_list_ptr->subid < subid))
+ instance_list_ptr = instance_list_ptr->next;
+
+ if (instance_list_ptr == NULL)
+ {
+ have_more = 0;
+ continue;
+ }
+ else if (instance_list_ptr->subid > subid)
+ {
+ subid = instance_list_ptr->subid;
+ continue;
+ }
+ } /* if (instance_list != NULL) */
for (i = 0; i < data->values_len; i++)
{
while ((value_table_ptr[i] != NULL)
- && (value_table_ptr[i]->subid < instance_list_ptr->subid))
+ && (value_table_ptr[i]->subid < subid))
value_table_ptr[i] = value_table_ptr[i]->next;
- if ((value_table_ptr[i] == NULL)
- || (value_table_ptr[i]->subid != instance_list_ptr->subid))
- break;
- vl.values[i] = value_table_ptr[i]->value;
- } /* for (data->values_len) */
- /* If the for-loop was aborted early, not all subid's match. */
+ if (value_table_ptr[i] == NULL)
+ {
+ have_more = 0;
+ break;
+ }
+ else if (value_table_ptr[i]->subid > subid)
+ {
+ subid = value_table_ptr[i]->subid;
+ break;
+ }
+ } /* for (i = 0; i < columns; i++) */
+ /* The subid has been increased - start scanning from the beginning
+ * again.. */
if (i < data->values_len)
- {
- DEBUG ("snmp plugin: host = %s; data = %s; i = %i; "
- "Skipping SUBID %i",
- host->name, data->name, i, instance_list_ptr->subid);
continue;
+
+ /* if we reach this line, all value_table_ptr[i] are non-NULL and are set
+ * to the same subid. instance_list_ptr is either NULL or points to the
+ * same subid, too. */
+#if COLLECT_DEBUG
+ for (i = 1; i < data->values_len; i++)
+ {
+ assert (value_table_ptr[i] != NULL);
+ assert (value_table_ptr[i-1]->subid == value_table_ptr[i]->subid);
+ }
+ assert ((instance_list_ptr == NULL)
+ || (instance_list_ptr->subid == value_table_ptr[0]->subid));
+#endif
+
+ {
+ char temp[DATA_MAX_NAME_LEN];
+
+ if (instance_list_ptr == NULL)
+ snprintf (temp, sizeof (temp), "%u",
+ (uint32_t) subid);
+ else
+ strncpy (temp, instance_list_ptr->instance,
+ sizeof (temp));
+ temp[sizeof (temp) - 1] = '\0';
+
+ if (data->instance_prefix == NULL)
+ strncpy (vl.type_instance, temp, sizeof (vl.type_instance));
+ else
+ snprintf (vl.type_instance, sizeof (vl.type_instance), "%s%s",
+ data->instance_prefix, temp);
+ vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
}
+ for (i = 0; i < data->values_len; i++)
+ vl.values[i] = value_table_ptr[i]->value;
+
/* If we get here `vl.type_instance' and all `vl.values' have been set */
plugin_dispatch_values (data->type, &vl);
- } /* for (instance_list) */
+
+ subid++;
+ } /* while (have_more != 0) */
sfree (vl.values);
sfree (value_table_ptr);
oid_list_len = data->values_len + 1;
oid_list = (oid_t *) malloc (sizeof (oid_t) * (oid_list_len));
if (oid_list == NULL)
+ {
+ ERROR ("snmp plugin: csnmp_read_table: malloc failed.");
return (-1);
- memcpy (oid_list, &data->instance.oid, sizeof (oid_t));
- for (i = 0; i < data->values_len; i++)
- memcpy (oid_list + (i + 1), data->values + i, sizeof (oid_t));
+ }
+ memcpy (oid_list, data->values, data->values_len * sizeof (oid_t));
+ if (data->instance.oid.oid_len > 0)
+ memcpy (oid_list + data->values_len, &data->instance.oid, sizeof (oid_t));
+ else
+ oid_list_len--;
/* Allocate the `value_table' */
value_table = (csnmp_table_values_t **) malloc (sizeof (csnmp_table_values_t *)
* 2 * data->values_len);
if (value_table == NULL)
{
+ ERROR ("snmp plugin: csnmp_read_table: malloc failed.");
sfree (oid_list);
return (-1);
}
status = 0;
while (status == 0)
{
- csnmp_list_instances_t *il;
-
req = snmp_pdu_create (SNMP_MSG_GETNEXT);
if (req == NULL)
{
break;
}
- /* Check if we left the subtree */
- if (snmp_oid_ncompare (data->instance.oid.oid, data->instance.oid.oid_len,
- vb->name, vb->name_length,
- data->instance.oid.oid_len) != 0)
- break;
-
- /* Allocate a new `csnmp_list_instances_t', insert the instance name and
- * add it to the list */
- il = (csnmp_list_instances_t *) malloc (sizeof (csnmp_list_instances_t));
- if (il == NULL)
- {
- status = -1;
+ /* Check if all values (and possibly the instance) have left their
+ * subtree */
+ if (csnmp_check_res_left_subtree (host, data, res) != 0)
break;
- }
- il->subid = vb->name[vb->name_length - 1];
- il->next = NULL;
- /* Get instance name */
- if ((vb->type == ASN_OCTET_STR) || (vb->type == ASN_BIT_STR))
+ /* if an instance-OID is configured.. */
+ if (data->instance.oid.oid_len > 0)
{
- char *ptr;
- size_t instance_len;
-
- instance_len = sizeof (il->instance) - 1;
- if (instance_len > vb->val_len)
- instance_len = vb->val_len;
-
- strncpy (il->instance, (char *) ((vb->type == ASN_OCTET_STR)
- ? vb->val.string
- : vb->val.bitstring),
- instance_len);
- il->instance[instance_len] = '\0';
-
- for (ptr = il->instance; *ptr != '\0'; ptr++)
+ /* Allocate a new `csnmp_list_instances_t', insert the instance name and
+ * add it to the list */
+ if (csnmp_instance_list_add (&instance_list, &instance_list_ptr,
+ res) != 0)
{
- if ((*ptr > 0) && (*ptr < 32))
- *ptr = ' ';
- else if (*ptr == '/')
- *ptr = '_';
+ ERROR ("snmp plugin: csnmp_instance_list_add failed.");
+ status = -1;
+ break;
}
- DEBUG ("snmp plugin: il->instance = `%s';", il->instance);
- }
- else
- {
- value_t val = csnmp_value_list_to_value (vb, DS_TYPE_COUNTER);
- snprintf (il->instance, sizeof (il->instance),
- "%llu", val.counter);
- }
- il->instance[sizeof (il->instance) - 1] = '\0';
- DEBUG ("snmp plugin: data = `%s'; il->instance = `%s';",
- data->name, il->instance);
-
- if (instance_list_ptr == NULL)
- instance_list = il;
- else
- instance_list_ptr->next = il;
- instance_list_ptr = il;
-
- /* Copy OID to oid_list[0] */
- memcpy (oid_list[0].oid, vb->name, sizeof (oid) * vb->name_length);
- oid_list[0].oid_len = vb->name_length;
-
- for (i = 0; i < data->values_len; i++)
- {
- csnmp_table_values_t *vt;
- vb = vb->next_variable;
+ /* Set vb on the last variable */
+ for (vb = res->variables;
+ (vb != NULL) && (vb->next_variable != NULL);
+ vb = vb->next_variable)
+ /* do nothing */;
if (vb == NULL)
{
status = -1;
break;
}
+ /* Copy OID to oid_list[data->values_len] */
+ memcpy (oid_list[data->values_len].oid, vb->name,
+ sizeof (oid) * vb->name_length);
+ oid_list[data->values_len].oid_len = vb->name_length;
+ }
+
+ for (vb = res->variables, i = 0;
+ (vb != NULL) && (i < data->values_len);
+ vb = vb->next_variable, i++)
+ {
+ csnmp_table_values_t *vt;
+
/* Check if we left the subtree */
if (snmp_oid_ncompare (data->values[i].oid,
data->values[i].oid_len,
if ((value_table_ptr[i] != NULL)
&& (vb->name[vb->name_length - 1] <= value_table_ptr[i]->subid))
{
- DEBUG ("snmp plugin: host = %s; data = %s; i = %i; SUBID is not increasing.",
+ DEBUG ("snmp plugin: host = %s; data = %s; i = %i; "
+ "SUBID is not increasing.",
host->name, data->name, i);
continue;
}
vt = (csnmp_table_values_t *) malloc (sizeof (csnmp_table_values_t));
- if (vt != NULL)
+ if (vt == NULL)
{
- vt->subid = vb->name[vb->name_length - 1];
- vt->value = csnmp_value_list_to_value (vb, ds->ds[i].type);
- vt->next = NULL;
-
- if (value_table_ptr[i] == NULL)
- value_table[i] = vt;
- else
- value_table_ptr[i]->next = vt;
- value_table_ptr[i] = vt;
+ ERROR ("snmp plugin: malloc failed.");
+ status = -1;
+ break;
}
+ vt->subid = vb->name[vb->name_length - 1];
+ vt->value = csnmp_value_list_to_value (vb, ds->ds[i].type,
+ data->scale, data->shift);
+ vt->next = NULL;
+
+ if (value_table_ptr[i] == NULL)
+ value_table[i] = vt;
+ else
+ value_table_ptr[i]->next = vt;
+ value_table_ptr[i] = vt;
+
/* Copy OID to oid_list[i + 1] */
- memcpy (oid_list[i + 1].oid, vb->name, sizeof (oid) * vb->name_length);
- oid_list[i + 1].oid_len = vb->name_length;
+ memcpy (oid_list[i].oid, vb->name, sizeof (oid) * vb->name_length);
+ oid_list[i].oid_len = vb->name_length;
} /* for (i = data->values_len) */
if (res != NULL)
for (i = 0; i < data->values_len; i++)
if (snmp_oid_compare (data->values[i].oid, data->values[i].oid_len,
vb->name, vb->name_length) == 0)
- vl.values[i] = csnmp_value_list_to_value (vb, ds->ds[i].type);
+ vl.values[i] = csnmp_value_list_to_value (vb, ds->ds[i].type,
+ data->scale, data->shift);
} /* for (res->variables) */
snmp_free_pdu (res);
--- /dev/null
+/**
+ * collectd - src/tcpconns.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"
+
+#if !KERNEL_LINUX
+# error "No applicable input method."
+#endif
+
+#define TCP_STATE_LISTEN 10
+#define TCP_STATE_MAX 11
+
+#define PORT_COLLECT_LOCAL 0x01
+#define PORT_COLLECT_REMOTE 0x02
+#define PORT_IS_LISTENING 0x04
+
+typedef struct port_entry_s
+{
+ uint16_t port;
+ uint16_t flags;
+ uint32_t count_local[TCP_STATE_MAX + 1];
+ uint32_t count_remote[TCP_STATE_MAX + 1];
+ struct port_entry_s *next;
+} port_entry_t;
+
+static const char *config_keys[] =
+{
+ "ListeningPorts",
+ "LocalPort",
+ "RemotePort"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static const char *tcp_state[] =
+{
+ "", /* 0 */
+ "ESTABLISHED",
+ "SYN_SENT",
+ "SYN_RECV",
+ "FIN_WAIT1",
+ "FIN_WAIT2",
+ "TIME_WAIT",
+ "CLOSE",
+ "CLOSE_WAIT",
+ "LAST_ACK",
+ "LISTEN", /* 10 */
+ "CLOSING"
+};
+
+static int port_collect_listening = 0;
+static port_entry_t *port_list_head = NULL;
+
+static void conn_submit_port_entry (port_entry_t *pe)
+{
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+ int i;
+
+ vl.values = values;
+ vl.values_len = 1;
+ vl.time = time (NULL);
+ strcpy (vl.host, hostname_g);
+ strcpy (vl.plugin, "tcpconns");
+
+ if (((port_collect_listening != 0) && (pe->flags & PORT_IS_LISTENING))
+ || (pe->flags & PORT_COLLECT_LOCAL))
+ {
+ snprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
+ "%hu-local", pe->port);
+ vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
+
+ for (i = 1; i <= TCP_STATE_MAX; i++)
+ {
+ vl.values[0].gauge = pe->count_local[i];
+
+ strncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
+ vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+
+ plugin_dispatch_values ("tcp_connections", &vl);
+ }
+ }
+
+ if (pe->flags & PORT_COLLECT_REMOTE)
+ {
+ snprintf (vl.plugin_instance, sizeof (vl.plugin_instance),
+ "%hu-remote", pe->port);
+ vl.plugin_instance[sizeof (vl.plugin_instance) - 1] = '\0';
+
+ for (i = 1; i <= TCP_STATE_MAX; i++)
+ {
+ vl.values[0].gauge = pe->count_remote[i];
+
+ strncpy (vl.type_instance, tcp_state[i], sizeof (vl.type_instance));
+ vl.type_instance[sizeof (vl.type_instance) - 1] = '\0';
+
+ plugin_dispatch_values ("tcp_connections", &vl);
+ }
+ }
+} /* void conn_submit */
+
+static void conn_submit_all (void)
+{
+ port_entry_t *pe;
+
+ for (pe = port_list_head; pe != NULL; pe = pe->next)
+ conn_submit_port_entry (pe);
+} /* void conn_submit_all */
+
+static port_entry_t *conn_get_port_entry (uint16_t port, int create)
+{
+ port_entry_t *ret;
+
+ ret = port_list_head;
+ while (ret != NULL)
+ {
+ if (ret->port == port)
+ break;
+ ret = ret->next;
+ }
+
+ if ((ret == NULL) && (create != 0))
+ {
+ ret = (port_entry_t *) malloc (sizeof (port_entry_t));
+ if (ret == NULL)
+ return (NULL);
+ memset (ret, '\0', sizeof (port_entry_t));
+
+ ret->port = port;
+ ret->next = port_list_head;
+ port_list_head = ret;
+ }
+
+ return (ret);
+} /* port_entry_t *conn_get_port_entry */
+
+/* Removes ports that were added automatically due to the `ListeningPorts'
+ * setting but which are no longer listening. */
+static void conn_reset_port_entry (void)
+{
+ port_entry_t *prev = NULL;
+ port_entry_t *pe = port_list_head;
+
+ while (pe != NULL)
+ {
+ /* If this entry was created while reading the files (ant not when handling
+ * the configuration) remove it now. */
+ if ((pe->flags & (PORT_COLLECT_LOCAL
+ | PORT_COLLECT_REMOTE
+ | PORT_IS_LISTENING)) == 0)
+ {
+ port_entry_t *next = pe->next;
+
+ DEBUG ("tcpconns plugin: Removing temporary entry "
+ "for listening port %hu", pe->port);
+
+ if (prev == NULL)
+ port_list_head = next;
+ else
+ prev->next = next;
+
+ sfree (pe);
+ pe = next;
+
+ continue;
+ }
+
+ memset (pe->count_local, '\0', sizeof (pe->count_local));
+ memset (pe->count_remote, '\0', sizeof (pe->count_remote));
+ pe->flags &= ~PORT_IS_LISTENING;
+
+ pe = pe->next;
+ }
+} /* void conn_reset_port_entry */
+
+static int conn_handle_ports (uint16_t port_local, uint16_t port_remote, uint8_t state)
+{
+ port_entry_t *pe = NULL;
+
+ if ((state == 0) || (state > TCP_STATE_MAX))
+ {
+ NOTICE ("tcpconns plugin: Ignoring connection with unknown state 0x%02x.",
+ state);
+ return (-1);
+ }
+
+ /* Listening sockets */
+ if ((state == TCP_STATE_LISTEN) && (port_collect_listening != 0))
+ {
+ pe = conn_get_port_entry (port_local, 1 /* create */);
+ if (pe != NULL)
+ pe->flags |= PORT_IS_LISTENING;
+ }
+
+ DEBUG ("tcpconns plugin: Connection %hu <-> %hu (%s)",
+ port_local, port_remote, tcp_state[state]);
+
+ pe = conn_get_port_entry (port_local, 0 /* no create */);
+ if (pe != NULL)
+ pe->count_local[state]++;
+
+ pe = conn_get_port_entry (port_remote, 0 /* no create */);
+ if (pe != NULL)
+ pe->count_remote[state]++;
+
+ return (0);
+} /* int conn_handle_ports */
+
+static int conn_handle_line (char *buffer)
+{
+ char *fields[32];
+ int fields_len;
+
+ char *endptr;
+
+ char *port_local_str;
+ char *port_remote_str;
+ uint16_t port_local;
+ uint16_t port_remote;
+
+ uint8_t state;
+
+ int buffer_len = strlen (buffer);
+
+ while ((buffer_len > 0) && (buffer[buffer_len - 1] < 32))
+ buffer[--buffer_len] = '\0';
+ if (buffer_len <= 0)
+ return (-1);
+
+ fields_len = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+ if (fields_len < 12)
+ {
+ DEBUG ("tcpconns plugin: Got %i fields, expected at least 12.", fields_len);
+ return (-1);
+ }
+
+ port_local_str = strchr (fields[1], ':');
+ port_remote_str = strchr (fields[2], ':');
+
+ if ((port_local_str == NULL) || (port_remote_str == NULL))
+ return (-1);
+ port_local_str++;
+ port_remote_str++;
+ if ((*port_local_str == '\0') || (*port_remote_str == '\0'))
+ return (-1);
+
+ endptr = NULL;
+ port_local = (uint16_t) strtol (port_local_str, &endptr, 16);
+ if ((endptr == NULL) || (*endptr != '\0'))
+ return (-1);
+
+ endptr = NULL;
+ port_remote = (uint16_t) strtol (port_remote_str, &endptr, 16);
+ if ((endptr == NULL) || (*endptr != '\0'))
+ return (-1);
+
+ endptr = NULL;
+ state = (uint8_t) strtol (fields[3], &endptr, 16);
+ if ((endptr == NULL) || (*endptr != '\0'))
+ return (-1);
+
+ return (conn_handle_ports (port_local, port_remote, state));
+} /* int conn_handle_line */
+
+static int conn_read_file (const char *file)
+{
+ FILE *fh;
+ char buffer[1024];
+
+ fh = fopen (file, "r");
+ if (fh == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("tcpconns plugin: fopen (%s) failed: %s",
+ file, sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ while (fgets (buffer, sizeof (buffer), fh) != NULL)
+ {
+ conn_handle_line (buffer);
+ } /* while (fgets) */
+
+ fclose (fh);
+
+ return (0);
+} /* int conn_read_file */
+
+static int conn_config (const char *key, const char *value)
+{
+ if (strcasecmp (key, "ListeningPorts") == 0)
+ {
+ if ((strcasecmp (value, "Yes") == 0)
+ || (strcasecmp (value, "True") == 0)
+ || (strcasecmp (value, "On") == 0))
+ port_collect_listening = 1;
+ else
+ port_collect_listening = 0;
+ }
+ else if ((strcasecmp (key, "LocalPort") == 0)
+ || (strcasecmp (key, "RemotePort") == 0))
+ {
+ port_entry_t *pe;
+ int port = atoi (value);
+
+ if ((port < 1) || (port > 65535))
+ {
+ ERROR ("tcpconns plugin: Invalid port: %i", port);
+ return (1);
+ }
+
+ pe = conn_get_port_entry ((uint16_t) port, 1 /* create */);
+ if (pe == NULL)
+ {
+ ERROR ("tcpconns plugin: conn_get_port_entry failed.");
+ return (1);
+ }
+
+ if (strcasecmp (key, "LocalPort") == 0)
+ pe->flags |= PORT_COLLECT_LOCAL;
+ else
+ pe->flags |= PORT_COLLECT_REMOTE;
+ }
+ else
+ {
+ return (-1);
+ }
+
+ return (0);
+} /* int conn_config */
+
+static int conn_init (void)
+{
+ if (port_list_head == NULL)
+ port_collect_listening = 1;
+
+ return (0);
+} /* int conn_init */
+
+static int conn_read (void)
+{
+ conn_reset_port_entry ();
+
+ conn_read_file ("/proc/net/tcp");
+ conn_read_file ("/proc/net/tcp6");
+
+ conn_submit_all ();
+
+ return (0);
+} /* int conn_read */
+
+void module_register (void)
+{
+ plugin_register_config ("tcpconns", conn_config,
+ config_keys, config_keys_num);
+ plugin_register_init ("tcpconns", conn_init);
+ plugin_register_read ("tcpconns", conn_read);
+} /* void module_register */
+
+/*
+ * vim: set shiftwidth=2 softtabstop=2 tabstop=8 :
+ */
apache_scoreboard count:GAUGE:0:65535
bitrate value:GAUGE:0:4294967295
charge value:GAUGE:0:U
+connections value:COUNTER:0:U
counter value:COUNTER:U:U
cpu value:COUNTER:0:4294967295
cpufreq value:GAUGE:0:U
ps_rss value:GAUGE:0:9223372036854775807
ps_state value:GAUGE:0:65535
spam_score value:GAUGE:U:U
+tcp_connections value:GAUGE:0:4294967295
temperature value:GAUGE:-273.15:U
time_dispersion seconds:GAUGE:-1000000:1000000
time_offset seconds:GAUGE:-1000000:1000000