author | Florian Forster <octo@noris.net> | |
Tue, 29 Apr 2008 15:44:26 +0000 (17:44 +0200) | ||
committer | Florian Forster <octo@noris.net> | |
Tue, 29 Apr 2008 15:44:26 +0000 (17:44 +0200) |
48 files changed:
index bd5daa42b0a6a94a1948fdd4792dcb50e46cacad..2004dff7727e32208b5e9fc0e3132296fece82e1 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
tape plugin by:
Scott Garrett <sgarrett at technomancer.com>
+teamspeak2 plugin by:
+ Stefan Hacker <stefan.hacker at web.de>
+
users plugin by:
Sebastian Harl <sh at tokkee.org>
index 8c0be1dad000f6caf39a35cfff75d43200c2b0ee..904e7c73a7692ed327a05418f211595e30d73cc9 100644 (file)
--- a/README
+++ b/README
Sensors in Macs running Mac OS X / Darwin: Temperature, fanspeed and
voltage sensors.
+ - ascent
+ Statistics about Ascent, a free server for the game `World of Warcraft'.
+
- battery
Batterycharge, -current and voltage of ACPI and PMU based laptop
batteries.
- swap
Pages swapped out onto harddisk or whatever is called `swap' by the OS..
+ - tail
+ Follows (tails) logfiles, parses them by lines and submits matched
+ values.
+
- tape
Bytes and operations read and written on tape devices. Solaris only.
- users
Users currently logged in.
+ - vmem
+ Virtual memory statistics, e. g. the number of page-ins/-outs or the
+ number of pagefaults.
+
- vserver
System resources used by Linux VServers.
See <http://linux-vserver.org/>.
platforms.
* libcurl (optional)
- If you want to use the `apache' and/or `nginx' plugins.
+ If you want to use the `apache', `ascent', or `nginx' plugin.
* libhal (optional)
If present, the uuid plugin will check for UUID from HAL.
Collect statistics from virtual machines.
* libxml2 (optional)
- Parse XML data provided by libvirt.
+ Parse XML data. This is needed for the `ascent' and `libvirt' plugins.
Configuring / Compiling / Installing
index 8749c1abff28e3fd1ee2880535a765b5911d5dae..f21ebfe9f285dc83060ba114c94f70996f4a3980 100644 (file)
return;
} # putnotif
+=item I<$obj>-E<gt>B<flush> (B<timeout> =E<gt> I<$timeout>, B<plugins> =E<gt> [...]);
+
+Flush cached data.
+
+Valid options are:
+
+=over 4
+
+=item B<timeout>
+
+If this option is specified, only data older than I<$timeout> seconds is
+flushed.
+
+=item B<plugins>
+
+If this option is specified, only the selected plugins will be flushed.
+
+=back
+
+=cut
+
+sub flush
+{
+ my $obj = shift;
+ my %args = @_;
+
+ my $fh = $obj->{'sock'} or confess;
+
+ my $status = 0;
+ my $msg = "FLUSH";
+
+ if ($args{'timeout'})
+ {
+ $msg .= " timeout=" . $args{'timeout'};
+ }
+
+ if ($args{'plugins'})
+ {
+ foreach my $plugin (@{$args{'plugins'}})
+ {
+ $msg .= " plugin=" . $plugin;
+ }
+ }
+
+ $msg .= "\n";
+
+ send ($fh, $msg, 0) or confess ("send: $!");
+ $msg = undef;
+ recv ($fh, $msg, 1024, 0) or confess ("recv: $!");
+
+ ($status, $msg) = split (' ', $msg, 2);
+ return (1) if ($status == 0);
+
+ $obj->{'error'} = $msg;
+ return;
+}
+
=item I<$obj>-E<gt>destroy ();
Closes the socket before the object is destroyed. This function is also
diff --git a/configure.in b/configure.in
index 9e6557c474d967d8b1c7fb3a2f489056134df073..7cd369980e9bbd3c27b38e8c97e4874d208c5e76 100644 (file)
--- a/configure.in
+++ b/configure.in
fi
AM_CONDITIONAL(BUILD_WITH_LIBNETLINK, test "x$with_libnetlink" = "xyes")
+with_libopenipmipthread="yes"
+with_libopenipmipthread_cflags=""
+with_libopenipmipthread_libs=""
+
+AC_MSG_CHECKING([for pkg-config])
+temp_result="no"
+if test "x$PKG_CONFIG" = "x"
+then
+ with_libopenipmipthread="no"
+ temp_result="no"
+else
+ temp_result="$PKG_CONFIG"
+fi
+AC_MSG_RESULT([$temp_result])
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+ AC_MSG_CHECKING([for libOpenIPMIpthread])
+ $PKG_CONFIG --exists OpenIPMIpthread 2>/dev/null
+ if test "$?" != "0"
+ then
+ with_libopenipmipthread="no ($PKG_CONFIG doesn't know OpenIPMIpthread)"
+ fi
+ AC_MSG_RESULT([$with_libopenipmipthread])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+ AC_MSG_CHECKING([for libOpenIPMIpthread CFLAGS])
+ temp_result="`$PKG_CONFIG --cflags OpenIPMIpthread`"
+ if test "$?" = "0"
+ then
+ with_libopenipmipthread_cflags="$temp_result"
+ else
+ with_libopenipmipthread="no ($PKG_CONFIG --cflags OpenIPMIpthread failed)"
+ temp_result="$PKG_CONFIG --cflags OpenIPMIpthread failed"
+ fi
+ AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+ AC_MSG_CHECKING([for libOpenIPMIpthread LDFLAGS])
+ temp_result="`$PKG_CONFIG --libs OpenIPMIpthread`"
+ if test "$?" = "0"
+ then
+ with_libopenipmipthread_ldflags="$temp_result"
+ else
+ with_libopenipmipthread="no ($PKG_CONFIG --libs OpenIPMIpthread failed)"
+ temp_result="$PKG_CONFIG --libs OpenIPMIpthread failed"
+ fi
+ AC_MSG_RESULT([$temp_result])
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+ SAVE_CPPFLAGS="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $with_libopenipmipthread_cflags"
+
+ AC_CHECK_HEADERS(OpenIPMI/ipmi_smi.h,
+ [with_libopenipmipthread="yes"],
+ [with_libopenipmipthread="no (OpenIPMI/ipmi_smi.h not found)"],
+[#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+])
+
+ CPPFLAGS="$SAVE_CPPFLAGS"
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+ BUILD_WITH_OPENIPMI_CFLAGS="$with_libopenipmipthread_cflags"
+ BUILD_WITH_OPENIPMI_LIBS="$with_libopenipmipthread_ldflags"
+ AC_SUBST(BUILD_WITH_OPENIPMI_CFLAGS)
+ AC_SUBST(BUILD_WITH_OPENIPMI_LIBS)
+fi
+
dnl Check for libvirt and libxml2 libraries.
with_libxml2="no (pkg-config isn't available)"
with_libxml2_cflags=""
with_libvirt="no (pkg-config isn't available)"
with_libvirt_cflags=""
with_libvirt_ldflags=""
-PKG_PROG_PKG_CONFIG
if test "x$PKG_CONFIG" != "x"
then
pkg-config --exists 'libxml-2.0' 2>/dev/null
AC_COLLECTD([daemon], [disable], [feature], [daemon mode])
AC_COLLECTD([getifaddrs],[enable], [feature], [getifaddrs under Linux])
+plugin_ascent="no"
plugin_battery="no"
plugin_cpu="no"
plugin_cpufreq="no"
plugin_disk="no"
plugin_entropy="no"
plugin_interface="no"
+plugin_ipmi="no"
plugin_ipvs="no"
plugin_irq="no"
plugin_libvirt="no"
plugin_tape="no"
plugin_tcpconns="no"
plugin_users="no"
+plugin_vmem="no"
plugin_vserver="no"
plugin_wireless="no"
plugin_serial="yes"
plugin_swap="yes"
plugin_tcpconns="yes"
+ plugin_vmem="yes"
plugin_vserver="yes"
plugin_wireless="yes"
plugin_swap="yes"
fi
+if test "x$with_libcurl" = "xyes" && test "x$with_libxml2" = "xyes"
+then
+ plugin_ascent="yes"
+fi
+
+if test "x$with_libopenipmipthread" = "xyes"
+then
+ plugin_ipmi="yes"
+fi
+
if test "x$have_processor_info" = "xyes"
then
plugin_cpu="yes"
AC_PLUGIN([apache], [$with_libcurl], [Apache httpd statistics])
AC_PLUGIN([apcups], [yes], [Statistics of UPSes by APC])
AC_PLUGIN([apple_sensors], [$with_libiokit], [Apple's hardware sensors])
+AC_PLUGIN([ascent], [$plugin_ascent], [AscentEmu player statistics])
AC_PLUGIN([battery], [$plugin_battery], [Battery statistics])
AC_PLUGIN([cpu], [$plugin_cpu], [CPU usage statistics])
AC_PLUGIN([cpufreq], [$plugin_cpufreq], [CPU frequency statistics])
AC_PLUGIN([hddtemp], [yes], [Query hddtempd])
AC_PLUGIN([interface], [$plugin_interface], [Interface traffic statistics])
AC_PLUGIN([iptables], [$with_libiptc], [IPTables rule counters])
+AC_PLUGIN([ipmi], [$plugin_ipmi], [IPMI sensor statistics])
AC_PLUGIN([ipvs], [$plugin_ipvs], [IPVS connection statistics])
AC_PLUGIN([irq], [$plugin_irq], [IRQ statistics])
AC_PLUGIN([libvirt], [$plugin_libvirt], [Virtual machine statistics])
AC_PLUGIN([nut], [$with_libupsclient], [Network UPS tools statistics])
AC_PLUGIN([perl], [$plugin_perl], [Embed a Perl interpreter])
AC_PLUGIN([ping], [$with_liboping], [Network latency statistics])
+AC_PLUGIN([powerdns], [yes], [PowerDNS statistics])
AC_PLUGIN([processes], [$plugin_processes], [Process statistics])
AC_PLUGIN([rrdtool], [$with_rrdtool], [RRDTool output plugin])
AC_PLUGIN([sensors], [$with_lm_sensors], [lm_sensors statistics])
AC_PLUGIN([snmp], [$with_libnetsnmp], [SNMP querying plugin])
AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics])
AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin])
+AC_PLUGIN([tail], [yes], [Parsing of logfiles])
AC_PLUGIN([tape], [$plugin_tape], [Tape drive statistics])
AC_PLUGIN([tcpconns], [$plugin_tcpconns], [TCP connection statistics])
+AC_PLUGIN([teamspeak2], [yes], [TeamSpeak2 server statistics])
AC_PLUGIN([unixsock], [yes], [Unixsock communication plugin])
AC_PLUGIN([users], [$plugin_users], [User statistics])
AC_PLUGIN([uuid], [yes], [UUID as hostname plugin])
+AC_PLUGIN([vmem], [$plugin_vmem], [Virtual memory statistics])
AC_PLUGIN([vserver], [$plugin_vserver], [Linux VServer statistics])
AC_PLUGIN([wireless], [$plugin_wireless], [Wireless statistics])
AC_PLUGIN([xmms], [$with_libxmms], [XMMS statistics])
libnetlink . . . . $with_libnetlink
libnetsnmp . . . . $with_libnetsnmp
liboconfig . . . . $with_liboconfig
+ libopenipmi . . . . $with_libopenipmipthread
liboping . . . . . $with_liboping
libpcap . . . . . . $with_libpcap
libperl . . . . . . $with_libperl
apache . . . . . . $enable_apache
apcups . . . . . . $enable_apcups
apple_sensors . . . $enable_apple_sensors
+ ascent . . . . . . $enable_ascent
battery . . . . . . $enable_battery
cpu . . . . . . . . $enable_cpu
cpufreq . . . . . . $enable_cpufreq
hddtemp . . . . . . $enable_hddtemp
interface . . . . . $enable_interface
iptables . . . . . $enable_iptables
+ ipmi . . . . . . . $enable_ipmi
ipvs . . . . . . . $enable_ipvs
irq . . . . . . . . $enable_irq
libvirt . . . . . . $enable_libvirt
nut . . . . . . . . $enable_nut
perl . . . . . . . $enable_perl
ping . . . . . . . $enable_ping
+ powerdns . . . . . $enable_powerdns
processes . . . . . $enable_processes
rrdtool . . . . . . $enable_rrdtool
sensors . . . . . . $enable_sensors
snmp . . . . . . . $enable_snmp
swap . . . . . . . $enable_swap
syslog . . . . . . $enable_syslog
+ tail . . . . . . . $enable_tail
tape . . . . . . . $enable_tape
tcpconns . . . . . $enable_tcpconns
+ teamspeak2 . . . . $enable_teamspeak2
unixsock . . . . . $enable_unixsock
users . . . . . . . $enable_users
uuid . . . . . . . $enable_uuid
+ vmem . . . . . . . $enable_vmem
vserver . . . . . . $enable_vserver
wireless . . . . . $enable_wireless
xmms . . . . . . . $enable_xmms
diff --git a/contrib/cussh.pl b/contrib/cussh.pl
index 65c634e0f8637dc57c34d8812645639b3a7fad13..6da2856d0d8b307387788da48aff2da58cf6353d 100755 (executable)
--- a/contrib/cussh.pl
+++ b/contrib/cussh.pl
#!/usr/bin/perl
#
# collectd - contrib/cussh.pl
-# Copyright (C) 2007 Sebastian Harl
+# Copyright (C) 2007-2008 Sebastian Harl
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
my $path = $ARGV[0] || "/var/run/collectd-unixsock";
my $sock = Collectd::Unixsock->new($path);
+ my $cmds = {
+ PUTVAL => \&putval,
+ GETVAL => \&getval,
+ FLUSH => \&flush,
+ LISTVAL => \&listval,
+ };
+
if (! $sock) {
print STDERR "Unable to connect to $path!\n";
exit 1;
}
- print "cussh version 0.1, Copyright (C) 2007 Sebastian Harl\n"
+ print "cussh version 0.2, Copyright (C) 2007-2008 Sebastian Harl\n"
. "cussh comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
. "and you are welcome to redistribute it under certain conditions.\n"
. "See the GNU General Public License 2 for more details.\n\n";
print "cussh> ";
my $line = <STDIN>;
- last if ((! $line) || ($line =~ m/^quit$/i));
+ last if (! $line);
+
+ chomp $line;
- my ($cmd) = $line =~ m/^(\w+)\s+/;
+ last if ($line =~ m/^quit$/i);
+
+ my ($cmd) = $line =~ m/^(\w+)\s*/;
$line = $';
next if (! $cmd);
$cmd = uc $cmd;
my $f = undef;
- if ($cmd eq "PUTVAL") {
- $f = \&putval;
- }
- elsif ($cmd eq "GETVAL") {
- $f = \&getval;
+ if (defined $cmds->{$cmd}) {
+ $f = $cmds->{$cmd};
}
else {
print STDERR "ERROR: Unknown command $cmd!\n";
print $$string . $/;
my ($h, $p, $pi, $t, $ti) =
- $$string =~ m/^(\w+)\/(\w+)(?:-(\w+))?\/(\w+)(?:-(\w+))?\s+/;
+ $$string =~ m/^(\w+)\/(\w+)(?:-(\w+))?\/(\w+)(?:-(\w+))?\s*/;
$$string = $';
return if ((! $h) || (! $p) || (! $t));
return \%id;
}
+sub putid {
+ my $ident = shift || return;
+
+ my $string;
+
+ $string = $ident->{'host'} . "/" . $ident->{'plugin'};
+
+ if (defined $ident->{'plugin_instance'}) {
+ $string .= "-" . $ident->{'plugin_instance'};
+ }
+
+ $string .= "/" . $ident->{'type'};
+
+ if (defined $ident->{'type_instance'}) {
+ $string .= "-" . $ident->{'type_instance'};
+ }
+ return $string;
+}
+
=head1 COMMANDS
=over 4
=item B<GETVAL> I<Identifier>
-=item B<PUTVAL> I<Identifier> I<Valuelist>
-
-These commands follow the exact same syntax as described in
-L<collectd-unixsock(5)>.
-
=cut
sub putval {
my $id = getid(\$line);
- return if (! $id);
+ if (! $id) {
+ print STDERR $sock->{'error'} . $/;
+ return;
+ }
my ($time, @values) = split m/:/, $line;
- return $sock->putval(%$id, $time, \@values);
+ return $sock->putval(%$id, time => $time, values => \@values);
}
+=item B<PUTVAL> I<Identifier> I<Valuelist>
+
+=cut
+
sub getval {
my $sock = shift || return;
my $line = shift || return;
my $id = getid(\$line);
- return if (! $id);
+ if (! $id) {
+ print STDERR $sock->{'error'} . $/;
+ return;
+ }
my $vals = $sock->getval(%$id);
- return if (! $vals);
+ if (! $vals) {
+ print STDERR $sock->{'error'} . $/;
+ return;
+ }
foreach my $key (keys %$vals) {
print "\t$key: $vals->{$key}\n";
return 1;
}
+=item B<FLUSH> [B<timeout>=I<$timeout>] [B<plugin>=I<$plugin>[ ...]]
+
+=cut
+
+sub flush {
+ my $sock = shift || return;
+ my $line = shift;
+
+ my $res;
+
+ if (! $line) {
+ $res = $sock->flush();
+ }
+ else {
+ my %args = ();
+
+ foreach my $i (split m/ /, $line) {
+ my ($option, $value) = $i =~ m/^([^=]+)=(.+)$/;
+ next if (! ($option && $value));
+
+ if ($option eq "plugin") {
+ push @{$args{"plugins"}}, $value;
+ }
+ elsif ($option eq "timeout") {
+ $args{"timeout"} = $value;
+ }
+ else {
+ print STDERR "Invalid option \"$option\".\n";
+ return;
+ }
+ }
+
+ $res = $sock->flush(%args);
+ }
+
+ if (! $res) {
+ print STDERR $sock->{'error'} . $/;
+ return;
+ }
+ return 1;
+}
+
+=item B<LISTVAL>
+
+=cut
+
+sub listval {
+ my $sock = shift || return;
+
+ my @res;
+
+ @res = $sock->listval();
+
+ if (! @res) {
+ print STDERR $sock->{'error'} . $/;
+ return;
+ }
+
+ foreach my $ident (@res) {
+ print $ident->{'time'} . " " . putid($ident) . $/;
+ }
+ return 1;
+}
+
+=back
+
+These commands follow the exact same syntax as described in
+L<collectd-unixsock(5)>.
+
=head1 SEE ALSO
L<collectd(1)>, L<collectd-unisock(5)>
diff --git a/contrib/migrate-3-4.px b/contrib/migrate-3-4.px
index ed418273359086c1ab88e20d780c276b1e7c2750..ed19a7b61535265f11c6423fd1154d867735bd7c 100755 (executable)
--- a/contrib/migrate-3-4.px
+++ b/contrib/migrate-3-4.px
print "./rrd_filter.px -i '$InDir/$orig_filename' -m '${src_ds}:${dst_ds}' -o '$OutDir/$dest_filename'\n";
}
}
- elsif (exists ($TypeRename{$orig->{'type'}}))
+ else
+ {
+ print "cp '$InDir/$orig_filename' '$OutDir/$dest_filename'\n";
+ }
+
+ if (exists ($TypeRename{$orig->{'type'}}))
{
my $src_dses = $TypeRename{$orig->{'type'}}->{'from'};
my $dst_dses = $TypeRename{$orig->{'type'}}->{'to'};
- my @sed_prog = ();
+ print "rrdtool tune '$OutDir/$dest_filename'";
for (my $i = 0; $i < @$src_dses; $i++)
{
- push (@sed_prog, 's/^' . $src_dses->[$i] . '$/' . $dst_dses->[$i] . '/g;');
+ print " --data-source-rename "
+ . $src_dses->[$i] . ':' . $dst_dses->[$i];
}
-
- print "rrdtool dump '$InDir/$orig_filename' | sed -e '" . join (' ', @sed_prog) . "' | rrdtool restore - '$OutDir/$dest_filename'\n";
- }
- else
- {
- print "cp '$InDir/$orig_filename' '$OutDir/$dest_filename'\n";
+ print "\n";
}
}
diff --git a/src/Makefile.am b/src/Makefile.am
index 02b85fcd1d7f438c718826f0c1957e2af0a5dc81..fe4af774f4a3b9614d6540c1820601936dbab24c 100644 (file)
--- a/src/Makefile.am
+++ b/src/Makefile.am
utils_cache.c utils_cache.h \
utils_ignorelist.c utils_ignorelist.h \
utils_llist.c utils_llist.h \
+ utils_tail_match.c utils_tail_match.h \
+ utils_match.c utils_match.h \
utils_mount.c utils_mount.h \
+ utils_tail.c utils_tail.h \
utils_threshold.c utils_threshold.h \
types_list.c types_list.h
collectd_DEPENDENCIES += apple_sensors.la
endif
+if BUILD_PLUGIN_ASCENT
+pkglib_LTLIBRARIES += ascent.la
+ascent_la_SOURCES = ascent.c
+ascent_la_LDFLAGS = -module -avoid-version
+ascent_la_CFLAGS = $(BUILD_WITH_LIBCURL_CFLAGS) $(BUILD_WITH_LIBXML2_CFLAGS)
+ascent_la_LIBADD = $(BUILD_WITH_LIBCURL_LIBS) $(BUILD_WITH_LIBXML2_LIBS)
+collectd_LDADD += "-dlopen" apache.la
+collectd_DEPENDENCIES += ascent.la
+endif
+
if BUILD_PLUGIN_BATTERY
pkglib_LTLIBRARIES += battery.la
battery_la_SOURCES = battery.c
collectd_DEPENDENCIES += iptables.la
endif
+if BUILD_PLUGIN_IPMI
+pkglib_LTLIBRARIES += ipmi.la
+ipmi_la_SOURCES = ipmi.c
+ipmi_la_CFLAGS = $(BUILD_WITH_OPENIPMI_CFLAGS)
+ipmi_la_LDFLAGS = -module -avoid-version
+ipmi_la_LIBADD = $(BUILD_WITH_OPENIPMI_LIBS)
+collectd_LDADD += "-dlopen" ipmi.la
+collectd_DEPENDENCIES += ipmi.la
+endif
+
if BUILD_PLUGIN_IPVS
pkglib_LTLIBRARIES += ipvs.la
ipvs_la_SOURCES = ipvs.c
collectd_DEPENDENCIES += ping.la
endif
+if BUILD_PLUGIN_POWERDNS
+pkglib_LTLIBRARIES += powerdns.la
+powerdns_la_SOURCES = powerdns.c
+powerdns_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" powerdns.la
+collectd_DEPENDENCIES += powerdns.la
+endif
+
if BUILD_PLUGIN_PROCESSES
pkglib_LTLIBRARIES += processes.la
processes_la_SOURCES = processes.c
collectd_DEPENDENCIES += syslog.la
endif
+if BUILD_PLUGIN_TAIL
+pkglib_LTLIBRARIES += tail.la
+tail_la_SOURCES = tail.c
+tail_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" tail.la
+collectd_DEPENDENCIES += tail.la
+endif
+
if BUILD_PLUGIN_TAPE
pkglib_LTLIBRARIES += tape.la
tape_la_SOURCES = tape.c
collectd_DEPENDENCIES += tcpconns.la
endif
+if BUILD_PLUGIN_TEAMSPEAK2
+pkglib_LTLIBRARIES += teamspeak2.la
+teamspeak2_la_SOURCES = teamspeak2.c
+teamspeak2_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" teamspeak2.la
+collectd_DEPENDENCIES += teamspeak2.la
+endif
+
if BUILD_PLUGIN_UNIXSOCK
pkglib_LTLIBRARIES += unixsock.la
-unixsock_la_SOURCES = unixsock.c utils_cmd_putval.h utils_cmd_putval.c utils_cmd_putnotif.h utils_cmd_putnotif.c
+unixsock_la_SOURCES = unixsock.c \
+ utils_cmd_flush.h utils_cmd_flush.c \
+ utils_cmd_getval.h utils_cmd_getval.c \
+ utils_cmd_listval.h utils_cmd_listval.c \
+ utils_cmd_putval.h utils_cmd_putval.c \
+ utils_cmd_putnotif.h utils_cmd_putnotif.c
unixsock_la_LDFLAGS = -module -avoid-version -lpthread
collectd_LDADD += "-dlopen" unixsock.la
collectd_DEPENDENCIES += unixsock.la
collectd_DEPENDENCIES += uuid.la
endif
+if BUILD_PLUGIN_VMEM
+pkglib_LTLIBRARIES += vmem.la
+vmem_la_SOURCES = vmem.c
+vmem_la_LDFLAGS = -module -avoid-version
+collectd_LDADD += "-dlopen" vmem.la
+collectd_DEPENDENCIES += vmem.la
+endif
+
if BUILD_PLUGIN_VSERVER
pkglib_LTLIBRARIES += vserver.la
vserver_la_SOURCES = vserver.c
diff --git a/src/apache.c b/src/apache.c
index 2a7e0b80cc41ef0e0bf912dab58895bb88b8ee56..3cda565074eb0376014946386856b8fba0f05ed8 100644 (file)
--- a/src/apache.c
+++ b/src/apache.c
#include <curl/curl.h>
-static char *url = NULL;
-static char *user = NULL;
-static char *pass = NULL;
-static char *cacert = NULL;
+static char *url = NULL;
+static char *user = NULL;
+static char *pass = NULL;
+static char *verify_peer = NULL;
+static char *verify_host = NULL;
+static char *cacert = NULL;
static CURL *curl = NULL;
"URL",
"User",
"Password",
+ "VerifyPeer",
+ "VerifyHost",
"CACert"
};
static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
return (config_set (&user, value));
else if (strcasecmp (key, "password") == 0)
return (config_set (&pass, value));
+ else if (strcasecmp (key, "verifypeer") == 0)
+ return (config_set (&verify_peer, value));
+ else if (strcasecmp (key, "verifyhost") == 0)
+ return (config_set (&verify_host, value));
else if (strcasecmp (key, "cacert") == 0)
return (config_set (&cacert, value));
else
curl_easy_setopt (curl, CURLOPT_URL, url);
+ if ((verify_peer == NULL) || (strcmp (verify_peer, "true") == 0))
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 1);
+ }
+ else
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0);
+ }
+
+ if ((verify_host == NULL) || (strcmp (verify_host, "true") == 0))
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 2);
+ }
+ else
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+
if (cacert != NULL)
{
curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
diff --git a/src/ascent.c b/src/ascent.c
--- /dev/null
+++ b/src/ascent.c
@@ -0,0 +1,596 @@
+/**
+ * collectd - src/ascent.c
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+
+#include <curl/curl.h>
+#include <libxml/parser.h>
+
+static char *races_list[] = /* {{{ */
+{
+ NULL,
+ "Human", /* 1 */
+ "Orc", /* 2 */
+ "Dwarf", /* 3 */
+ "Nightelf", /* 4 */
+ "Undead", /* 5 */
+ "Tauren", /* 6 */
+ "Gnome", /* 7 */
+ "Troll", /* 8 */
+ NULL,
+ "Bloodelf", /* 10 */
+ "Draenei" /* 11 */
+}; /* }}} */
+#define RACES_LIST_LENGTH STATIC_ARRAY_SIZE (races_list)
+
+static char *classes_list[] = /* {{{ */
+{
+ NULL,
+ "Warrior", /* 1 */
+ "Paladin", /* 2 */
+ "Hunter", /* 3 */
+ "Rogue", /* 4 */
+ "Priest", /* 5 */
+ NULL,
+ "Shaman", /* 7 */
+ "Mage", /* 8 */
+ "Warlock", /* 9 */
+ NULL,
+ "Druid" /* 11 */
+}; /* }}} */
+#define CLASSES_LIST_LENGTH STATIC_ARRAY_SIZE (classes_list)
+
+static char *genders_list[] = /* {{{ */
+{
+ "Male",
+ "Female"
+}; /* }}} */
+#define GENDERS_LIST_LENGTH STATIC_ARRAY_SIZE (genders_list)
+
+struct player_stats_s
+{
+ int races[RACES_LIST_LENGTH];
+ int classes[CLASSES_LIST_LENGTH];
+ int genders[GENDERS_LIST_LENGTH];
+ int level_sum;
+ int level_num;
+ int latency_sum;
+ int latency_num;
+};
+typedef struct player_stats_s player_stats_t;
+
+struct player_info_s
+{
+ int race;
+ int class;
+ int gender;
+ int level;
+ int latency;
+};
+typedef struct player_info_s player_info_t;
+#define PLAYER_INFO_STATIC_INIT { -1, -1, -1, -1, -1 }
+
+static char *url = NULL;
+static char *user = NULL;
+static char *pass = NULL;
+static char *cacert = NULL;
+
+static CURL *curl = NULL;
+
+static char *ascent_buffer = NULL;
+static size_t ascent_buffer_size = 0;
+static size_t ascent_buffer_fill = 0;
+static char ascent_curl_error[CURL_ERROR_SIZE];
+
+static const char *config_keys[] =
+{
+ "URL",
+ "User",
+ "Password",
+ "CACert"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int ascent_submit_gauge (const char *plugin_instance, /* {{{ */
+ const char *type, const char *type_instance, gauge_t value)
+{
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+
+ values[0].gauge = value;
+
+ vl.values = values;
+ vl.values_len = 1;
+ vl.time = time (NULL);
+ strcpy (vl.host, hostname_g);
+ strcpy (vl.plugin, "ascent");
+
+ if (plugin_instance != NULL)
+ sstrncpy (vl.plugin_instance, plugin_instance,
+ sizeof (vl.plugin_instance));
+
+ if (type_instance != NULL)
+ sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+ plugin_dispatch_values (type, &vl);
+ return (0);
+} /* }}} int ascent_submit_gauge */
+
+static size_t ascent_curl_callback (void *buf, size_t size, size_t nmemb, /* {{{ */
+ void *stream)
+{
+ size_t len = size * nmemb;
+
+ if (len <= 0)
+ return (len);
+
+ if ((ascent_buffer_fill + len) >= ascent_buffer_size)
+ {
+ char *temp;
+
+ temp = (char *) realloc (ascent_buffer,
+ ascent_buffer_fill + len + 1);
+ if (temp == NULL)
+ {
+ ERROR ("ascent plugin: realloc failed.");
+ return (0);
+ }
+ ascent_buffer = temp;
+ ascent_buffer_size = ascent_buffer_fill + len + 1;
+ }
+
+ memcpy (ascent_buffer + ascent_buffer_fill, (char *) buf, len);
+ ascent_buffer_fill += len;
+ ascent_buffer[ascent_buffer_fill] = 0;
+
+ return (len);
+} /* }}} size_t ascent_curl_callback */
+
+static int ascent_submit_players (player_stats_t *ps) /* {{{ */
+{
+ int i;
+ gauge_t value;
+
+ for (i = 0; i < RACES_LIST_LENGTH; i++)
+ if (races_list[i] != NULL)
+ ascent_submit_gauge ("by-race", "players", races_list[i],
+ (gauge_t) ps->races[i]);
+
+ for (i = 0; i < CLASSES_LIST_LENGTH; i++)
+ if (classes_list[i] != NULL)
+ ascent_submit_gauge ("by-class", "players", classes_list[i],
+ (gauge_t) ps->classes[i]);
+
+ for (i = 0; i < GENDERS_LIST_LENGTH; i++)
+ if (genders_list[i] != NULL)
+ ascent_submit_gauge ("by-gender", "players", genders_list[i],
+ (gauge_t) ps->genders[i]);
+
+ if (ps->level_num <= 0)
+ value = NAN;
+ else
+ value = ((double) ps->level_sum) / ((double) ps->level_num);
+ ascent_submit_gauge (NULL, "gauge", "avg-level", value);
+
+ /* Latency is in ms, but we store seconds. */
+ if (ps->latency_num <= 0)
+ value = NAN;
+ else
+ value = ((double) ps->latency_sum) / (1000.0 * ((double) ps->latency_num));
+ ascent_submit_gauge (NULL, "latency", "average", value);
+
+ return (0);
+} /* }}} int ascent_submit_players */
+
+static int ascent_account_player (player_stats_t *ps, /* {{{ */
+ player_info_t *pi)
+{
+ if (pi->race >= 0)
+ {
+ if ((pi->race >= RACES_LIST_LENGTH)
+ || (races_list[pi->race] == NULL))
+ ERROR ("ascent plugin: Ignoring invalid numeric race %i.", pi->race);
+ else
+ ps->races[pi->race]++;
+ }
+
+ if (pi->class >= 0)
+ {
+ if ((pi->class >= CLASSES_LIST_LENGTH)
+ || (classes_list[pi->class] == NULL))
+ ERROR ("ascent plugin: Ignoring invalid numeric class %i.", pi->class);
+ else
+ ps->classes[pi->class]++;
+ }
+
+ if (pi->gender >= 0)
+ {
+ if ((pi->gender >= GENDERS_LIST_LENGTH)
+ || (genders_list[pi->gender] == NULL))
+ ERROR ("ascent plugin: Ignoring invalid numeric gender %i.",
+ pi->gender);
+ else
+ ps->genders[pi->gender]++;
+ }
+
+
+ if (pi->level > 0)
+ {
+ ps->level_sum += pi->level;
+ ps->level_num++;
+ }
+
+ if (pi->latency >= 0)
+ {
+ ps->latency_sum += pi->latency;
+ ps->latency_num++;
+ }
+
+ return (0);
+} /* }}} int ascent_account_player */
+
+static int ascent_xml_submit_gauge (xmlDoc *doc, xmlNode *node, /* {{{ */
+ const char *plugin_instance, const char *type, const char *type_instance)
+{
+ char *str_ptr;
+ gauge_t value;
+
+ str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ if (str_ptr == NULL)
+ {
+ ERROR ("ascent plugin: ascent_xml_submit_gauge: xmlNodeListGetString failed.");
+ return (-1);
+ }
+
+ if (strcasecmp ("N/A", str_ptr) == 0)
+ value = NAN;
+ else
+ {
+ char *end_ptr = NULL;
+ value = strtod (str_ptr, &end_ptr);
+ if (str_ptr == end_ptr)
+ {
+ ERROR ("ascent plugin: ascent_xml_submit_gauge: strtod failed.");
+ return (-1);
+ }
+ }
+
+ return (ascent_submit_gauge (plugin_instance, type, type_instance, value));
+} /* }}} int ascent_xml_submit_gauge */
+
+static int ascent_xml_read_int (xmlDoc *doc, xmlNode *node, /* {{{ */
+ int *ret_value)
+{
+ char *str_ptr;
+ int value;
+
+ str_ptr = (char *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ if (str_ptr == NULL)
+ {
+ ERROR ("ascent plugin: ascent_xml_read_int: xmlNodeListGetString failed.");
+ return (-1);
+ }
+
+ if (strcasecmp ("N/A", str_ptr) == 0)
+ value = -1;
+ else
+ {
+ char *end_ptr = NULL;
+ value = strtol (str_ptr, &end_ptr, 0);
+ if (str_ptr == end_ptr)
+ {
+ ERROR ("ascent plugin: ascent_xml_read_int: strtol failed.");
+ return (-1);
+ }
+ }
+
+ *ret_value = value;
+ return (0);
+} /* }}} int ascent_xml_read_int */
+
+static int ascent_xml_sessions_plr (xmlDoc *doc, xmlNode *node, /* {{{ */
+ player_info_t *pi)
+{
+ xmlNode *child;
+
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+ {
+ if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+ /* ignore */;
+ else if (xmlStrcmp ((const xmlChar *) "race", child->name) == 0)
+ ascent_xml_read_int (doc, child, &pi->race);
+ else if (xmlStrcmp ((const xmlChar *) "class", child->name) == 0)
+ ascent_xml_read_int (doc, child, &pi->class);
+ else if (xmlStrcmp ((const xmlChar *) "gender", child->name) == 0)
+ ascent_xml_read_int (doc, child, &pi->gender);
+ else if (xmlStrcmp ((const xmlChar *) "level", child->name) == 0)
+ ascent_xml_read_int (doc, child, &pi->level);
+ else if (xmlStrcmp ((const xmlChar *) "latency", child->name) == 0)
+ ascent_xml_read_int (doc, child, &pi->latency);
+ else if ((xmlStrcmp ((const xmlChar *) "name", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "pvprank", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "map", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "areaid", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "xpos", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "ypos", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "onime", child->name) == 0))
+ /* ignore */;
+ else
+ {
+ WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+ }
+ } /* for (child) */
+
+ return (0);
+} /* }}} int ascent_xml_sessions_plr */
+
+static int ascent_xml_sessions (xmlDoc *doc, xmlNode *node) /* {{{ */
+{
+ xmlNode *child;
+ player_stats_t ps;
+
+ memset (&ps, 0, sizeof (ps));
+
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+ {
+ if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+ /* ignore */;
+ else if (xmlStrcmp ((const xmlChar *) "plr", child->name) == 0)
+ {
+ int status;
+ player_info_t pi = PLAYER_INFO_STATIC_INIT;
+
+ status = ascent_xml_sessions_plr (doc, child, &pi);
+ if (status == 0)
+ ascent_account_player (&ps, &pi);
+ }
+ else
+ {
+ WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+ }
+ } /* for (child) */
+
+ ascent_submit_players (&ps);
+
+ return (0);
+} /* }}} int ascent_xml_sessions */
+
+static int ascent_xml_status (xmlDoc *doc, xmlNode *node) /* {{{ */
+{
+ xmlNode *child;
+
+ for (child = node->xmlChildrenNode; child != NULL; child = child->next)
+ {
+ if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+ /* ignore */;
+ else if (xmlStrcmp ((const xmlChar *) "alliance", child->name) == 0)
+ ascent_xml_submit_gauge (doc, child, NULL, "players", "alliance");
+ else if (xmlStrcmp ((const xmlChar *) "horde", child->name) == 0)
+ ascent_xml_submit_gauge (doc, child, NULL, "players", "horde");
+ else if (xmlStrcmp ((const xmlChar *) "qplayers", child->name) == 0)
+ ascent_xml_submit_gauge (doc, child, NULL, "players", "queued");
+ else if ((xmlStrcmp ((const xmlChar *) "acceptedconns", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "avglat", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "cdbquerysize", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "cpu", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "fthreads", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "gmcount", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "lastupdate", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "ontime", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "oplayers", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "peakcount", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "platform", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "ram", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "threads", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "uptime", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "wdbquerysize", child->name) == 0))
+ /* ignore */;
+ else
+ {
+ WARNING ("ascent plugin: ascent_xml_status: Unknown tag: %s", child->name);
+ }
+ } /* for (child) */
+
+ return (0);
+} /* }}} int ascent_xml_status */
+
+static int ascent_xml (const char *data) /* {{{ */
+{
+ xmlDoc *doc;
+ xmlNode *cur;
+ xmlNode *child;
+
+#if 0
+ doc = xmlParseMemory (data, strlen (data),
+ /* URL = */ "ascent.xml",
+ /* encoding = */ NULL,
+ /* options = */ 0);
+#else
+ doc = xmlParseMemory (data, strlen (data));
+#endif
+ if (doc == NULL)
+ {
+ ERROR ("ascent plugin: xmlParseMemory failed.");
+ return (-1);
+ }
+
+ cur = xmlDocGetRootElement (doc);
+ if (cur == NULL)
+ {
+ ERROR ("ascent plugin: XML document is empty.");
+ xmlFreeDoc (doc);
+ return (-1);
+ }
+
+ if (xmlStrcmp ((const xmlChar *) "serverpage", cur->name) != 0)
+ {
+ ERROR ("ascent plugin: XML root element is not \"serverpage\".");
+ xmlFreeDoc (doc);
+ return (-1);
+ }
+
+ for (child = cur->xmlChildrenNode; child != NULL; child = child->next)
+ {
+ if ((xmlStrcmp ((const xmlChar *) "comment", child->name) == 0)
+ || (xmlStrcmp ((const xmlChar *) "text", child->name) == 0))
+ /* ignore */;
+ else if (xmlStrcmp ((const xmlChar *) "status", child->name) == 0)
+ ascent_xml_status (doc, child);
+ else if (xmlStrcmp ((const xmlChar *) "instances", child->name) == 0)
+ /* ignore for now */;
+ else if (xmlStrcmp ((const xmlChar *) "gms", child->name) == 0)
+ /* ignore for now */;
+ else if (xmlStrcmp ((const xmlChar *) "sessions", child->name) == 0)
+ ascent_xml_sessions (doc, child);
+ else
+ {
+ WARNING ("ascent plugin: ascent_xml: Unknown tag: %s", child->name);
+ }
+ } /* for (child) */
+
+ xmlFreeDoc (doc);
+ return (0);
+} /* }}} int ascent_xml */
+
+static int config_set (char **var, const char *value) /* {{{ */
+{
+ if (*var != NULL)
+ {
+ free (*var);
+ *var = NULL;
+ }
+
+ if ((*var = strdup (value)) == NULL)
+ return (1);
+ else
+ return (0);
+} /* }}} int config_set */
+
+static int ascent_config (const char *key, const char *value) /* {{{ */
+{
+ if (strcasecmp (key, "URL") == 0)
+ return (config_set (&url, value));
+ else if (strcasecmp (key, "User") == 0)
+ return (config_set (&user, value));
+ else if (strcasecmp (key, "Password") == 0)
+ return (config_set (&pass, value));
+ else if (strcasecmp (key, "CACert") == 0)
+ return (config_set (&cacert, value));
+ else
+ return (-1);
+} /* }}} int ascent_config */
+
+static int ascent_init (void) /* {{{ */
+{
+ static char credentials[1024];
+
+ if (url == NULL)
+ {
+ WARNING ("ascent plugin: ascent_init: No URL configured, "
+ "returning an error.");
+ return (-1);
+ }
+
+ if (curl != NULL)
+ {
+ curl_easy_cleanup (curl);
+ }
+
+ if ((curl = curl_easy_init ()) == NULL)
+ {
+ ERROR ("ascent plugin: ascent_init: curl_easy_init failed.");
+ return (-1);
+ }
+
+ curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, ascent_curl_callback);
+ curl_easy_setopt (curl, CURLOPT_USERAGENT, PACKAGE_NAME"/"PACKAGE_VERSION);
+ curl_easy_setopt (curl, CURLOPT_ERRORBUFFER, ascent_curl_error);
+
+ if (user != NULL)
+ {
+ int status;
+
+ status = snprintf (credentials, sizeof (credentials), "%s:%s",
+ user, (pass == NULL) ? "" : pass);
+ if (status >= sizeof (credentials))
+ {
+ ERROR ("ascent plugin: ascent_init: Returning an error because the "
+ "credentials have been truncated.");
+ return (-1);
+ }
+ credentials[sizeof (credentials) - 1] = '\0';
+
+ curl_easy_setopt (curl, CURLOPT_USERPWD, credentials);
+ }
+
+ curl_easy_setopt (curl, CURLOPT_URL, url);
+
+ if (cacert != NULL)
+ curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
+
+ return (0);
+} /* }}} int ascent_init */
+
+static int ascent_read (void) /* {{{ */
+{
+ int status;
+
+ if (curl == NULL)
+ {
+ ERROR ("ascent plugin: I don't have a CURL object.");
+ return (-1);
+ }
+
+ if (url == NULL)
+ {
+ ERROR ("ascent plugin: No URL has been configured.");
+ return (-1);
+ }
+
+ ascent_buffer_fill = 0;
+ if (curl_easy_perform (curl) != 0)
+ {
+ ERROR ("ascent plugin: curl_easy_perform failed: %s",
+ ascent_curl_error);
+ return (-1);
+ }
+
+ status = ascent_xml (ascent_buffer);
+ if (status != 0)
+ return (-1);
+ else
+ return (0);
+} /* }}} int ascent_read */
+
+void module_register (void)
+{
+ plugin_register_config ("ascent", ascent_config, config_keys, config_keys_num);
+ plugin_register_init ("ascent", ascent_init);
+ plugin_register_read ("ascent", ascent_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 et fdm=marker : */
index 3ef24386548abf19a4588d0d9710a7eb4b744f3c..971cb36dc0a138a9c826d5148178714e57078b91 100644 (file)
connections. Once a connection is established the client can send commands to
the daemon which it will answer, if it understand them.
+In general the plugin answers with a status line of the following form:
+
+I<Status> I<Message>
+
+If I<Status> is greater than or equal to zero the message indicates success,
+if I<Status> is less than zero the message indicates failure. I<Message> is a
+human-readable string that further describes the return value.
+
+On success, I<Status> furthermore indicates the number of subsequent lines of
+output (not including the status line). Each such lines usually contains a
+single return value. See the description of each command for details.
+
The following commands are implemented:
=over 4
=item B<GETVAL> I<Identifier>
If the value identified by I<Identifier> (see below) is found the complete
-value-list is returned. The response is a space separated list of
-name-value-pairs:
-
-I<num> I<name>B<=>I<value>[ I<name>B<=>I<value>[ ...]]
-
-If I<num> is less then zero, an error occurred. Otherwise it contains the
-number of values that follow. Each value is of the form I<name>B<=>I<value>.
+value-list is returned. The response is a list of name-value-pairs, each pair
+on its own line (the number of lines is indicated by the status line - see
+above). Each name-value-pair is of the form I<name>B<=>I<value>.
Counter-values are converted to a rate, e.E<nbsp>g. bytes per second.
Undefined values are returned as B<NaN>.
Example:
-> | GETVAL myhost/cpu-0/cpu-user
- <- | 1 value=1.260000e+00
+ <- | 1 Value found
+ <- | value=1.260000e+00
=item B<LISTVAL>
Returns a list of the values available in the value cache together with the
time of the last update, so that querying applications can issue a B<GETVAL>
-command for the values that have changed.
-
-The first line's status number is the number of identifiers returned or less
-than zero if an error occurred. Each of the following lines contains the
-update time as an epoch value and the identifier, separated by a space.
+command for the values that have changed. Each return value consists of the
+update time as an epoch value and the identifier, separated by a space. The
+update time is the time of the last value, as provided by the collecting
+instance and may be very different from the time the server considers to be
+"now".
Example:
-> | LISTVAL
<- | 69 Values found
- <- | 1182204284 leeloo/cpu-0/cpu-idle
- <- | 1182204284 leeloo/cpu-0/cpu-nice
- <- | 1182204284 leeloo/cpu-0/cpu-system
- <- | 1182204284 leeloo/cpu-0/cpu-user
+ <- | 1182204284 myhost/cpu-0/cpu-idle
+ <- | 1182204284 myhost/cpu-0/cpu-nice
+ <- | 1182204284 myhost/cpu-0/cpu-system
+ <- | 1182204284 myhost/cpu-0/cpu-user
...
=item B<PUTVAL> I<Identifier> [I<OptionList>] I<Valuelist>
-> | PUTNOTIF type=temperature severity=warning time=1201094702 message=The roof is on fire!
<- | 0 Success
+=item B<FLUSH> [B<timeout=>I<Timeout>] [B<plugin=>I<Plugin> [...]]
+
+Flushes all cached data older than I<Timeout> seconds. If no timeout has been
+specified, it defaults to -1 which causes all data to be flushed. B<timeout>
+may be specified multiple times - each occurrence applies to plugins listed
+afterwards.
+
+If specified, only specific plugins are flushed. Otherwise all plugins
+providing a flush callback are flushed.
+
+Example:
+ -> | FLUSH
+ <- | 0 Done
+
=back
=head2 Identifiers
myhost/memory/memory-used
myhost/disk-sda/disk_octets
-=head2 Return values
-
-Unless otherwise noted the plugin answers with a line of the following form:
-
-I<Num> I<Message>
-
-If I<Num> is zero the message indicates success, if I<Num> is non-zero the
-message indicates failure. I<Message> is a human-readable string that describes
-the return value further.
-
-Commands that return values may use I<Num> to return the number of values that
-follow, such as the B<GETVAL> command. These commands usually return a negative
-value on failure and never return zero.
-
=head1 ABSTRACTION LAYER
B<collectd> ships the Perl-Module L<Collectd::Unixsock> which
diff --git a/src/collectd.c b/src/collectd.c
index 984ff757f3911fdc156717261bbb9c195907930a..d2ca56870c9e146a193ba498018d6952601c5513 100644 (file)
--- a/src/collectd.c
+++ b/src/collectd.c
#include <sys/socket.h>
#include <netdb.h>
+#include <pthread.h>
+
#include "plugin.h"
#include "configfile.h"
static int loop = 0;
-static void sigIntHandler (int signal)
+static void *do_flush (void *arg)
+{
+ INFO ("Flushing all data.");
+ plugin_flush_all (-1);
+ INFO ("Finished flushing all data.");
+ pthread_exit (NULL);
+ return NULL;
+}
+
+static void sig_int_handler (int signal)
{
loop++;
}
-static void sigTermHandler (int signal)
+static void sig_term_handler (int signal)
{
loop++;
}
+static void sig_usr1_handler (int signal)
+{
+ pthread_t thread;
+ pthread_attr_t attr;
+
+ /* flushing the data might take a while,
+ * so it should be done asynchronously */
+ pthread_attr_init (&attr);
+ pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+ pthread_create (&thread, &attr, do_flush, NULL);
+}
+
static int init_hostname (void)
{
const char *str;
" General:\n"
" -C <file> Configuration file.\n"
" Default: "CONFIGFILE"\n"
+ " -t Test config and exit.\n"
" -P <file> PID-file.\n"
" Default: "PIDFILE"\n"
#if COLLECT_DAEMON
" -f Don't fork to the background.\n"
#endif
+ " -h Display help (this message)\n"
"\nBuiltin defaults:\n"
" Config-File "CONFIGFILE"\n"
" PID-File "PIDFILE"\n"
int main (int argc, char **argv)
{
- struct sigaction sigIntAction;
- struct sigaction sigTermAction;
+ struct sigaction sig_int_action;
+ struct sigaction sig_term_action;
+ struct sigaction sig_usr1_action;
char *configfile = CONFIGFILE;
int test_config = 0;
const char *basedir;
/*
* install signal handlers
*/
- memset (&sigIntAction, '\0', sizeof (sigIntAction));
- sigIntAction.sa_handler = sigIntHandler;
- sigaction (SIGINT, &sigIntAction, NULL);
+ memset (&sig_int_action, '\0', sizeof (sig_int_action));
+ sig_int_action.sa_handler = sig_int_handler;
+ if (0 != sigaction (SIGINT, &sig_int_action, NULL)) {
+ char errbuf[1024];
+ ERROR ("Error: Failed to install a signal handler for signal INT: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (1);
+ }
+
+ memset (&sig_term_action, '\0', sizeof (sig_term_action));
+ sig_term_action.sa_handler = sig_term_handler;
+ if (0 != sigaction (SIGTERM, &sig_term_action, NULL)) {
+ char errbuf[1024];
+ ERROR ("Error: Failed to install a signal handler for signal TERM: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (1);
+ }
- memset (&sigTermAction, '\0', sizeof (sigTermAction));
- sigTermAction.sa_handler = sigTermHandler;
- sigaction (SIGTERM, &sigTermAction, NULL);
+ memset (&sig_usr1_action, '\0', sizeof (sig_usr1_action));
+ sig_usr1_action.sa_handler = sig_usr1_handler;
+ if (0 != sigaction (SIGUSR1, &sig_usr1_action, NULL)) {
+ char errbuf[1024];
+ ERROR ("Error: Failed to install a signal handler for signal USR1: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (1);
+ }
/*
* run the actual loops
diff --git a/src/collectd.conf.in b/src/collectd.conf.in
index e125bb63173ee7585e71902feee3dacfa79cc071..036916e70e10f337fb39d09bbfaa41547eb09377 100644 (file)
--- a/src/collectd.conf.in
+++ b/src/collectd.conf.in
@BUILD_PLUGIN_APACHE_TRUE@LoadPlugin apache
@BUILD_PLUGIN_APCUPS_TRUE@LoadPlugin apcups
@BUILD_PLUGIN_APPLE_SENSORS_TRUE@LoadPlugin apple_sensors
+@BUILD_PLUGIN_ASCENT_TRUE@LoadPlugin ascent
@BUILD_PLUGIN_BATTERY_TRUE@LoadPlugin battery
@BUILD_PLUGIN_CPU_TRUE@LoadPlugin cpu
@BUILD_PLUGIN_CPUFREQ_TRUE@LoadPlugin cpufreq
@BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp
@BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface
@BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables
+@BUILD_PLUGIN_IPMI_TRUE@LoadPlugin ipmi
@BUILD_PLUGIN_IPVS_TRUE@LoadPlugin ipvs
@BUILD_PLUGIN_IRQ_TRUE@LoadPlugin irq
@BUILD_PLUGIN_LIBVIRT_TRUE@LoadPlugin libvirt
@BUILD_PLUGIN_SYSLOG_TRUE@LoadPlugin syslog
@BUILD_PLUGIN_TAPE_TRUE@LoadPlugin tape
@BUILD_PLUGIN_TCPCONNS_TRUE@LoadPlugin tcpconns
+@BUILD_PLUGIN_TEAMSPEAK2_TRUE@LoadPlugin teamspeak2
@BUILD_PLUGIN_UNIXSOCK_TRUE@LoadPlugin unixsock
@BUILD_PLUGIN_USERS_TRUE@LoadPlugin users
@BUILD_PLUGIN_UUID_TRUE@LoadPlugin uuid
+@BUILD_PLUGIN_VMEM_TRUE@LoadPlugin vmem
@BUILD_PLUGIN_VSERVER_TRUE@LoadPlugin vserver
@BUILD_PLUGIN_WIRELESS_TRUE@LoadPlugin wireless
@BUILD_PLUGIN_XMMS_TRUE@LoadPlugin xmms
# Port "3551"
#</Plugin>
+#<Plugin ascent>
+# URL "http://localhost/ascent/status/"
+# User "www-user"
+# Password "secret"
+# CACert "/etc/ssl/ca.crt"
+#</Plugin>
+
#<Plugin csv>
# DataDir "@prefix@/var/lib/@PACKAGE_NAME@/csv"
# StoreRates false
# RemotePort "25"
#</Plugin>
+#<Plugin teamspeak2>
+# Host "127.0.0.1"
+# Port "51234"
+# Server "8767"
+#</Plugin>
+
#<Plugin unixsock>
# SocketFile "@prefix@/var/run/@PACKAGE_NAME@-unixsock"
# SocketGroup "collectd"
# UUIDFile "/etc/uuid"
#</Plugin>
+#<Plugin vmem>
+# Verbose false
+#</Plugin>
+
diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod
index aa4421dc7e30bc8b25efcdae494bba8dd7e0bf09..781e273823730d9265dfeea6dc15f0f3729c5ad9 100644 (file)
--- a/src/collectd.conf.pod
+++ b/src/collectd.conf.pod
Optional password needed for authentication.
+=item B<VerifyPeer> B<true|false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks
+if the C<Common Name> or a C<Subject Alternate Name> field of the SSL
+certificate matches the host name provided by the B<URL> option. If this
+identity check fails, the connection is aborted. Obviously, only works when
+connecting to a SSL enabled server. Enabled by default.
+
=item B<CACert> I<File>
File that holds one or more SSL certificates. If you want to use HTTPS you will
=back
+=head2 Plugin C<ascent>
+
+This plugin collects information about an Ascent server, a free server for the
+"World of Warcraft" game. This plugin gathers the information by fetching the
+XML status page using C<libcurl> and parses it using C<libxml2>.
+
+The configuration options are the same as for the C<apache> plugin above:
+
+=over 4
+
+=item B<URL> I<http://localhost/ascent/status/>
+
+Sets the URL of the XML status output.
+
+=item B<User> I<Username>
+
+Optional user name needed for authentication.
+
+=item B<Password> I<Password>
+
+Optional password needed for authentication.
+
+=item B<CACert> I<File>
+
+File that holds one or more SSL certificates. If you want to use HTTPS you will
+possibly need this option. What CA certificates come bundled with C<libcurl>
+and are checked by default depends on the distribution you use.
+
+=back
+
=head2 Plugin C<cpufreq>
This plugin doesn't have any options. It reads
=back
+=head2 Plugin C<disk>
+
+The C<disk> plugin collects information about the usage of physical disks and
+logical disks (partitions). Values collected are the number of octets written
+to and read from a disk or partition, the number of read/write operations
+issued to the disk and a rather complex "time" it took for these commands to be
+issued.
+
+Using the following two options you can ignore some disks or configure the
+collection only of specific disks.
+
+=over 4
+
+=item B<Disk> I<Name>
+
+Select the disk I<Name>. Whether it is collected or ignored depends on the
+B<IgnoreSelected> setting, see below. As with other plugins that use the
+daemon's ignorelist functionality, a string that starts and ends with a slash
+is interpreted as a regular expression. Examples:
+
+ Disk "sdd"
+ Disk "/hda[34]/"
+
+=item B<IgnoreSelected> B<true>|B<false>
+
+Sets whether selected disks, i.E<nbsp>e. the ones matches by any of the B<Disk>
+statements, are ignored or if all other disks are ignored. The behavior
+(hopefully) is intuitive: If no B<Disk> option is configured, all disks are
+collected. If at least one B<Disk> option is given and no B<IgnoreSelected> or
+set to B<false>, B<only> matching disks will be collected. If B<IgnoreSelected>
+is set to B<true>, all disks are collected B<except> the ones matched.
+
+=back
+
=head2 Plugin C<dns>
=over 4
Optional password needed for authentication.
+=item B<VerifyPeer> B<true|false>
+
+Enable or disable peer SSL certificate verification. See
+L<http://curl.haxx.se/docs/sslcerts.html> for details. Enabled by default.
+
+=item B<VerifyHost> B<true|false>
+
+Enable or disable peer host name verification. If enabled, the plugin checks
+if the C<Common Name> or a C<Subject Alternate Name> field of the SSL
+certificate matches the host name provided by the B<URL> option. If this
+identity check fails, the connection is aborted. Obviously, only works when
+connecting to a SSL enabled server. Enabled by default.
+
=item B<CACert> I<File>
File that holds one or more SSL certificates. If you want to use HTTPS you will
=back
+=head2 Plugin C<tail>
+
+The C<tail plugin> plugins follows logfiles, just like L<tail(1)> does, parses
+each line and dispatches found values. What is matched can be configured by the
+user using (extended) regular expressions, as described in L<regex(7)>.
+
+ <Plugin "tail">
+ <File "/var/log/exim4/mainlog">
+ Instance "exim"
+ <Match>
+ Regex "S=([1-9][0-9]*)"
+ DSType "CounterAdd"
+ Type "ipt_bytes"
+ Instance "total"
+ </Match>
+ <Match>
+ Regex "\\<R=local_user\\>"
+ DSType "CounterInc"
+ Type "email_count"
+ Instance "local_user"
+ </Match>
+ </File>
+ </Plugin>
+
+The config consists of one or more B<File> blocks, each of which configures one
+logfile to parse. Within each B<File> block, there are one or more B<Match>
+blocks, which configure a regular expression to search for.
+
+The B<Instance> option in the B<File> block may be used to set the plugin
+instance. So in the above example the plugin name C<tail-foo> would be used.
+This plugin instance is for all B<Match> blocks that B<follow> it, until the
+next B<Instance> option. This way you can extract several plugin instances from
+one logfile, handy when parsing syslog and the like.
+
+Each B<Match> block has the following options to describe how the match should
+be performed:
+
+=over 4
+
+=item B<Regex> I<regex>
+
+Sets the regular expression to use for matching against a line. The first
+subexpression has to match something that can be turned into a number by
+L<strtoll(3)> or L<strtod(3)>, depending on the value of C<CounterAdd>, see
+below. Because B<extended> regular expressions are used, you do not need to use
+backslashes for subexpressions! If in doubt, please consult L<regex(7)>. Due to
+collectd's config parsing you need to escape backslashes, though. So if you
+want to match literal parentheses you need to do the following:
+
+ Regex "SPAM \\(Score: (-?[0-9]+\\.[0-9]+)\\)"
+
+=item B<DSType> I<Type>
+
+Sets how the values are cumulated. I<Type> is one of:
+
+=over 4
+
+=item B<GaugeAverage>
+
+Calculate the average.
+
+=item B<GaugeMin>
+
+Use the smallest number only.
+
+=item B<GaugeMax>
+
+Use the greatest number only.
+
+=item B<GaugeLast>
+
+Use the last number found.
+
+=item B<CounterSet>
+
+The matched number is a counter. Simply sets the internal counter to this
+value.
+
+=item B<CounterAdd>
+
+Add the matched value to the internal counter.
+
+=item B<CounterInc>
+
+Increase the internal counter by one. This B<DSType> is the only one that does
+not use the matched subexpression, but simply counts the number of matched
+lines. Thus, you may use a regular expression without submatch in this case.
+
+=back
+
+As you'd expect the B<Gauge*> types interpret the submatch as a floating point
+number, using L<strtod(3)>. The B<CounterSet> and B<CounterAdd> interpret the
+submatch as an integer using L<strtoll(3)>. B<CounterInc> does not use the
+submatch at all and it may be omitted in this case.
+
+=item B<Type> I<Type>
+
+Sets the type used to dispatch this value. Detailed information about types and
+their configuration can be found in L<types.db(5)>.
+
+=item B<Instance> I<TypeInstance>
+
+This optional setting sets the type instance to use.
+
+=back
+
+=head2 Plugin C<teamspeak2>
+
+The C<teamspeak2 plugin> connects to the query port of a teamspeak2 server and
+polls interesting global and virtual server data. The plugin can query only one
+physical server but unlimited virtual servers. You can use the following
+options to configure it:
+
+=over 4
+
+=item B<Host> I<hostname/ip>
+
+The hostname or ip which identifies the physical server.
+Default: 127.0.0.1
+
+=item B<Port> I<port>
+
+The query port of the physical server. This needs to be a string.
+Default: "51234"
+
+=item B<Server> I<port>
+
+This option has to be added once for every virtual server the plugin should
+query. If you want to query the virtual server on port 8767 this is what the
+option would look like:
+
+ Server "8767"
+
+This option, although numeric, needs to be a string, i.E<nbsp>e. you B<must>
+use quotes around it! If no such statement is given only global information
+will be collected.
+
=head2 Plugin C<tcpconns>
The C<tcpconns plugin> counts the number of currently established TCP
=over 4
+=back
+
=item B<ListeningPorts> I<true>|I<false>
If this option is set to I<true>, statistics for all local ports for which a
=back
+=head2 Plugin C<vmem>
+
+The C<vmem> plugin collects information about the usage of virtual memory.
+Since the statistics provided by the Linux kernel are very detailed, they are
+collected very detailed. However, to get all the details, you have to switch
+them on manually. Most people just want an overview over, such as the number of
+pages read from swap space.
+
+=over 4
+
+=item B<Verbose> B<true>|B<false>
+
+Enables verbose collection of information. This will start collecting page
+"actions", e.E<nbsp>g. page allocations, (de)activations, steals and so on.
+Part of these statistics are collected on a "per zone" basis.
+
+=back
+
=head2 Plugin C<vserver>
This plugin doesn't have any options. B<VServer> support is only available for
Instance "eth0"
<Type "if_octets">
FailureMax 10000000
+ DataSource "rx"
</Type>
</Plugin>
@@ -1164,6 +1412,19 @@ infinity. If a value is less than B<FailureMin> a B<FAILURE> notification will
be created. If the value is less than B<WarningMin> but greater than (or equal
to) B<FailureMin> a B<WARNING> notification will be created.
+=item B<DataSource> I<DSName>
+
+Some data sets have more than one "data source". Interesting examples are the
+C<if_octets> data set, which has received (C<rx>) and sent (C<tx>) bytes and
+the C<disk_ops> data set, which holds C<read> and C<write> operations. The
+system load data set, C<load>, even has three data sources: C<shortterm>,
+C<midterm>, and C<longterm>.
+
+Normally, all data sources are checked against a configured threshold. If this
+is undesirable, or if you want to specify different limits for each data
+source, you can use the B<DataSource> option to have a threshold apply only to
+one data source.
+
=item B<Invert> B<true>|B<false>
If set to B<true> the range of acceptable values is inverted, i.E<nbsp>e.
diff --git a/src/collectd.pod b/src/collectd.pod
index b55362a3d12296161a5347ce0826fdef8a5341fd..fa27e77a0f0453725c80f17468c4ee4fffbf802a 100644 (file)
--- a/src/collectd.pod
+++ b/src/collectd.pod
detail. In particular those are L<collectd-email(5)>, L<collectd-exec(5)>,
L<collectd-perl(5)>, L<collectd-snmp(5)>, and L<collectd-unixsock(5)>
+=head1 SIGNALS
+
+B<collectd> accepts the following signals:
+
+=over 4
+
+=item B<SIGINT>, B<SIGTERM>
+
+These signals cause B<collectd> to shut down all plugins and terminate.
+
+=item B<SIGUSR1>
+
+This signal causes B<collectd> to signal all plugins to flush data from
+internal caches. E.E<nbsp>g. the C<rrdtool plugin> will write all pending data
+to the RRD files. This is the same as using the C<FLUSH -1> command of the
+C<unixsock plugin>.
+
=head1 SEE ALSO
L<collectd.conf(5)>,
diff --git a/src/disk.c b/src/disk.c
index 8feaa8dff6fe77d8127c20c9afdaa9fd2530d14d..5491dcbd625f99124a4abf456a22ac5976aaafea 100644 (file)
--- a/src/disk.c
+++ b/src/disk.c
/**
* collectd - src/disk.c
- * Copyright (C) 2005-2007 Florian octo Forster
+ * Copyright (C) 2005-2008 Florian octo Forster
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
#include "collectd.h"
#include "common.h"
#include "plugin.h"
+#include "utils_ignorelist.h"
#if HAVE_MACH_MACH_TYPES_H
# include <mach/mach_types.h>
# error "No applicable input method."
#endif
+static const char *config_keys[] =
+{
+ "Disk",
+ "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+static int disk_config (const char *key, const char *value)
+{
+ if (ignorelist == NULL)
+ ignorelist = ignorelist_create (/* invert = */ 1);
+ if (ignorelist == NULL)
+ return (1);
+
+ if (strcasecmp ("Disk", key) == 0)
+ {
+ ignorelist_add (ignorelist, value);
+ }
+ else if (strcasecmp ("IgnoreSelected", key) == 0)
+ {
+ int invert = 1;
+ if ((strcasecmp ("True", value) == 0)
+ || (strcasecmp ("Yes", value) == 0)
+ || (strcasecmp ("On", value) == 0))
+ invert = 0;
+ ignorelist_set_invert (ignorelist, invert);
+ }
+ else
+ {
+ return (-1);
+ }
+
+ return (0);
+} /* int disk_config */
+
static int disk_init (void)
{
#if HAVE_IOKIT_IOKITLIB_H
value_t values[2];
value_list_t vl = VALUE_LIST_INIT;
+ /* Both `ignorelist' and `plugin_instance' may be NULL. */
+ if (ignorelist_match (ignorelist, plugin_instance) != 0)
+ return;
+
values[0].counter = read;
values[1].counter = write;
void module_register (void)
{
- plugin_register_init ("disk", disk_init);
- plugin_register_read ("disk", disk_read);
+ plugin_register_config ("disk", disk_config,
+ config_keys, config_keys_num);
+ plugin_register_init ("disk", disk_init);
+ plugin_register_read ("disk", disk_read);
} /* void module_register */
diff --git a/src/exec.c b/src/exec.c
index 5eae906bee3f12ca74faeb07382488de7f36c6f6..07c35c9bcf36d0e307ebbc48a93fb1e7fafa57e1 100644 (file)
--- a/src/exec.c
+++ b/src/exec.c
} /* void exec_child }}} */
/*
- * Creates two pipes (one for reading, ong for writing), forks a child, sets up
- * the pipes so that fd_in is connected to STDIN of the child and fd_out is
- * connected to STDOUT and STDERR of the child. Then is calls `exec_child'.
+ * Creates three pipes (one for reading, one for writing and one for errors),
+ * forks a child, sets up the pipes so that fd_in is connected to STDIN of
+ * the child and fd_out is connected to STDOUT and fd_err is connected to STDERR
+ * of the child. Then is calls `exec_child'.
*/
-static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */
+static int fork_child (program_list_t *pl, int *fd_in, int *fd_out, int *fd_err) /* {{{ */
{
int fd_pipe_in[2];
int fd_pipe_out[2];
+ int fd_pipe_err[2];
+ char errbuf[1024];
int status;
int pid;
status = pipe (fd_pipe_in);
if (status != 0)
{
- char errbuf[1024];
ERROR ("exec plugin: pipe failed: %s",
sstrerror (errno, errbuf, sizeof (errbuf)));
return (-1);
status = pipe (fd_pipe_out);
if (status != 0)
{
- char errbuf[1024];
+ ERROR ("exec plugin: pipe failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ status = pipe (fd_pipe_err);
+ if (status != 0)
+ {
ERROR ("exec plugin: pipe failed: %s",
sstrerror (errno, errbuf, sizeof (errbuf)));
return (-1);
pid = fork ();
if (pid < 0)
{
- char errbuf[1024];
ERROR ("exec plugin: fork failed: %s",
sstrerror (errno, errbuf, sizeof (errbuf)));
return (-1);
fd_num = getdtablesize ();
for (fd = 0; fd < fd_num; fd++)
{
- if ((fd == fd_pipe_in[0]) || (fd == fd_pipe_out[1]))
+ if ((fd == fd_pipe_in[0])
+ || (fd == fd_pipe_out[1])
+ || (fd == fd_pipe_err[1]))
continue;
close (fd);
}
- /* If the `out' pipe has the filedescriptor STDIN we have to be careful
- * with the `dup's below. So, if this is the case we have to handle the
- * `out' pipe first. */
- if (fd_pipe_out[1] == STDIN_FILENO)
- {
- int new_fileno = (fd_pipe_in[0] == STDOUT_FILENO)
- ? STDERR_FILENO : STDOUT_FILENO;
- dup2 (fd_pipe_out[1], new_fileno);
- close (fd_pipe_out[1]);
- fd_pipe_out[1] = new_fileno;
- }
- /* Now `fd_pipe_out[1]' is either `STDOUT' or `STDERR', but definitely not
- * `STDIN_FILENO'. */
-
/* Connect the `in' pipe to STDIN */
if (fd_pipe_in[0] != STDIN_FILENO)
{
dup2 (fd_pipe_in[0], STDIN_FILENO);
close (fd_pipe_in[0]);
- fd_pipe_in[0] = STDIN_FILENO;
}
- /* Now connect the `out' pipe to STDOUT and STDERR */
+ /* Now connect the `out' pipe to STDOUT */
if (fd_pipe_out[1] != STDOUT_FILENO)
+ {
dup2 (fd_pipe_out[1], STDOUT_FILENO);
- if (fd_pipe_out[1] != STDERR_FILENO)
- dup2 (fd_pipe_out[1], STDERR_FILENO);
+ close (fd_pipe_out[1]);
+ }
- /* If the pipe has some FD that's something completely different, close it
- * now. */
- if ((fd_pipe_out[1] != STDOUT_FILENO) && (fd_pipe_out[1] != STDERR_FILENO))
+ /* Now connect the `out' pipe to STDOUT */
+ if (fd_pipe_err[1] != STDERR_FILENO)
{
- close (fd_pipe_out[1]);
- fd_pipe_out[1] = STDOUT_FILENO;
+ dup2 (fd_pipe_err[1], STDERR_FILENO);
+ close (fd_pipe_err[1]);
}
exec_child (pl);
close (fd_pipe_in[0]);
close (fd_pipe_out[1]);
+ close (fd_pipe_err[1]);
if (fd_in != NULL)
*fd_in = fd_pipe_in[1];
else
close (fd_pipe_out[0]);
+ if (fd_err != NULL)
+ *fd_err = fd_pipe_err[0];
+ else
+ close (fd_pipe_err[0]);
+
return (pid);
} /* int fork_child }}} */
static void *exec_read_one (void *arg) /* {{{ */
{
program_list_t *pl = (program_list_t *) arg;
- int fd;
- FILE *fh;
- char buffer[1024];
+ int fd, fd_err, highest_fd;
+ fd_set fdset, copy;
int status;
+ char buffer[1200]; /* if not completely read */
+ char buffer_err[1024];
+ char *pbuffer = buffer;
+ char *pbuffer_err = buffer_err;
- status = fork_child (pl, NULL, &fd);
+ status = fork_child (pl, NULL, &fd, &fd_err);
if (status < 0)
pthread_exit ((void *) 1);
pl->pid = status;
assert (pl->pid != 0);
- fh = fdopen (fd, "r");
- if (fh == NULL)
- {
- char errbuf[1024];
- ERROR ("exec plugin: fdopen (%i) failed: %s", fd,
- sstrerror (errno, errbuf, sizeof (errbuf)));
- kill (pl->pid, SIGTERM);
- pl->pid = 0;
- close (fd);
- pthread_exit ((void *) 1);
- }
+ FD_ZERO( &fdset );
+ FD_SET(fd, &fdset);
+ FD_SET(fd_err, &fdset);
- buffer[0] = '\0';
- while (fgets (buffer, sizeof (buffer), fh) != NULL)
+ /* Determine the highest file descriptor */
+ highest_fd = (fd > fd_err) ? fd : fd_err;
+
+ /* We use a copy of fdset, as select modifies it */
+ copy = fdset;
+
+ while (select(highest_fd + 1, ©, NULL, NULL, NULL ) > 0)
{
int len;
- len = strlen (buffer);
+ if (FD_ISSET(fd, ©))
+ {
+ char *pnl;
- /* Remove newline from end. */
- while ((len > 0) && ((buffer[len - 1] == '\n')
- || (buffer[len - 1] == '\r')))
- buffer[--len] = '\0';
+ len = read(fd, pbuffer, sizeof(buffer) - 1 - (pbuffer - buffer));
- DEBUG ("exec plugin: exec_read_one: buffer = %s", buffer);
+ if (len < 0)
+ {
+ if (errno == EAGAIN || errno == EINTR) continue;
+ break;
+ }
+ else if (len == 0) break; /* We've reached EOF */
- parse_line (buffer);
- } /* while (fgets) */
+ pbuffer[len] = '\0';
- fclose (fh);
+ len += pbuffer - buffer;
+ pbuffer = buffer;
+
+ while ((pnl = strchr(pbuffer, '\n')))
+ {
+ *pnl = '\0';
+ if (*(pnl-1) == '\r' ) *(pnl-1) = '\0';
+
+ parse_line (pbuffer);
+
+ pbuffer = ++pnl;
+ }
+ /* not completely read ? */
+ if (pbuffer - buffer < len)
+ {
+ len -= pbuffer - buffer;
+ memmove(buffer, pbuffer, len);
+ pbuffer = buffer + len;
+ }
+ else
+ pbuffer = buffer;
+ }
+ else if (FD_ISSET(fd_err, ©))
+ {
+ char *pnl;
+
+ len = read(fd_err, pbuffer_err, sizeof(buffer_err) - 1 - (pbuffer_err - buffer_err));
+
+ if (len < 0)
+ {
+ if (errno == EAGAIN || errno == EINTR) continue;
+ break;
+ }
+ else if (len == 0) break; /* We've reached EOF */
+
+ pbuffer_err[len] = '\0';
+
+ len += pbuffer_err - buffer_err;
+ pbuffer_err = buffer_err;
+
+ while ((pnl = strchr(pbuffer_err, '\n')))
+ {
+ *pnl = '\0';
+ if (*(pnl-1) == '\r' ) *(pnl-1) = '\0';
+
+ ERROR ("exec plugin: exec_read_one: error = %s", pbuffer_err);
+
+ pbuffer_err = ++pnl;
+ }
+ /* not completely read ? */
+ if (pbuffer_err - buffer_err < len)
+ {
+ len -= pbuffer_err - buffer_err;
+ memmove(buffer_err, pbuffer_err, len);
+ pbuffer_err = buffer_err + len;
+ }
+ else
+ pbuffer_err = buffer_err;
+ }
+ /* reset copy */
+ copy = fdset;
+ }
if (waitpid (pl->pid, &status, 0) > 0)
pl->status = status;
pl->flags &= ~PL_RUNNING;
pthread_mutex_unlock (&pl_lock);
+ close (fd);
+ close (fd_err);
+
pthread_exit ((void *) 0);
return (NULL);
} /* void *exec_read_one }}} */
int status;
const char *severity;
- pid = fork_child (pl, &fd, NULL);
+ pid = fork_child (pl, &fd, NULL, NULL);
if (pid < 0) {
sfree (arg);
pthread_exit ((void *) 1);
diff --git a/src/ipmi.c b/src/ipmi.c
--- /dev/null
+++ b/src/ipmi.c
@@ -0,0 +1,554 @@
+/**
+ * collectd - src/ipmi.c
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_ignorelist.h"
+
+#include <pthread.h>
+
+#include <OpenIPMI/ipmiif.h>
+#include <OpenIPMI/ipmi_err.h>
+#include <OpenIPMI/ipmi_posix.h>
+#include <OpenIPMI/ipmi_conn.h>
+#include <OpenIPMI/ipmi_smi.h>
+
+/*
+ * Private data types
+ */
+struct c_ipmi_sensor_list_s;
+typedef struct c_ipmi_sensor_list_s c_ipmi_sensor_list_t;
+
+struct c_ipmi_sensor_list_s
+{
+ ipmi_sensor_id_t sensor_id;
+ c_ipmi_sensor_list_t *next;
+};
+
+/*
+ * Module global variables
+ */
+static pthread_mutex_t sensor_list_lock = PTHREAD_MUTEX_INITIALIZER;
+static c_ipmi_sensor_list_t *sensor_list = NULL;
+
+static int c_ipmi_active = 0;
+static pthread_t thread_id = (pthread_t) 0;
+
+static const char *config_keys[] =
+{
+ "Sensor",
+ "IgnoreSelected"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static ignorelist_t *ignorelist = NULL;
+
+/*
+ * Misc private functions
+ */
+static void c_ipmi_error (const char *func, int status)
+{
+ char errbuf[4096];
+
+ memset (errbuf, 0, sizeof (errbuf));
+
+ if (IPMI_IS_OS_ERR (status))
+ {
+ sstrerror_r (IPMI_GET_OS_ERR (status), errbuf, sizeof (errbuf));
+ }
+ else if (IPMI_IS_IPMI_ERR (status))
+ {
+ ipmi_get_error_string (IPMI_GET_IPMI_ERR (status), errbuf, sizeof (errbuf));
+ }
+
+ if (errbuf[0] == 0)
+ {
+ ssnprintf (errbuf, sizeof (errbuf), "Unknown error %#x", status);
+ }
+ errbuf[sizeof (errbuf) - 1] = 0;
+
+ ERROR ("ipmi plugin: %s failed: %s", func, errbuf);
+} /* void c_ipmi_error */
+
+/*
+ * Sensor handlers
+ */
+/* Prototype for sensor_list_remove, so sensor_read_handler can call it. */
+static int sensor_list_remove (ipmi_sensor_t *sensor);
+
+static void sensor_read_handler (ipmi_sensor_t *sensor,
+ int err,
+ enum ipmi_value_present_e value_present,
+ unsigned int raw_value,
+ double value,
+ ipmi_states_t *states,
+ void *user_data)
+{
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+
+ char sensor_name[IPMI_SENSOR_NAME_LEN];
+ char *sensor_name_ptr;
+ int sensor_type;
+ const char *type;
+
+ memset (sensor_name, 0, sizeof (sensor_name));
+ ipmi_sensor_get_name (sensor, sensor_name, sizeof (sensor_name));
+ sensor_name[sizeof (sensor_name) - 1] = 0;
+
+ sensor_name_ptr = strstr (sensor_name, ").");
+ if (sensor_name_ptr == NULL)
+ sensor_name_ptr = sensor_name;
+ else
+ sensor_name_ptr += 2;
+
+ if (err != 0)
+ {
+ INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+ "because it failed with status %#x.",
+ sensor_name_ptr, err);
+ sensor_list_remove (sensor);
+ return;
+ }
+
+ if (value_present != IPMI_BOTH_VALUES_PRESENT)
+ {
+ INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+ "because it provides %s. If you need this sensor, "
+ "please file a bug report.",
+ sensor_name_ptr,
+ (value_present == IPMI_RAW_VALUE_PRESENT)
+ ? "only the raw value"
+ : "no value");
+ sensor_list_remove (sensor);
+ return;
+ }
+
+ /* Both `ignorelist' and `plugin_instance' may be NULL. */
+ if (ignorelist_match (ignorelist, sensor_name_ptr) != 0)
+ {
+ sensor_list_remove (sensor);
+ return;
+ }
+
+ /* FIXME: Use rate unit or base unit to scale the value */
+
+ sensor_type = ipmi_sensor_get_sensor_type (sensor);
+ switch (sensor_type)
+ {
+ case IPMI_SENSOR_TYPE_TEMPERATURE:
+ type = "temperature";
+ break;
+
+ case IPMI_SENSOR_TYPE_VOLTAGE:
+ type = "voltage";
+ break;
+
+ case IPMI_SENSOR_TYPE_CURRENT:
+ type = "current";
+ break;
+
+ case IPMI_SENSOR_TYPE_FAN:
+ type = "fanspeed";
+ break;
+
+ default:
+ {
+ const char *sensor_type_str;
+
+ sensor_type_str = ipmi_sensor_get_sensor_type_string (sensor);
+ INFO ("ipmi plugin: sensor_read_handler: Removing sensor %s, "
+ "because I don't know how to handle its type (%#x, %s). "
+ "If you need this sensor, please file a bug report.",
+ sensor_name_ptr, sensor_type, sensor_type_str);
+ sensor_list_remove (sensor);
+ return;
+ }
+ } /* switch (sensor_type) */
+
+ values[0].gauge = value;
+
+ vl.values = values;
+ vl.values_len = 1;
+ vl.time = time (NULL);
+
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "ipmi", sizeof (vl.plugin));
+ sstrncpy (vl.type_instance, sensor_name_ptr, sizeof (vl.type_instance));
+
+ plugin_dispatch_values (type, &vl);
+} /* void sensor_read_handler */
+
+static int sensor_list_add (ipmi_sensor_t *sensor)
+{
+ ipmi_sensor_id_t sensor_id;
+ c_ipmi_sensor_list_t *list_item;
+ c_ipmi_sensor_list_t *list_prev;
+
+ sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+ pthread_mutex_lock (&sensor_list_lock);
+
+ list_prev = NULL;
+ for (list_item = sensor_list;
+ list_item != NULL;
+ list_item = list_item->next)
+ {
+ if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+ break;
+ list_prev = list_item;
+ } /* for (list_item) */
+
+ if (list_item != NULL)
+ {
+ pthread_mutex_unlock (&sensor_list_lock);
+ return (0);
+ }
+
+ list_item = (c_ipmi_sensor_list_t *) calloc (1, sizeof (c_ipmi_sensor_list_t));
+ if (list_item == NULL)
+ {
+ pthread_mutex_unlock (&sensor_list_lock);
+ return (-1);
+ }
+
+ list_item->sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+ if (list_prev != NULL)
+ list_prev->next = list_item;
+ else
+ sensor_list = list_item;
+
+ pthread_mutex_unlock (&sensor_list_lock);
+
+ return (0);
+} /* int sensor_list_add */
+
+static int sensor_list_remove (ipmi_sensor_t *sensor)
+{
+ ipmi_sensor_id_t sensor_id;
+ c_ipmi_sensor_list_t *list_item;
+ c_ipmi_sensor_list_t *list_prev;
+
+ sensor_id = ipmi_sensor_convert_to_id (sensor);
+
+ pthread_mutex_lock (&sensor_list_lock);
+
+ list_prev = NULL;
+ for (list_item = sensor_list;
+ list_item != NULL;
+ list_item = list_item->next)
+ {
+ if (ipmi_cmp_sensor_id (sensor_id, list_item->sensor_id) == 0)
+ break;
+ list_prev = list_item;
+ } /* for (list_item) */
+
+ if (list_item == NULL)
+ {
+ pthread_mutex_unlock (&sensor_list_lock);
+ return (-1);
+ }
+
+ if (list_prev == NULL)
+ sensor_list = list_item->next;
+ else
+ list_prev->next = list_item->next;
+
+ list_prev = NULL;
+ list_item->next = NULL;
+
+ pthread_mutex_unlock (&sensor_list_lock);
+
+ free (list_item);
+ return (0);
+} /* int sensor_list_remove */
+
+static int sensor_list_read_all (void)
+{
+ c_ipmi_sensor_list_t *list_item;
+
+ pthread_mutex_lock (&sensor_list_lock);
+
+ for (list_item = sensor_list;
+ list_item != NULL;
+ list_item = list_item->next)
+ {
+ ipmi_sensor_id_get_reading (list_item->sensor_id,
+ sensor_read_handler, /* user data = */ NULL);
+ } /* for (list_item) */
+
+ pthread_mutex_unlock (&sensor_list_lock);
+
+ return (0);
+} /* int sensor_list_read_all */
+
+static int sensor_list_remove_all (void)
+{
+ c_ipmi_sensor_list_t *list_item;
+
+ pthread_mutex_lock (&sensor_list_lock);
+
+ list_item = sensor_list;
+ sensor_list = NULL;
+
+ pthread_mutex_unlock (&sensor_list_lock);
+
+ while (list_item != NULL)
+ {
+ c_ipmi_sensor_list_t *list_next = list_item->next;
+
+ free (list_item);
+
+ list_item = list_next;
+ } /* while (list_item) */
+
+ return (0);
+} /* int sensor_list_remove_all */
+
+/*
+ * Entity handlers
+ */
+static void entity_sensor_update_handler (enum ipmi_update_e op,
+ ipmi_entity_t *entity,
+ ipmi_sensor_t *sensor,
+ void *user_data)
+{
+ /* TODO: Ignore sensors we cannot read */
+
+ if ((op == IPMI_ADDED) || (op == IPMI_CHANGED))
+ {
+ /* Will check for duplicate entries.. */
+ sensor_list_add (sensor);
+ }
+ else if (op == IPMI_DELETED)
+ {
+ sensor_list_remove (sensor);
+ }
+} /* void entity_sensor_update_handler */
+
+/*
+ * Domain handlers
+ */
+static void domain_entity_update_handler (enum ipmi_update_e op,
+ ipmi_domain_t *domain,
+ ipmi_entity_t *entity,
+ void *user_data)
+{
+ int status;
+
+ if (op == IPMI_ADDED)
+ {
+ status = ipmi_entity_add_sensor_update_handler (entity,
+ entity_sensor_update_handler, /* user data = */ NULL);
+ if (status != 0)
+ {
+ c_ipmi_error ("ipmi_entity_add_sensor_update_handler", status);
+ }
+ }
+ else if (op == IPMI_DELETED)
+ {
+ status = ipmi_entity_remove_sensor_update_handler (entity,
+ entity_sensor_update_handler, /* user data = */ NULL);
+ if (status != 0)
+ {
+ c_ipmi_error ("ipmi_entity_remove_sensor_update_handler", status);
+ }
+ }
+} /* void domain_entity_update_handler */
+
+static void domain_connection_change_handler (ipmi_domain_t *domain,
+ int err,
+ unsigned int conn_num,
+ unsigned int port_num,
+ int still_connected,
+ void *user_data)
+{
+ int status;
+
+ printf ("domain_connection_change_handler (domain = %p, err = %i, "
+ "conn_num = %u, port_num = %u, still_connected = %i, "
+ "user_data = %p);\n",
+ (void *) domain, err, conn_num, port_num, still_connected, user_data);
+
+ status = ipmi_domain_add_entity_update_handler (domain,
+ domain_entity_update_handler, /* user data = */ NULL);
+ if (status != 0)
+ {
+ c_ipmi_error ("ipmi_domain_add_entity_update_handler", status);
+ }
+} /* void domain_connection_change_handler */
+
+static int thread_init (os_handler_t **ret_os_handler)
+{
+ os_handler_t *os_handler;
+ ipmi_open_option_t open_option[1];
+ ipmi_con_t *smi_connection = NULL;
+ ipmi_domain_id_t domain_id;
+ int status;
+
+ os_handler = ipmi_posix_thread_setup_os_handler (SIGUSR2);
+ if (os_handler == NULL)
+ {
+ ERROR ("ipmi plugin: ipmi_posix_thread_setup_os_handler failed.");
+ return (-1);
+ }
+
+ ipmi_init (os_handler);
+
+ status = ipmi_smi_setup_con (/* if_num = */ 0,
+ os_handler,
+ /* user data = */ NULL,
+ &smi_connection);
+ if (status != 0)
+ {
+ c_ipmi_error ("ipmi_smi_setup_con", status);
+ return (-1);
+ }
+
+ memset (open_option, 0, sizeof (open_option));
+ open_option[0].option = IPMI_OPEN_OPTION_ALL;
+ open_option[0].ival = 1;
+
+ status = ipmi_open_domain ("mydomain", &smi_connection, /* num_con = */ 1,
+ domain_connection_change_handler, /* user data = */ NULL,
+ /* domain_fully_up_handler = */ NULL, /* user data = */ NULL,
+ open_option, sizeof (open_option) / sizeof (open_option[0]),
+ &domain_id);
+ if (status != 0)
+ {
+ c_ipmi_error ("ipmi_open_domain", status);
+ return (-1);
+ }
+
+ *ret_os_handler = os_handler;
+ return (0);
+} /* int thread_init */
+
+static void *thread_main (void *user_data)
+{
+ int status;
+ os_handler_t *os_handler = NULL;
+
+ status = thread_init (&os_handler);
+ if (status != 0)
+ {
+ fprintf (stderr, "ipmi plugin: thread_init failed.\n");
+ return ((void *) -1);
+ }
+
+ while (c_ipmi_active != 0)
+ {
+ struct timeval tv = { 1, 0 };
+ os_handler->perform_one_op (os_handler, &tv);
+ }
+
+ ipmi_posix_thread_free_os_handler (os_handler);
+
+ return ((void *) 0);
+} /* void *thread_main */
+
+static int c_ipmi_config (const char *key, const char *value)
+{
+ if (ignorelist == NULL)
+ ignorelist = ignorelist_create (/* invert = */ 1);
+ if (ignorelist == NULL)
+ return (1);
+
+ if (strcasecmp ("Sensor", key) == 0)
+ {
+ ignorelist_add (ignorelist, value);
+ }
+ else if (strcasecmp ("IgnoreSelected", key) == 0)
+ {
+ int invert = 1;
+ if ((strcasecmp ("True", value) == 0)
+ || (strcasecmp ("Yes", value) == 0)
+ || (strcasecmp ("On", value) == 0))
+ invert = 0;
+ ignorelist_set_invert (ignorelist, invert);
+ }
+ else
+ {
+ return (-1);
+ }
+
+ return (0);
+} /* int c_ipmi_config */
+
+static int c_ipmi_init (void)
+{
+ int status;
+
+ c_ipmi_active = 1;
+
+ status = pthread_create (&thread_id, /* attr = */ NULL, thread_main,
+ /* user data = */ NULL);
+ if (status != 0)
+ {
+ c_ipmi_active = 0;
+ thread_id = (pthread_t) 0;
+ ERROR ("ipmi plugin: pthread_create failed.");
+ return (-1);
+ }
+
+ return (0);
+} /* int c_ipmi_init */
+
+static int c_ipmi_read (void)
+{
+ if ((c_ipmi_active == 0) || (thread_id == (pthread_t) 0))
+ {
+ INFO ("ipmi plugin: c_ipmi_read: I'm not active, returning false.");
+ return (-1);
+ }
+
+ sensor_list_read_all ();
+
+ return (0);
+} /* int c_ipmi_read */
+
+static int c_ipmi_shutdown (void)
+{
+ c_ipmi_active = 0;
+
+ if (thread_id != (pthread_t) 0)
+ {
+ pthread_join (thread_id, NULL);
+ thread_id = (pthread_t) 0;
+ }
+
+ sensor_list_remove_all ();
+
+ return (0);
+} /* int c_ipmi_shutdown */
+
+void module_register (void)
+{
+ plugin_register_config ("ipmi", c_ipmi_config,
+ config_keys, config_keys_num);
+ plugin_register_init ("ipmi", c_ipmi_init);
+ plugin_register_read ("ipmi", c_ipmi_read);
+ plugin_register_shutdown ("ipmi", c_ipmi_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 fdm=marker et : */
diff --git a/src/network.c b/src/network.c
index b67928c7c2fe6a10aec868ba61c5c7fbe512f6c9..e15036424d880316ec58bcdffd7d9d3cc76ff2ae 100644 (file)
--- a/src/network.c
+++ b/src/network.c
return (0);
} /* int network_init */
+static int network_flush (int timeout)
+{
+ pthread_mutex_lock (&send_buffer_lock);
+
+ if (((time (NULL) - cache_flush_last) >= timeout)
+ && (send_buffer_fill > 0))
+ {
+ flush_buffer ();
+ }
+
+ pthread_mutex_unlock (&send_buffer_lock);
+
+ return (0);
+} /* int network_flush */
+
void module_register (void)
{
plugin_register_config ("network", network_config,
config_keys, config_keys_num);
plugin_register_init ("network", network_init);
+ plugin_register_flush ("network", network_flush);
} /* void module_register */
diff --git a/src/nginx.c b/src/nginx.c
index a44e8a5778bb9de85802c599a91574745048458e..3b107fb72ad209c41d08c22ded54fe349310d305 100644 (file)
--- a/src/nginx.c
+++ b/src/nginx.c
#include <curl/curl.h>
-static char *url = NULL;
-static char *user = NULL;
-static char *pass = NULL;
-static char *cacert = NULL;
+static char *url = NULL;
+static char *user = NULL;
+static char *pass = NULL;
+static char *verify_peer = NULL;
+static char *verify_host = NULL;
+static char *cacert = NULL;
static CURL *curl = NULL;
"URL",
"User",
"Password",
+ "VerifyPeer",
+ "VerifyHost",
"CACert"
};
static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
return (config_set (&user, value));
else if (strcasecmp (key, "password") == 0)
return (config_set (&pass, value));
+ else if (strcasecmp (key, "verifypeer") == 0)
+ return (config_set (&verify_peer, value));
+ else if (strcasecmp (key, "verifyhost") == 0)
+ return (config_set (&verify_host, value));
else if (strcasecmp (key, "cacert") == 0)
return (config_set (&cacert, value));
else
curl_easy_setopt (curl, CURLOPT_URL, url);
}
+ if ((verify_peer == NULL) || (strcmp (verify_peer, "true") == 0))
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 1);
+ }
+ else
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYPEER, 0);
+ }
+
+ if ((verify_host == NULL) || (strcmp (verify_host, "true") == 0))
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 2);
+ }
+ else
+ {
+ curl_easy_setopt (curl, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+
if (cacert != NULL)
{
curl_easy_setopt (curl, CURLOPT_CAINFO, cacert);
diff --git a/src/plugin.c b/src/plugin.c
index 8b2803dfa49b345b95d4da9f586740e6105af69a..1aad97c1d7f073931de749bb9676874de3d2b59c 100644 (file)
--- a/src/plugin.c
+++ b/src/plugin.c
/**
* collectd - src/plugin.c
- * Copyright (C) 2005,2006 Florian octo Forster
+ * Copyright (C) 2005-2008 Florian octo Forster
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
*
* Authors:
* Florian octo Forster <octo at verplant.org>
+ * Sebastian Harl <sh at tokkee.org>
**/
#include "collectd.h"
static llist_t *list_init;
static llist_t *list_read;
static llist_t *list_write;
+static llist_t *list_flush;
static llist_t *list_shutdown;
static llist_t *list_log;
static llist_t *list_notification;
return (register_callback (&list_write, name, (void *) callback));
} /* int plugin_register_write */
+int plugin_register_flush (const char *name, int (*callback) (const int))
+{
+ return (register_callback (&list_flush, name, (void *) callback));
+} /* int plugin_register_flush */
+
int plugin_register_shutdown (char *name,
int (*callback) (void))
{
return (plugin_unregister (list_write, name));
}
+int plugin_unregister_flush (const char *name)
+{
+ return (plugin_unregister (list_flush, name));
+}
+
int plugin_unregister_shutdown (const char *name)
{
return (plugin_unregister (list_shutdown, name));
pthread_mutex_unlock (&read_lock);
} /* void plugin_read_all */
+int plugin_flush_one (int timeout, const char *name)
+{
+ int (*callback) (int);
+ llentry_t *le;
+ int status;
+
+ if (list_flush == NULL)
+ return (-1);
+
+ le = llist_search (list_flush, name);
+ if (le == NULL)
+ return (-1);
+ callback = (int (*) (int)) le->value;
+
+ status = (*callback) (timeout);
+
+ return (status);
+} /* int plugin_flush_ont */
+
+void plugin_flush_all (int timeout)
+{
+ int (*callback) (int);
+ llentry_t *le;
+
+ if (list_flush == NULL)
+ return;
+
+ le = llist_head (list_flush);
+ while (le != NULL)
+ {
+ callback = (int (*) (int)) le->value;
+ le = le->next;
+
+ (*callback) (timeout);
+ }
+} /* void plugin_flush_all */
+
void plugin_shutdown_all (void)
{
int (*callback) (void);
diff --git a/src/plugin.h b/src/plugin.h
index 25c745cbbccede74eb1398da38653d129566b248..7b59930df3f7ae53eea9690f74d7b998846948ed 100644 (file)
--- a/src/plugin.h
+++ b/src/plugin.h
#define PLUGIN_H
/**
* collectd - src/plugin.h
- * Copyright (C) 2005-2007 Florian octo Forster
+ * Copyright (C) 2005-2008 Florian octo Forster
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
*
* Authors:
* Florian octo Forster <octo at verplant.org>
+ * Sebastian Harl <sh at tokkee.org>
**/
#include "collectd.h"
void plugin_init_all (void);
void plugin_read_all (void);
+void plugin_flush_all (int timeout);
void plugin_shutdown_all (void);
+int plugin_flush_one (int timeout, const char *name);
+
/*
* The `plugin_register_*' functions are used to make `config', `init',
* `read', `write' and `shutdown' functions known to the plugin
int (*callback) (void));
int plugin_register_write (const char *name,
int (*callback) (const data_set_t *ds, const value_list_t *vl));
+int plugin_register_flush (const char *name,
+ int (*callback) (const int));
int plugin_register_shutdown (char *name,
int (*callback) (void));
int plugin_register_data_set (const data_set_t *ds);
int plugin_unregister_init (const char *name);
int plugin_unregister_read (const char *name);
int plugin_unregister_write (const char *name);
+int plugin_unregister_flush (const char *name);
int plugin_unregister_shutdown (const char *name);
int plugin_unregister_data_set (const char *name);
int plugin_unregister_log (const char *name);
diff --git a/src/powerdns.c b/src/powerdns.c
--- /dev/null
+++ b/src/powerdns.c
@@ -0,0 +1,775 @@
+/**
+ * collectd - src/powerdns.c
+ * Copyright (C) 2007-2008 C-Ware, Inc.
+ * Copyright (C) 2008 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Luke Heberling <lukeh at c-ware.com>
+ * Florian Forster <octo at verplant.org>
+ *
+ * DESCRIPTION
+ * Queries a PowerDNS control socket for statistics
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "configfile.h"
+#include "utils_llist.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#ifndef UNIX_PATH_MAX
+# define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
+#endif
+#define FUNC_ERROR(func) do { char errbuf[1024]; ERROR ("powerdns plugin: %s failed: %s", func, sstrerror (errno, errbuf, sizeof (errbuf))); } while (0)
+
+#define SERVER_SOCKET "/var/run/pdns.controlsocket"
+#define SERVER_COMMAND "SHOW *"
+
+#define RECURSOR_SOCKET "/var/run/pdns_recursor.controlsocket"
+#define RECURSOR_COMMAND "get all-outqueries answers0-1 " /* {{{ */ \
+ "answers100-1000 answers10-100 answers1-10 answers-slow cache-entries " \
+ "cache-hits cache-misses chain-resends client-parse-errors " \
+ "concurrent-queries dlg-only-drops ipv6-outqueries negcache-entries " \
+ "noerror-answers nsset-invalidations nsspeeds-entries nxdomain-answers " \
+ "outgoing-timeouts qa-latency questions resource-limits " \
+ "server-parse-errors servfail-answers spoof-prevents sys-msec " \
+ "tcp-client-overflow tcp-outqueries tcp-questions throttled-out " \
+ "throttled-outqueries throttle-entries unauthorized-tcp unauthorized-udp " \
+ "unexpected-packets unreachables user-msec" /* }}} */
+
+struct list_item_s;
+typedef struct list_item_s list_item_t;
+
+struct list_item_s
+{
+ int (*func) (list_item_t *item);
+ char *instance;
+ char *command;
+ struct sockaddr_un sockaddr;
+ int socktype;
+};
+
+struct statname_lookup_s
+{
+ char *name;
+ char *type;
+ char *type_instance;
+};
+typedef struct statname_lookup_s statname_lookup_t;
+
+/* Description of statistics returned by the recursor: {{{
+all-outqueries counts the number of outgoing UDP queries since starting
+answers0-1 counts the number of queries answered within 1 milisecond
+answers100-1000 counts the number of queries answered within 1 second
+answers10-100 counts the number of queries answered within 100 miliseconds
+answers1-10 counts the number of queries answered within 10 miliseconds
+answers-slow counts the number of queries answered after 1 second
+cache-entries shows the number of entries in the cache
+cache-hits counts the number of cache hits since starting
+cache-misses counts the number of cache misses since starting
+chain-resends number of queries chained to existing outstanding query
+client-parse-errors counts number of client packets that could not be parsed
+concurrent-queries shows the number of MThreads currently running
+dlg-only-drops number of records dropped because of delegation only setting
+negcache-entries shows the number of entries in the Negative answer cache
+noerror-answers counts the number of times it answered NOERROR since starting
+nsspeeds-entries shows the number of entries in the NS speeds map
+nsset-invalidations number of times an nsset was dropped because it no longer worked
+nxdomain-answers counts the number of times it answered NXDOMAIN since starting
+outgoing-timeouts counts the number of timeouts on outgoing UDP queries since starting
+qa-latency shows the current latency average
+questions counts all End-user initiated queries with the RD bit set
+resource-limits counts number of queries that could not be performed because of resource limits
+server-parse-errors counts number of server replied packets that could not be parsed
+servfail-answers counts the number of times it answered SERVFAIL since starting
+spoof-prevents number of times PowerDNS considered itself spoofed, and dropped the data
+sys-msec number of CPU milliseconds spent in 'system' mode
+tcp-client-overflow number of times an IP address was denied TCP access because it already had too many connections
+tcp-outqueries counts the number of outgoing TCP queries since starting
+tcp-questions counts all incoming TCP queries (since starting)
+throttled-out counts the number of throttled outgoing UDP queries since starting
+throttle-entries shows the number of entries in the throttle map
+unauthorized-tcp number of TCP questions denied because of allow-from restrictions
+unauthorized-udp number of UDP questions denied because of allow-from restrictions
+unexpected-packets number of answers from remote servers that were unexpected (might point to spoofing)
+uptime number of seconds process has been running (since 3.1.5)
+user-msec number of CPU milliseconds spent in 'user' mode
+}}} */
+
+statname_lookup_t lookup_table[] = /* {{{ */
+{
+ /*
+ * Recursor statistics
+ */
+ /*
+ * corrupt-packets
+ * deferred-cache-inserts
+ * deferred-cache-lookup
+ * qsize-q
+ * servfail-packets
+ * timedout-packets
+ * udp4-answers
+ * udp4-queries
+ * udp6-answers
+ * udp6-queries
+ */
+ /* Questions */
+ {"recursing-questions", "dns_question", "recurse"},
+ {"tcp-queries", "dns_question", "tcp"},
+ {"udp-queries", "dns_question", "udp"},
+
+ /* Answers */
+ {"recursing-answers", "dns_answer", "recurse"},
+ {"tcp-answers", "dns_answer", "tcp"},
+ {"udp-answers", "dns_answer", "udp"},
+
+ /* Cache stuff */
+ {"packetcache-hit", "cache_result", "packet-hit"},
+ {"packetcache-miss", "cache_result", "packet-miss"},
+ {"packetcache-size", "cache_size", "packet"},
+ {"query-cache-hit", "cache_result", "query-hit"},
+ {"query-cache-miss", "cache_result", "query-miss"},
+
+ /* Latency */
+ {"latency", "latency", NULL},
+
+ /*
+ * Recursor statistics
+ */
+ /* Answers by return code */
+ {"noerror-answers", "dns_rcode", "NOERROR"},
+ {"nxdomain-answers", "dns_rcode", "NXDOMAIN"},
+ {"servfail-answers", "dns_rcode", "SERVFAIL"},
+
+ /* CPU utilization */
+ {"sys-msec", "cpu", "system"},
+ {"user-msec", "cpu", "user"},
+
+ /* Question-to-answer latency */
+ {"qa-latency", "latency", NULL},
+
+ /* Cache */
+ {"cache-entries", "cache_size", NULL},
+ {"cache-hits", "cache_result", "hit"},
+ {"cache-misses", "cache_result", "miss"},
+
+ /* Total number of questions.. */
+ {"questions", "dns_qtype", "total"}
+}; /* }}} */
+int lookup_table_length = STATIC_ARRAY_SIZE (lookup_table);
+
+static llist_t *list = NULL;
+
+#define PDNS_LOCAL_SOCKPATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-powerdns"
+static char *local_sockpath = NULL;
+
+/* <http://doc.powerdns.com/recursor-stats.html> */
+static void submit (const char *plugin_instance, /* {{{ */
+ const char *pdns_type, const char *value)
+{
+ value_list_t vl = VALUE_LIST_INIT;
+ value_t values[1];
+
+ const char *type = NULL;
+ const char *type_instance = NULL;
+ const data_set_t *ds;
+
+ int i;
+
+ for (i = 0; i < lookup_table_length; i++)
+ if (strcmp (lookup_table[i].name, pdns_type) == 0)
+ break;
+
+ if (lookup_table[i].type == NULL)
+ return;
+
+ if (i >= lookup_table_length)
+ {
+ DEBUG ("powerdns plugin: submit: Not found in lookup table: %s = %s;",
+ pdns_type, value);
+ return;
+ }
+
+ type = lookup_table[i].type;
+ type_instance = lookup_table[i].type_instance;
+
+ ds = plugin_get_ds (type);
+ if (ds == NULL)
+ {
+ ERROR ("powerdns plugin: The lookup table returned type `%s', "
+ "but I cannot find it via `plugin_get_ds'.",
+ type);
+ return;
+ }
+
+ if (ds->ds_num != 1)
+ {
+ ERROR ("powerdns plugin: type `%s' has %i data sources, "
+ "but I can only handle one.",
+ type, ds->ds_num);
+ return;
+ }
+
+ if (ds->ds[0].type == DS_TYPE_GAUGE)
+ {
+ char *endptr = NULL;
+
+ values[0].gauge = strtod (value, &endptr);
+
+ if (endptr == value)
+ {
+ ERROR ("powerdns plugin: Cannot convert `%s' "
+ "to a floating point number.", value);
+ return;
+ }
+ }
+ else
+ {
+ char *endptr = NULL;
+
+ values[0].counter = strtoll (value, &endptr, 0);
+ if (endptr == value)
+ {
+ ERROR ("powerdns plugin: Cannot convert `%s' "
+ "to an integer number.", value);
+ return;
+ }
+ }
+
+ vl.values = values;
+ vl.values_len = 1;
+ vl.time = time (NULL);
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "powerdns", sizeof (vl.plugin));
+ if (type_instance != NULL)
+ sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+ sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+
+ plugin_dispatch_values (type, &vl);
+} /* }}} static void submit */
+
+static int powerdns_get_data_dgram (list_item_t *item, /* {{{ */
+ char **ret_buffer,
+ size_t *ret_buffer_size)
+{
+ int sd;
+ int status;
+
+ char temp[4096];
+ char *buffer = NULL;
+ size_t buffer_size = 0;
+
+ struct sockaddr_un sa_unix;
+
+ sd = socket (PF_UNIX, item->socktype, 0);
+ if (sd < 0)
+ {
+ FUNC_ERROR ("socket");
+ return (-1);
+ }
+
+ memset (&sa_unix, 0, sizeof (sa_unix));
+ sa_unix.sun_family = AF_UNIX;
+ strncpy (sa_unix.sun_path,
+ (local_sockpath != NULL) ? local_sockpath : PDNS_LOCAL_SOCKPATH,
+ sizeof (sa_unix.sun_path));
+ sa_unix.sun_path[sizeof (sa_unix.sun_path) - 1] = 0;
+
+ status = unlink (sa_unix.sun_path);
+ if ((status != 0) && (errno != ENOENT))
+ {
+ FUNC_ERROR ("unlink");
+ close (sd);
+ return (-1);
+ }
+
+ do /* while (0) */
+ {
+ /* We need to bind to a specific path, because this is a datagram socket
+ * and otherwise the daemon cannot answer. */
+ status = bind (sd, (struct sockaddr *) &sa_unix, sizeof (sa_unix));
+ if (status != 0)
+ {
+ FUNC_ERROR ("bind");
+ break;
+ }
+
+ /* Make the socket writeable by the daemon.. */
+ status = chmod (sa_unix.sun_path, 0666);
+ if (status != 0)
+ {
+ FUNC_ERROR ("chmod");
+ break;
+ }
+
+ status = connect (sd, (struct sockaddr *) &item->sockaddr,
+ sizeof (item->sockaddr));
+ if (status != 0)
+ {
+ FUNC_ERROR ("connect");
+ break;
+ }
+
+ status = send (sd, item->command, strlen (item->command), 0);
+ if (status < 0)
+ {
+ FUNC_ERROR ("send");
+ break;
+ }
+
+ status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
+ if (status < 0)
+ {
+ FUNC_ERROR ("recv");
+ break;
+ }
+ status = 0;
+ } while (0);
+
+ close (sd);
+ unlink (sa_unix.sun_path);
+
+ if (status != 0)
+ return (-1);
+
+ buffer_size = status + 1;
+ buffer = (char *) malloc (buffer_size);
+ if (buffer == NULL)
+ {
+ FUNC_ERROR ("malloc");
+ return (-1);
+ }
+
+ memcpy (buffer, temp, status);
+ buffer[status] = 0;
+
+ *ret_buffer = buffer;
+ *ret_buffer_size = buffer_size;
+
+ return (0);
+} /* }}} int powerdns_get_data_dgram */
+
+static int powerdns_get_data_stream (list_item_t *item, /* {{{ */
+ char **ret_buffer,
+ size_t *ret_buffer_size)
+{
+ int sd;
+ int status;
+
+ char temp[4096];
+ char *buffer = NULL;
+ size_t buffer_size = 0;
+
+ sd = socket (PF_UNIX, item->socktype, 0);
+ if (sd < 0)
+ {
+ FUNC_ERROR ("socket");
+ return (-1);
+ }
+
+ status = connect (sd, (struct sockaddr *) &item->sockaddr,
+ sizeof (item->sockaddr));
+ if (status != 0)
+ {
+ FUNC_ERROR ("connect");
+ close (sd);
+ return (-1);
+ }
+
+ /* strlen + 1, because we need to send the terminating NULL byte, too. */
+ status = send (sd, item->command, strlen (item->command) + 1,
+ /* flags = */ 0);
+ if (status < 0)
+ {
+ FUNC_ERROR ("send");
+ close (sd);
+ return (-1);
+ }
+
+ while (42)
+ {
+ char *buffer_new;
+
+ status = recv (sd, temp, sizeof (temp), /* flags = */ 0);
+ if (status < 0)
+ {
+ FUNC_ERROR ("recv");
+ break;
+ }
+ else if (status == 0)
+ break;
+
+ buffer_new = (char *) realloc (buffer, buffer_size + status + 1);
+ if (buffer_new == NULL)
+ {
+ FUNC_ERROR ("realloc");
+ status = -1;
+ break;
+ }
+ buffer = buffer_new;
+
+ memcpy (buffer + buffer_size, temp, status);
+ buffer_size += status;
+ buffer[buffer_size] = 0;
+ } /* while (42) */
+ close (sd);
+ sd = -1;
+
+ if (status < 0)
+ {
+ sfree (buffer);
+ }
+ else
+ {
+ assert (status == 0);
+ *ret_buffer = buffer;
+ *ret_buffer_size = buffer_size;
+ }
+
+ return (status);
+} /* }}} int powerdns_get_data_stream */
+
+static int powerdns_get_data (list_item_t *item, char **ret_buffer,
+ size_t *ret_buffer_size)
+{
+ if (item->socktype == SOCK_DGRAM)
+ return (powerdns_get_data_dgram (item, ret_buffer, ret_buffer_size));
+ else if (item->socktype == SOCK_STREAM)
+ return (powerdns_get_data_stream (item, ret_buffer, ret_buffer_size));
+ else
+ {
+ ERROR ("powerdns plugin: Unknown socket type: %i", (int) item->socktype);
+ return (-1);
+ }
+} /* int powerdns_get_data */
+
+static int powerdns_read_server (list_item_t *item) /* {{{ */
+{
+ char *buffer = NULL;
+ size_t buffer_size = 0;
+ int status;
+
+ char *dummy;
+ char *saveptr;
+
+ char *key;
+ char *value;
+
+ status = powerdns_get_data (item, &buffer, &buffer_size);
+ if (status != 0)
+ return (-1);
+
+ /* corrupt-packets=0,deferred-cache-inserts=0,deferred-cache-lookup=0,latency=0,packetcache-hit=0,packetcache-miss=0,packetcache-size=0,qsize-q=0,query-cache-hit=0,query-cache-miss=0,recursing-answers=0,recursing-questions=0,servfail-packets=0,tcp-answers=0,tcp-queries=0,timedout-packets=0,udp-answers=0,udp-queries=0,udp4-answers=0,udp4-queries=0,udp6-answers=0,udp6-queries=0, */
+ dummy = buffer;
+ saveptr = NULL;
+ while ((key = strtok_r (dummy, ",", &saveptr)) != NULL)
+ {
+ dummy = NULL;
+
+ value = strchr (key, '=');
+ if (value == NULL)
+ break;
+
+ *value = '\0';
+ value++;
+
+ if (value[0] == '\0')
+ continue;
+
+ submit (item->instance, key, value);
+ } /* while (strtok_r) */
+
+ sfree (buffer);
+
+ return (0);
+} /* }}} int powerdns_read_server */
+
+static int powerdns_read_recursor (list_item_t *item) /* {{{ */
+{
+ char *buffer = NULL;
+ size_t buffer_size = 0;
+ int status;
+
+ char *dummy;
+
+ char *keys_list;
+ char *key;
+ char *key_saveptr;
+ char *value;
+ char *value_saveptr;
+
+ status = powerdns_get_data (item, &buffer, &buffer_size);
+ if (status != 0)
+ return (-1);
+
+ keys_list = strdup (item->command);
+ if (keys_list == NULL)
+ {
+ FUNC_ERROR ("strdup");
+ sfree (buffer);
+ return (-1);
+ }
+
+ key_saveptr = NULL;
+ value_saveptr = NULL;
+
+ /* Skip the `get' at the beginning */
+ strtok_r (keys_list, " \t", &key_saveptr);
+
+ dummy = buffer;
+ while ((value = strtok_r (dummy, " \t\n\r", &value_saveptr)) != NULL)
+ {
+ dummy = NULL;
+
+ key = strtok_r (NULL, " \t", &key_saveptr);
+ if (key == NULL)
+ break;
+
+ submit (item->instance, key, value);
+ } /* while (strtok_r) */
+
+ sfree (buffer);
+ sfree (keys_list);
+
+ return (0);
+} /* }}} int powerdns_read_recursor */
+
+static int powerdns_config_add_string (const char *name, /* {{{ */
+ char **dest,
+ oconfig_item_t *ci)
+{
+ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
+ name);
+ return (-1);
+ }
+
+ sfree (*dest);
+ *dest = strdup (ci->values[0].value.string);
+ if (*dest == NULL)
+ return (-1);
+
+ return (0);
+} /* }}} int ctail_config_add_string */
+
+static int powerdns_config_add_server (oconfig_item_t *ci) /* {{{ */
+{
+ char *socket_temp;
+
+ list_item_t *item;
+ int status;
+ int i;
+
+ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ WARNING ("powerdns plugin: `%s' needs exactly one string argument.",
+ ci->key);
+ return (-1);
+ }
+
+ item = (list_item_t *) malloc (sizeof (list_item_t));
+ if (item == NULL)
+ {
+ ERROR ("powerdns plugin: malloc failed.");
+ return (-1);
+ }
+ memset (item, '\0', sizeof (list_item_t));
+
+ item->instance = strdup (ci->values[0].value.string);
+ if (item->instance == NULL)
+ {
+ ERROR ("powerdns plugin: strdup failed.");
+ sfree (item);
+ return (-1);
+ }
+
+ /*
+ * Set default values for the members of list_item_t
+ */
+ if (strcasecmp ("Server", ci->key) == 0)
+ {
+ item->func = powerdns_read_server;
+ item->command = strdup (SERVER_COMMAND);
+ item->socktype = SOCK_STREAM;
+ socket_temp = strdup (SERVER_SOCKET);
+ }
+ else if (strcasecmp ("Recursor", ci->key) == 0)
+ {
+ item->func = powerdns_read_recursor;
+ item->command = strdup (RECURSOR_COMMAND);
+ item->socktype = SOCK_DGRAM;
+ socket_temp = strdup (RECURSOR_SOCKET);
+ }
+
+ status = 0;
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *option = ci->children + i;
+
+ if (strcasecmp ("Command", option->key) == 0)
+ status = powerdns_config_add_string ("Command", &item->command, option);
+ else if (strcasecmp ("Socket", option->key) == 0)
+ status = powerdns_config_add_string ("Socket", &socket_temp, option);
+ else
+ {
+ ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ }
+
+ while (status == 0)
+ {
+ llentry_t *e;
+
+ if (socket_temp == NULL)
+ {
+ ERROR ("powerdns plugin: socket_temp == NULL.");
+ status = -1;
+ break;
+ }
+
+ if (item->command == NULL)
+ {
+ ERROR ("powerdns plugin: item->command == NULL.");
+ status = -1;
+ break;
+ }
+
+ item->sockaddr.sun_family = AF_UNIX;
+ sstrncpy (item->sockaddr.sun_path, socket_temp, UNIX_PATH_MAX);
+
+ e = llentry_create (item->instance, item);
+ if (e == NULL)
+ {
+ ERROR ("powerdns plugin: llentry_create failed.");
+ status = -1;
+ break;
+ }
+ llist_append (list, e);
+
+ break;
+ }
+
+ if (status != 0)
+ {
+ sfree (item);
+ return (-1);
+ }
+
+ DEBUG ("powerdns plugin: Add server: instance = %s;", item->instance);
+
+ return (0);
+} /* }}} int powerdns_config_add_server */
+
+static int powerdns_config (oconfig_item_t *ci) /* {{{ */
+{
+ int i;
+
+ DEBUG ("powerdns plugin: powerdns_config (ci = %p);", (void *) ci);
+
+ if (list == NULL)
+ {
+ list = llist_create ();
+
+ if (list == NULL)
+ {
+ ERROR ("powerdns plugin: `llist_create' failed.");
+ return (-1);
+ }
+ }
+
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *option = ci->children + i;
+
+ if ((strcasecmp ("Server", option->key) == 0)
+ || (strcasecmp ("Recursor", option->key) == 0))
+ powerdns_config_add_server (option);
+ if (strcasecmp ("LocalSocket", option->key) == 0)
+ {
+ char *temp = strdup (option->key);
+ if (temp == NULL)
+ return (1);
+ sfree (local_sockpath);
+ local_sockpath = temp;
+ }
+ else
+ {
+ ERROR ("powerdns plugin: Option `%s' not allowed here.", option->key);
+ }
+ } /* for (i = 0; i < ci->children_num; i++) */
+
+ return (0);
+} /* }}} int powerdns_config */
+
+static int powerdns_read (void)
+{
+ llentry_t *e;
+
+ for (e = llist_head (list); e != NULL; e = e->next)
+ {
+ list_item_t *item = e->value;
+ item->func (item);
+ }
+
+ return (0);
+} /* static int powerdns_read */
+
+static int powerdns_shutdown (void)
+{
+ llentry_t *e;
+
+ if (list == NULL)
+ return (0);
+
+ for (e = llist_head (list); e != NULL; e = e->next)
+ {
+ list_item_t *item = (list_item_t *) e->value;
+ e->value = NULL;
+
+ sfree (item->instance);
+ sfree (item->command);
+ sfree (item);
+ }
+
+ llist_destroy (list);
+ list = NULL;
+
+ return (0);
+} /* static int powerdns_shutdown */
+
+void module_register (void)
+{
+ plugin_register_complex_config ("powerdns", powerdns_config);
+ plugin_register_read ("powerdns", powerdns_read);
+ plugin_register_shutdown ("powerdns", powerdns_shutdown );
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 fdm=marker : */
diff --git a/src/rrdtool.c b/src/rrdtool.c
index 3bb5a9e7ede71ed45b87f09fa3dd2424a305b850..93c9d7a8ac670897747a3f6231bd5f2f8df34de1 100644 (file)
--- a/src/rrdtool.c
+++ b/src/rrdtool.c
span = rts[i];
if ((span / ss) < rrarows)
- continue;
+ span = ss * rrarows;
if (cdp_len == 0)
cdp_len = 1;
return (status);
} /* int rrd_write */
+static int rrd_flush (const int timeout)
+{
+ pthread_mutex_lock (&cache_lock);
+
+ if (cache == NULL) {
+ pthread_mutex_unlock (&cache_lock);
+ return (0);
+ }
+
+ rrd_cache_flush (timeout);
+ pthread_mutex_unlock (&cache_lock);
+ return (0);
+} /* int rrd_flush */
+
static int rrd_config (const char *key, const char *value)
{
if (strcasecmp ("CacheTimeout", key) == 0)
config_keys, config_keys_num);
plugin_register_init ("rrdtool", rrd_init);
plugin_register_write ("rrdtool", rrd_write);
+ plugin_register_flush ("rrdtool", rrd_flush);
plugin_register_shutdown ("rrdtool", rrd_shutdown);
}
diff --git a/src/sensors.c b/src/sensors.c
index 173ed9c14553e160f3a5ac183ab36d372d1b7f80..0968e311fb170c63ba8c01727ed8ea7e423bf733 100644 (file)
--- a/src/sensors.c
+++ b/src/sensors.c
/**
* collectd - src/sensors.c
- * Copyright (C) 2005-2007 Florian octo Forster
+ * Copyright (C) 2005-2008 Florian octo Forster
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
diff --git a/src/tail.c b/src/tail.c
--- /dev/null
+++ b/src/tail.c
@@ -0,0 +1,353 @@
+/**
+ * collectd - src/tail.c
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_tail_match.h"
+
+/*
+ * <Plugin tail>
+ * <File "/var/log/exim4/mainlog">
+ * Instance "exim"
+ * <Match>
+ * Regex "S=([1-9][0-9]*)"
+ * DSType "CounterAdd"
+ * Type "ipt_bytes"
+ * Instance "total"
+ * </Match>
+ * </File>
+ * </Plugin>
+ */
+
+struct ctail_config_match_s
+{
+ char *regex;
+ int flags;
+ char *type;
+ char *type_instance;
+};
+typedef struct ctail_config_match_s ctail_config_match_t;
+
+cu_tail_match_t **tail_match_list = NULL;
+size_t tail_match_list_num = 0;
+
+static int ctail_config_add_string (const char *name, char **dest, oconfig_item_t *ci)
+{
+ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ WARNING ("tail plugin: `%s' needs exactly one string argument.", name);
+ return (-1);
+ }
+
+ sfree (*dest);
+ *dest = strdup (ci->values[0].value.string);
+ if (*dest == NULL)
+ return (-1);
+
+ return (0);
+} /* int ctail_config_add_string */
+
+static int ctail_config_add_match_dstype (ctail_config_match_t *cm,
+ oconfig_item_t *ci)
+{
+ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ WARNING ("tail plugin: `DSType' needs exactly one string argument.");
+ return (-1);
+ }
+
+ if (strncasecmp ("Gauge", ci->values[0].value.string, strlen ("Gauge")) == 0)
+ {
+ cm->flags = UTILS_MATCH_DS_TYPE_GAUGE;
+ if (strcasecmp ("GaugeAverage", ci->values[0].value.string) == 0)
+ cm->flags |= UTILS_MATCH_CF_GAUGE_AVERAGE;
+ else if (strcasecmp ("GaugeMin", ci->values[0].value.string) == 0)
+ cm->flags |= UTILS_MATCH_CF_GAUGE_MIN;
+ else if (strcasecmp ("GaugeMax", ci->values[0].value.string) == 0)
+ cm->flags |= UTILS_MATCH_CF_GAUGE_MAX;
+ else if (strcasecmp ("GaugeLast", ci->values[0].value.string) == 0)
+ cm->flags |= UTILS_MATCH_CF_GAUGE_LAST;
+ else
+ cm->flags = 0;
+ }
+ else if (strncasecmp ("Counter", ci->values[0].value.string, strlen ("Counter")) == 0)
+ {
+ cm->flags = UTILS_MATCH_DS_TYPE_COUNTER;
+ if (strcasecmp ("CounterSet", ci->values[0].value.string) == 0)
+ cm->flags |= UTILS_MATCH_CF_COUNTER_SET;
+ else if (strcasecmp ("CounterAdd", ci->values[0].value.string) == 0)
+ cm->flags |= UTILS_MATCH_CF_COUNTER_ADD;
+ else if (strcasecmp ("CounterInc", ci->values[0].value.string) == 0)
+ cm->flags |= UTILS_MATCH_CF_COUNTER_INC;
+ else
+ cm->flags = 0;
+ }
+ else
+ {
+ cm->flags = 0;
+ }
+
+ if (cm->flags == 0)
+ {
+ WARNING ("tail plugin: `%s' is not a valid argument to `DSType'.",
+ ci->values[0].value.string);
+ return (-1);
+ }
+
+ return (0);
+} /* int ctail_config_add_match_dstype */
+
+static int ctail_config_add_match (cu_tail_match_t *tm,
+ const char *plugin_instance, oconfig_item_t *ci)
+{
+ ctail_config_match_t cm;
+ int status;
+ int i;
+
+ memset (&cm, '\0', sizeof (cm));
+
+ if (ci->values_num != 0)
+ {
+ WARNING ("tail plugin: Ignoring arguments for the `Match' block.");
+ }
+
+ status = 0;
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *option = ci->children + i;
+
+ if (strcasecmp ("Regex", option->key) == 0)
+ status = ctail_config_add_string ("Regex", &cm.regex, option);
+ else if (strcasecmp ("DSType", option->key) == 0)
+ status = ctail_config_add_match_dstype (&cm, option);
+ else if (strcasecmp ("Type", option->key) == 0)
+ status = ctail_config_add_string ("Type", &cm.type, option);
+ else if (strcasecmp ("Instance", option->key) == 0)
+ status = ctail_config_add_string ("Instance", &cm.type_instance, option);
+ else
+ {
+ WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ } /* for (i = 0; i < ci->children_num; i++) */
+
+ while (status == 0)
+ {
+ if (cm.regex == NULL)
+ {
+ WARNING ("tail plugin: `Regex' missing in `Match' block.");
+ status = -1;
+ break;
+ }
+
+ if (cm.type == NULL)
+ {
+ WARNING ("tail plugin: `Type' missing in `Match' block.");
+ status = -1;
+ break;
+ }
+
+ if (cm.flags == 0)
+ {
+ WARNING ("tail plugin: `DSType' missing in `Match' block.");
+ status = -1;
+ break;
+ }
+
+ break;
+ } /* while (status == 0) */
+
+ if (status == 0)
+ {
+ status = tail_match_add_match_simple (tm, cm.regex, cm.flags,
+ "tail", plugin_instance, cm.type, cm.type_instance);
+
+ if (status != 0)
+ {
+ ERROR ("tail plugin: tail_match_add_match_simple failed.");
+ }
+ }
+
+ sfree (cm.regex);
+ sfree (cm.type);
+ sfree (cm.type_instance);
+
+ return (status);
+} /* int ctail_config_add_match */
+
+static int ctail_config_add_file (oconfig_item_t *ci)
+{
+ cu_tail_match_t *tm;
+ char *plugin_instance = NULL;
+ int num_matches = 0;
+ int status;
+ int i;
+
+ if ((ci->values_num != 1) || (ci->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ WARNING ("tail plugin: `File' needs exactly one string argument.");
+ return (-1);
+ }
+
+ tm = tail_match_create (ci->values[0].value.string);
+ if (tm == NULL)
+ {
+ ERROR ("tail plugin: tail_match_create (%s) failed.",
+ ci->values[0].value.string);
+ return (-1);
+ }
+
+ status = 0;
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *option = ci->children + i;
+
+ if (strcasecmp ("Match", option->key) == 0)
+ {
+ status = ctail_config_add_match (tm, plugin_instance, option);
+ if (status == 0)
+ num_matches++;
+ /* Be mild with failed matches.. */
+ status = 0;
+ }
+ else if (strcasecmp ("Instance", option->key) == 0)
+ status = ctail_config_add_string ("Instance", &plugin_instance, option);
+ else
+ {
+ WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+ status = -1;
+ }
+
+ if (status != 0)
+ break;
+ } /* for (i = 0; i < ci->children_num; i++) */
+
+ if (num_matches == 0)
+ {
+ ERROR ("tail plugin: No (valid) matches found for file `%s'.",
+ ci->values[0].value.string);
+ tail_match_destroy (tm);
+ return (-1);
+ }
+ else
+ {
+ cu_tail_match_t **temp;
+
+ temp = (cu_tail_match_t **) realloc (tail_match_list,
+ sizeof (cu_tail_match_t *) * (tail_match_list_num + 1));
+ if (temp == NULL)
+ {
+ ERROR ("tail plugin: realloc failed.");
+ tail_match_destroy (tm);
+ return (-1);
+ }
+
+ tail_match_list = temp;
+ tail_match_list[tail_match_list_num] = tm;
+ tail_match_list_num++;
+ }
+
+ return (0);
+} /* int ctail_config_add_file */
+
+static int ctail_config (oconfig_item_t *ci)
+{
+ int i;
+
+ for (i = 0; i < ci->children_num; i++)
+ {
+ oconfig_item_t *option = ci->children + i;
+
+ if (strcasecmp ("File", option->key) == 0)
+ ctail_config_add_file (option);
+ else
+ {
+ WARNING ("tail plugin: Option `%s' not allowed here.", option->key);
+ }
+ } /* for (i = 0; i < ci->children_num; i++) */
+
+ return (0);
+} /* int ctail_config */
+
+static int ctail_init (void)
+{
+ if (tail_match_list_num == 0)
+ {
+ WARNING ("tail plugin: File list is empty. Returning an error.");
+ return (-1);
+ }
+
+ return (0);
+} /* int ctail_init */
+
+static int ctail_read (void)
+{
+ int success = 0;
+ int i;
+
+ for (i = 0; i < tail_match_list_num; i++)
+ {
+ int status;
+
+ status = tail_match_read (tail_match_list[i]);
+ if (status != 0)
+ {
+ ERROR ("tail plugin: tail_match_read[%i] failed.", i);
+ }
+ else
+ {
+ success++;
+ }
+ }
+
+ if (success == 0)
+ return (-1);
+ return (0);
+} /* int ctail_read */
+
+static int ctail_shutdown (void)
+{
+ int i;
+
+ for (i = 0; i < tail_match_list_num; i++)
+ {
+ tail_match_destroy (tail_match_list[i]);
+ tail_match_list[i] = NULL;
+ }
+ sfree (tail_match_list);
+ tail_match_list_num = 0;
+
+ return (0);
+} /* int ctail_shutdown */
+
+void module_register (void)
+{
+ plugin_register_complex_config ("tail", ctail_config);
+ plugin_register_init ("tail", ctail_init);
+ plugin_register_read ("tail", ctail_read);
+ plugin_register_shutdown ("tail", ctail_shutdown);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/teamspeak2.c b/src/teamspeak2.c
--- /dev/null
+++ b/src/teamspeak2.c
@@ -0,0 +1,844 @@
+/**
+ * collectd - src/teamspeak2.c
+ * Copyright (C) 2008 Stefan Hacker
+ * Copyright (C) 2008 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Stefan Hacker <d0t at dbclan dot de>
+ * Florian Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+/*
+ * Defines
+ */
+/* Default host and port */
+#define DEFAULT_HOST "127.0.0.1"
+#define DEFAULT_PORT "51234"
+
+/*
+ * Variables
+ */
+/* Server linked list structure */
+typedef struct vserver_list_s
+{
+ int port;
+ struct vserver_list_s *next;
+} vserver_list_t;
+static vserver_list_t *server_list = NULL;
+
+/* Host data */
+static char *config_host = NULL;
+static char *config_port = NULL;
+
+static FILE *global_read_fh = NULL;
+static FILE *global_write_fh = NULL;
+
+/* Config data */
+static const char *config_keys[] =
+{
+ "Host",
+ "Port",
+ "Server"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+/*
+ * Functions
+ */
+static int tss2_add_vserver (int vserver_port)
+{
+ /*
+ * Adds a new vserver to the linked list
+ */
+ vserver_list_t *entry;
+
+ /* Check port range */
+ if ((vserver_port <= 0) || (vserver_port > 65535))
+ {
+ ERROR ("teamspeak2 plugin: VServer port is invalid: %i",
+ vserver_port);
+ return (-1);
+ }
+
+ /* Allocate memory */
+ entry = (vserver_list_t *) malloc (sizeof (vserver_list_t));
+ if (entry == NULL)
+ {
+ ERROR ("teamspeak2 plugin: malloc failed.");
+ return (-1);
+ }
+ memset (entry, 0, sizeof (vserver_list_t));
+
+ /* Save data */
+ entry->port = vserver_port;
+
+ /* Insert to list */
+ if(server_list == NULL) {
+ /* Add the server as the first element */
+ server_list = entry;
+ }
+ else {
+ vserver_list_t *prev;
+
+ /* Add the server to the end of the list */
+ prev = server_list;
+ while (prev->next != NULL)
+ prev = prev->next;
+ prev->next = entry;
+ }
+
+ INFO ("teamspeak2 plugin: Registered new vserver: %i", vserver_port);
+
+ return (0);
+} /* int tss2_add_vserver */
+
+static void tss2_submit_gauge (const char *plugin_instance,
+ const char *type, const char *type_instance,
+ gauge_t value)
+{
+ /*
+ * Submits a gauge value to the collectd daemon
+ */
+ value_t values[1];
+ value_list_t vl = VALUE_LIST_INIT;
+
+ values[0].gauge = value;
+
+ vl.values = values;
+ vl.values_len = 1;
+ vl.time = time (NULL);
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
+
+ if (plugin_instance != NULL)
+ sstrncpy (vl.plugin_instance, plugin_instance,
+ sizeof (vl.plugin_instance));
+
+ if (type_instance != NULL)
+ sstrncpy (vl.type_instance, type_instance,
+ sizeof (vl.type_instance));
+
+ plugin_dispatch_values (type, &vl);
+} /* void tss2_submit_gauge */
+
+static void tss2_submit_io (const char *plugin_instance, const char *type,
+ counter_t rx, counter_t tx)
+{
+ /*
+ * Submits the io rx/tx tuple to the collectd daemon
+ */
+ 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);
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, "teamspeak2", sizeof (vl.plugin));
+
+ if (plugin_instance != NULL)
+ sstrncpy (vl.plugin_instance, plugin_instance,
+ sizeof (vl.plugin_instance));
+
+ plugin_dispatch_values (type, &vl);
+} /* void tss2_submit_gauge */
+
+static void tss2_close_socket (void)
+{
+ /*
+ * Closes all sockets
+ */
+ if (global_write_fh != NULL)
+ {
+ fputs ("quit\r\n", global_write_fh);
+ }
+
+ if (global_read_fh != NULL)
+ {
+ fclose (global_read_fh);
+ global_read_fh = NULL;
+ }
+
+ if (global_write_fh != NULL)
+ {
+ fclose (global_write_fh);
+ global_write_fh = NULL;
+ }
+} /* void tss2_close_socket */
+
+static int tss2_get_socket (FILE **ret_read_fh, FILE **ret_write_fh)
+{
+ /*
+ * Returns connected file objects or establishes the connection
+ * if it's not already present
+ */
+ struct addrinfo ai_hints;
+ struct addrinfo *ai_head;
+ struct addrinfo *ai_ptr;
+ int sd = -1;
+ int status;
+
+ /* Check if we already got opened connections */
+ if ((global_read_fh != NULL) && (global_write_fh != NULL))
+ {
+ /* If so, use them */
+ if (ret_read_fh != NULL)
+ *ret_read_fh = global_read_fh;
+ if (ret_write_fh != NULL)
+ *ret_write_fh = global_write_fh;
+ return (0);
+ }
+
+ /* Get all addrs for this hostname */
+ memset (&ai_hints, 0, sizeof (ai_hints));
+#ifdef AI_ADDRCONFIG
+ ai_hints.ai_flags |= AI_ADDRCONFIG;
+#endif
+ ai_hints.ai_family = AF_UNSPEC;
+ ai_hints.ai_socktype = SOCK_STREAM;
+
+ status = getaddrinfo ((config_host != NULL) ? config_host : DEFAULT_HOST,
+ (config_port != NULL) ? config_port : DEFAULT_PORT,
+ &ai_hints,
+ &ai_head);
+ if (status != 0)
+ {
+ ERROR ("teamspeak2 plugin: getaddrinfo failed: %s",
+ gai_strerror (status));
+ return (-1);
+ }
+
+ /* Try all given hosts until we can connect to one */
+ for (ai_ptr = ai_head; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
+ {
+ /* Create socket */
+ sd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype,
+ ai_ptr->ai_protocol);
+ if (sd < 0)
+ {
+ char errbuf[1024];
+ WARNING ("teamspeak2 plugin: socket failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ continue;
+ }
+
+ /* Try to connect */
+ status = connect (sd, ai_ptr->ai_addr, ai_ptr->ai_addrlen);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ WARNING ("teamspeak2 plugin: connect failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ close (sd);
+ continue;
+ }
+
+ /*
+ * Success, we can break. Don't need more than one connection
+ */
+ break;
+ } /* for (ai_ptr) */
+
+ freeaddrinfo (ai_head);
+
+ /* Check if we really got connected */
+ if (sd < 0)
+ return (-1);
+
+ /* Create file objects from sockets */
+ global_read_fh = fdopen (sd, "r");
+ if (global_read_fh == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("teamspeak2 plugin: fdopen failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ close (sd);
+ return (-1);
+ }
+
+ global_write_fh = fdopen (sd, "w");
+ if (global_write_fh == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("teamspeak2 plugin: fdopen failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ tss2_close_socket ();
+ return (-1);
+ }
+
+ { /* Check that the server correctly identifies itself. */
+ char buffer[4096];
+ char *buffer_ptr;
+
+ buffer_ptr = fgets (buffer, sizeof (buffer), global_read_fh);
+ buffer[sizeof (buffer) - 1] = 0;
+
+ if (memcmp ("[TS]\r\n", buffer, 6) != 0)
+ {
+ ERROR ("teamspeak2 plugin: Unexpected response when connecting "
+ "to server. Expected ``[TS]'', got ``%s''.",
+ buffer);
+ tss2_close_socket ();
+ return (-1);
+ }
+ DEBUG ("teamspeak2 plugin: Server send correct banner, connected!");
+ }
+
+ /* Copy the new filehandles to the given pointers */
+ if (ret_read_fh != NULL)
+ *ret_read_fh = global_read_fh;
+ if (ret_write_fh != NULL)
+ *ret_write_fh = global_write_fh;
+ return (0);
+} /* int tss2_get_socket */
+
+static int tss2_send_request (FILE *fh, const char *request)
+{
+ /*
+ * This function puts a request to the server socket
+ */
+ int status;
+
+ status = fputs (request, fh);
+ if (status < 0)
+ {
+ ERROR ("teamspeak2 plugin: fputs failed.");
+ tss2_close_socket ();
+ return (-1);
+ }
+ fflush (fh);
+
+ return (0);
+} /* int tss2_send_request */
+
+static int tss2_receive_line (FILE *fh, char *buffer, int buffer_size)
+{
+ /*
+ * Receive a single line from the given file object
+ */
+ char *temp;
+
+ /*
+ * fgets is blocking but much easier then doing anything else
+ * TODO: Non-blocking Version would be safer
+ */
+ temp = fgets (buffer, buffer_size, fh);
+ if (temp == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("teamspeak2 plugin: fgets failed: %s",
+ sstrerror (errno, errbuf, sizeof(errbuf)));
+ tss2_close_socket ();
+ return (-1);
+ }
+
+ buffer[buffer_size - 1] = 0;
+ return (0);
+} /* int tss2_receive_line */
+
+static int tss2_select_vserver (FILE *read_fh, FILE *write_fh, vserver_list_t *vserver)
+{
+ /*
+ * Tell the server to select the given vserver
+ */
+ char command[128];
+ char response[128];
+ int status;
+
+ /* Send request */
+ snprintf (command, sizeof (command), "sel %i\r\n", vserver->port);
+ command[sizeof (command) - 1] = 0;
+
+ status = tss2_send_request (write_fh, command);
+ if (status != 0)
+ {
+ ERROR ("teamspeak2 plugin: tss2_send_request (%s) failed.", command);
+ return (-1);
+ }
+
+ /* Get answer */
+ status = tss2_receive_line (read_fh, response, sizeof (response));
+ if (status != 0)
+ {
+ ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+ return (-1);
+ }
+ response[sizeof (response)] = 0;
+
+ /* Check answer */
+ if ((strncasecmp ("OK", response, 2) == 0)
+ && ((response[2] == 0)
+ || (response[2] == '\n')
+ || (response[2] == '\r')))
+ return (0);
+
+ ERROR ("teamspeak2 plugin: Command ``%s'' failed. "
+ "Response received from server was: ``%s''.",
+ command, response);
+ return (-1);
+} /* int tss2_select_vserver */
+
+static int tss2_vserver_gapl (FILE *read_fh, FILE *write_fh,
+ vserver_list_t *vserver, gauge_t *ret_value)
+{
+ /*
+ * Reads the vserver's average packet loss and submits it to collectd.
+ * Be sure to run the tss2_read_vserver function before calling this so
+ * the vserver is selected correctly.
+ */
+ gauge_t packet_loss = NAN;
+ int status;
+
+ status = tss2_send_request (write_fh, "gapl\r\n");
+ if (status != 0)
+ {
+ ERROR("teamspeak2 plugin: tss2_send_request (gapl) failed.");
+ return (-1);
+ }
+
+ while (42)
+ {
+ char buffer[4096];
+ char *value;
+ char *endptr = NULL;
+
+ status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
+ if (status != 0)
+ {
+ /* Set to NULL just to make sure noone uses these FHs anymore. */
+ read_fh = NULL;
+ write_fh = NULL;
+ ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+ return (-1);
+ }
+ buffer[sizeof (buffer)] = 0;
+
+ if (strncmp ("average_packet_loss=", buffer,
+ strlen ("average_packet_loss=")) == 0)
+ {
+ /* Got average packet loss, now interpret it */
+ value = &buffer[20];
+ /* Replace , with . */
+ while (*value != 0)
+ {
+ if (*value == ',')
+ {
+ *value = '.';
+ break;
+ }
+ value++;
+ }
+
+ value = &buffer[20];
+
+ packet_loss = strtod (value, &endptr);
+ if (value == endptr)
+ {
+ /* Failed */
+ WARNING ("teamspeak2 plugin: Could not read average package "
+ "loss from string: %s", buffer);
+ continue;
+ }
+ }
+ else if (strncasecmp ("OK", buffer, 2) == 0)
+ {
+ break;
+ }
+ else if (strncasecmp ("ERROR", buffer, 5) == 0)
+ {
+ ERROR ("teamspeak2 plugin: Server returned an error: %s", buffer);
+ return (-1);
+ }
+ else
+ {
+ WARNING ("teamspeak2 plugin: Server returned unexpected string: %s",
+ buffer);
+ }
+ }
+
+ *ret_value = packet_loss;
+ return (0);
+} /* int tss2_vserver_gapl */
+
+static int tss2_read_vserver (vserver_list_t *vserver)
+{
+ /*
+ * Poll information for the given vserver and submit it to collect.
+ * If vserver is NULL the global server information will be queried.
+ */
+ int status;
+
+ gauge_t users = NAN;
+ gauge_t channels = NAN;
+ gauge_t servers = NAN;
+ counter_t rx_octets = 0;
+ counter_t tx_octets = 0;
+ counter_t rx_packets = 0;
+ counter_t tx_packets = 0;
+ gauge_t packet_loss = NAN;
+ int valid = 0;
+
+ char plugin_instance[DATA_MAX_NAME_LEN];
+
+ FILE *read_fh;
+ FILE *write_fh;
+
+ /* Get the send/receive sockets */
+ status = tss2_get_socket (&read_fh, &write_fh);
+ if (status != 0)
+ {
+ ERROR ("teamspeak2 plugin: tss2_get_socket failed.");
+ return (-1);
+ }
+
+ if (vserver == NULL)
+ {
+ /* Request global information */
+ memset (plugin_instance, 0, sizeof (plugin_instance));
+
+ status = tss2_send_request (write_fh, "gi\r\n");
+ }
+ else
+ {
+ /* Request server information */
+ snprintf (plugin_instance, sizeof (plugin_instance), "vserver%i",
+ vserver->port);
+ plugin_instance[sizeof (plugin_instance) - 1] = 0;
+
+ /* Select the server */
+ status = tss2_select_vserver (read_fh, write_fh, vserver);
+ if (status != 0)
+ return (status);
+
+ status = tss2_send_request (write_fh, "si\r\n");
+ }
+
+ if (status != 0)
+ {
+ ERROR ("teamspeak2 plugin: tss2_send_request failed.");
+ return (-1);
+ }
+
+ /* Loop until break */
+ while (42)
+ {
+ char buffer[4096];
+ char *key;
+ char *value;
+ char *endptr = NULL;
+
+ /* Read one line of the server's answer */
+ status = tss2_receive_line (read_fh, buffer, sizeof (buffer));
+ if (status != 0)
+ {
+ /* Set to NULL just to make sure noone uses these FHs anymore. */
+ read_fh = NULL;
+ write_fh = NULL;
+ ERROR ("teamspeak2 plugin: tss2_receive_line failed.");
+ break;
+ }
+
+ if (strncasecmp ("ERROR", buffer, 5) == 0)
+ {
+ ERROR ("teamspeak2 plugin: Server returned an error: %s",
+ buffer);
+ break;
+ }
+ else if (strncasecmp ("OK", buffer, 2) == 0)
+ {
+ break;
+ }
+
+ /* Split line into key and value */
+ key = strchr (buffer, '_');
+ if (key == NULL)
+ {
+ DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
+ continue;
+ }
+ key++;
+
+ /* Evaluate assignment */
+ value = strchr (key, '=');
+ if (value == NULL)
+ {
+ DEBUG ("teamspeak2 plugin: Cannot parse line: %s", buffer);
+ continue;
+ }
+ *value = 0;
+ value++;
+
+ /* Check for known key and save the given value */
+ /* global info: users_online,
+ * server info: currentusers. */
+ if ((strcmp ("currentusers", key) == 0)
+ || (strcmp ("users_online", key) == 0))
+ {
+ users = strtod (value, &endptr);
+ if (value != endptr)
+ valid |= 0x01;
+ }
+ /* global info: channels,
+ * server info: currentchannels. */
+ else if ((strcmp ("currentchannels", key) == 0)
+ || (strcmp ("channels", key) == 0))
+ {
+ channels = strtod (value, &endptr);
+ if (value != endptr)
+ valid |= 0x40;
+ }
+ /* global only */
+ else if (strcmp ("servers", key) == 0)
+ {
+ servers = strtod (value, &endptr);
+ if (value != endptr)
+ valid |= 0x80;
+ }
+ else if (strcmp ("bytesreceived", key) == 0)
+ {
+ rx_octets = strtoll (value, &endptr, 0);
+ if (value != endptr)
+ valid |= 0x02;
+ }
+ else if (strcmp ("bytessend", key) == 0)
+ {
+ tx_octets = strtoll (value, &endptr, 0);
+ if (value != endptr)
+ valid |= 0x04;
+ }
+ else if (strcmp ("packetsreceived", key) == 0)
+ {
+ rx_packets = strtoll (value, &endptr, 0);
+ if (value != endptr)
+ valid |= 0x08;
+ }
+ else if (strcmp ("packetssend", key) == 0)
+ {
+ tx_packets = strtoll (value, &endptr, 0);
+ if (value != endptr)
+ valid |= 0x10;
+ }
+ else if ((strncmp ("allow_codec_", key, strlen ("allow_codec_")) == 0)
+ || (strncmp ("bwinlast", key, strlen ("bwinlast")) == 0)
+ || (strncmp ("bwoutlast", key, strlen ("bwoutlast")) == 0)
+ || (strncmp ("webpost_", key, strlen ("webpost_")) == 0)
+ || (strcmp ("adminemail", key) == 0)
+ || (strcmp ("clan_server", key) == 0)
+ || (strcmp ("countrynumber", key) == 0)
+ || (strcmp ("id", key) == 0)
+ || (strcmp ("ispname", key) == 0)
+ || (strcmp ("linkurl", key) == 0)
+ || (strcmp ("maxusers", key) == 0)
+ || (strcmp ("name", key) == 0)
+ || (strcmp ("password", key) == 0)
+ || (strcmp ("platform", key) == 0)
+ || (strcmp ("server_platform", key) == 0)
+ || (strcmp ("server_uptime", key) == 0)
+ || (strcmp ("server_version", key) == 0)
+ || (strcmp ("udpport", key) == 0)
+ || (strcmp ("uptime", key) == 0)
+ || (strcmp ("users_maximal", key) == 0)
+ || (strcmp ("welcomemessage", key) == 0))
+ /* ignore */;
+ else
+ {
+ INFO ("teamspeak2 plugin: Unknown key-value-pair: "
+ "key = %s; value = %s;", key, value);
+ }
+ } /* while (42) */
+
+ /* Collect vserver packet loss rates only if the loop above did not exit
+ * with an error. */
+ if ((status == 0) && (vserver != NULL))
+ {
+ status = tss2_vserver_gapl (read_fh, write_fh, vserver, &packet_loss);
+ if (status == 0)
+ {
+ valid |= 0x20;
+ }
+ else
+ {
+ WARNING ("teamspeak2 plugin: Reading package loss "
+ "for vserver %i failed.", vserver->port);
+ }
+ }
+
+ if ((valid & 0x01) == 0x01)
+ tss2_submit_gauge (plugin_instance, "users", NULL, users);
+
+ if ((valid & 0x06) == 0x06)
+ tss2_submit_io (plugin_instance, "io_octets", rx_octets, tx_octets);
+
+ if ((valid & 0x18) == 0x18)
+ tss2_submit_io (plugin_instance, "io_packets", rx_packets, tx_packets);
+
+ if ((valid & 0x20) == 0x20)
+ tss2_submit_gauge (plugin_instance, "percent", "packet_loss", packet_loss);
+
+ if ((valid & 0x40) == 0x40)
+ tss2_submit_gauge (plugin_instance, "gauge", "channels", channels);
+
+ if ((valid & 0x80) == 0x80)
+ tss2_submit_gauge (plugin_instance, "gauge", "servers", servers);
+
+ if (valid == 0)
+ return (-1);
+ return (0);
+} /* int tss2_read_vserver */
+
+static int tss2_config (const char *key, const char *value)
+{
+ /*
+ * Interpret configuration values
+ */
+ if (strcasecmp ("Host", key) == 0)
+ {
+ char *temp;
+
+ temp = strdup (value);
+ if (temp == NULL)
+ {
+ ERROR("teamspeak2 plugin: strdup failed.");
+ return (1);
+ }
+ sfree (config_host);
+ config_host = temp;
+ }
+ else if (strcasecmp ("Port", key) == 0)
+ {
+ char *temp;
+
+ temp = strdup (value);
+ if (temp == NULL)
+ {
+ ERROR("teamspeak2 plugin: strdup failed.");
+ return (1);
+ }
+ sfree (config_port);
+ config_port = temp;
+ }
+ else if (strcasecmp ("Server", key) == 0)
+ {
+ /* Server variable found */
+ int status;
+
+ status = tss2_add_vserver (atoi (value));
+ if (status != 0)
+ return (1);
+ }
+ else
+ {
+ /* Unknown variable found */
+ return (-1);
+ }
+
+ return 0;
+} /* int tss2_config */
+
+static int tss2_read (void)
+{
+ /*
+ * Poll function which collects global and vserver information
+ * and submits it to collectd
+ */
+ vserver_list_t *vserver;
+ int success = 0;
+ int status;
+
+ /* Handle global server variables */
+ status = tss2_read_vserver (NULL);
+ if (status == 0)
+ {
+ success++;
+ }
+ else
+ {
+ WARNING ("teamspeak2 plugin: Reading global server variables failed.");
+ }
+
+ /* Handle vservers */
+ for (vserver = server_list; vserver != NULL; vserver = vserver->next)
+ {
+ status = tss2_read_vserver (vserver);
+ if (status == 0)
+ {
+ success++;
+ }
+ else
+ {
+ WARNING ("teamspeak2 plugin: Reading statistics "
+ "for vserver %i failed.", vserver->port);
+ continue;
+ }
+ }
+
+ if (success == 0)
+ return (-1);
+ return (0);
+} /* int tss2_read */
+
+static int tss2_shutdown(void)
+{
+ /*
+ * Shutdown handler
+ */
+ vserver_list_t *entry;
+
+ tss2_close_socket ();
+
+ entry = server_list;
+ server_list = NULL;
+ while (entry != NULL)
+ {
+ vserver_list_t *next;
+
+ next = entry->next;
+ sfree (entry);
+ entry = next;
+ }
+
+ /* Get rid of the configuration */
+ sfree (config_host);
+ sfree (config_port);
+
+ return (0);
+} /* int tss2_shutdown */
+
+void module_register(void)
+{
+ /*
+ * Mandatory module_register function
+ */
+ plugin_register_config ("teamspeak2", tss2_config,
+ config_keys, config_keys_num);
+ plugin_register_read ("teamspeak2", tss2_read);
+ plugin_register_shutdown ("teamspeak2", tss2_shutdown);
+} /* void module_register */
+
+/* vim: set sw=4 ts=4 : */
diff --git a/src/types.db b/src/types.db
index a19f26dc997a9d2513f8dc3d5221307384ebfa9f..97c170b7dd90fef053ad105d1b82f866a56e3376 100644 (file)
--- a/src/types.db
+++ b/src/types.db
apache_requests count:COUNTER:0:134217728
apache_scoreboard count:GAUGE:0:65535
bitrate value:GAUGE:0:4294967295
+cache_result value:COUNTER:0:4294967295
+cache_size value:GAUGE:0:4294967295
charge value:GAUGE:0:U
connections value:COUNTER:0:U
counter value:COUNTER:U:U
disk_octets read:COUNTER:0:17179869183, write:COUNTER:0:17179869183
disk_ops read:COUNTER:0:4294967295, write:COUNTER:0:4294967295
disk_time read:COUNTER:0:1000000, write:COUNTER:0:1000000
+dns_answer value:COUNTER:0:65535
dns_octets queries:COUNTER:0:125000000, responses:COUNTER:0:125000000
dns_opcode value:COUNTER:0:65535
dns_qtype value:COUNTER:0:65535
+dns_question value:COUNTER:0:65535
dns_rcode value:COUNTER:0:65535
email_check value:GAUGE:0:U
email_count value:GAUGE:0:U
if_packets rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
if_rx_errors value:COUNTER:0:4294967295
if_tx_errors value:COUNTER:0:4294967295
+io_octets rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
+io_packets rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295
ipt_bytes value:COUNTER:0:134217728
ipt_packets value:COUNTER:0:134217728
irq value:COUNTER:U:65535
+latency value:GAUGE:0:65535
load shortterm:GAUGE:0:100, midterm:GAUGE:0:100, longterm:GAUGE:0:100
memcached_command value:COUNTER:0:U
memcached_connections value:GAUGE:0:U
nginx_requests value:COUNTER:0:134217728
percent percent:GAUGE:0:100.1
ping ping:GAUGE:0:65535
+players value:GAUGE:0:1000000
power value:GAUGE:0:U
ps_count processes:GAUGE:0:1000000, threads:GAUGE:0:1000000
ps_cputime user:COUNTER:0:16000000, syst:COUNTER:0:16000000
users users:GAUGE:0:65535
virt_cpu_total ns:COUNTER:0:256000000000
virt_vcpu ns:COUNTER:0:1000000000
+vmpage_action value:COUNTER:0:4294967295
+vmpage_faults minflt:COUNTER:0:9223372036854775807, majflt:COUNTER:0:9223372036854775807
+vmpage_io in:COUNTER:0:4294967295, out:COUNTER:0:4294967295
+vmpage_number value:GAUGE:0:4294967295
voltage_threshold value:GAUGE:U:U, threshold:GAUGE:U:U
voltage value:GAUGE:U:U
vs_memory value:GAUGE:0:9223372036854775807
diff --git a/src/types_list.c b/src/types_list.c
index ff8426286ac515472b63285aee83fe7df0d45cb7..3be792d5ca11bbd741f9b599a517c9cfea7da9ac 100644 (file)
--- a/src/types_list.c
+++ b/src/types_list.c
else
dsrc->max = atof (fields[3]);
- DEBUG ("parse_ds: dsrc = {%s, %i, %lf, %lf};",
- dsrc->name, dsrc->type, dsrc->min, dsrc->max);
-
return (0);
} /* int parse_ds */
return;
}
- DEBUG ("parse_line: ds = {%s, %i, %p};",
- ds->type, ds->ds_num, (void *) ds->ds);
-
plugin_register_data_set (ds);
sfree (ds->ds);
diff --git a/src/unixsock.c b/src/unixsock.c
index 45ed9c699b783fa9e9e10a4dfd482a594e59d1a4..025c91d5a6be76f6a2a94812af2f018782b17d43 100644 (file)
--- a/src/unixsock.c
+++ b/src/unixsock.c
#include "plugin.h"
#include "configfile.h"
+#include "utils_cmd_flush.h"
+#include "utils_cmd_getval.h"
+#include "utils_cmd_listval.h"
#include "utils_cmd_putval.h"
#include "utils_cmd_putnotif.h"
#define US_DEFAULT_PATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-unixsock"
-/*
- * Private data structures
- */
-/* linked list of cached values */
-typedef struct value_cache_s
-{
- char name[4*DATA_MAX_NAME_LEN];
- int values_num;
- gauge_t *gauge;
- counter_t *counter;
- const data_set_t *ds;
- time_t time;
- struct value_cache_s *next;
-} value_cache_t;
-
/*
* Private variables
*/
{
"SocketFile",
"SocketGroup",
- "SocketPerms",
- NULL
+ "SocketPerms"
};
-static int config_keys_num = 3;
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
static int loop = 0;
static pthread_t listen_thread = (pthread_t) 0;
-/* Linked list and auxilliary variables for saving values */
-static value_cache_t *cache_head = NULL;
-static pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
-static time_t cache_oldest = -1;
-
/*
* Functions
*/
-static value_cache_t *cache_search (const char *name)
-{
- value_cache_t *vc;
-
- for (vc = cache_head; vc != NULL; vc = vc->next)
- {
- if (strcmp (vc->name, name) == 0)
- break;
- } /* for vc = cache_head .. NULL */
-
- return (vc);
-} /* value_cache_t *cache_search */
-
-static int cache_insert (const data_set_t *ds, const value_list_t *vl)
-{
- /* We're called from `cache_update' so we don't need to lock the mutex */
- value_cache_t *vc;
- int i;
-
- DEBUG ("unixsock plugin: cache_insert: ds->type = %s; ds->ds_num = %i;"
- " vl->values_len = %i;",
- ds->type, ds->ds_num, vl->values_len);
-#if COLLECT_DEBUG
- assert (ds->ds_num == vl->values_len);
-#else
- if (ds->ds_num != vl->values_len)
- {
- ERROR ("unixsock plugin: ds->type = %s: (ds->ds_num = %i) != "
- "(vl->values_len = %i)",
- ds->type, ds->ds_num, vl->values_len);
- return (-1);
- }
-#endif
-
- vc = (value_cache_t *) malloc (sizeof (value_cache_t));
- if (vc == NULL)
- {
- char errbuf[1024];
- pthread_mutex_unlock (&cache_lock);
- ERROR ("unixsock plugin: malloc failed: %s",
- sstrerror (errno, errbuf, sizeof (errbuf)));
- return (-1);
- }
-
- vc->gauge = (gauge_t *) malloc (sizeof (gauge_t) * vl->values_len);
- if (vc->gauge == NULL)
- {
- char errbuf[1024];
- pthread_mutex_unlock (&cache_lock);
- ERROR ("unixsock plugin: malloc failed: %s",
- sstrerror (errno, errbuf, sizeof (errbuf)));
- free (vc);
- return (-1);
- }
-
- vc->counter = (counter_t *) malloc (sizeof (counter_t) * vl->values_len);
- if (vc->counter == NULL)
- {
- char errbuf[1024];
- pthread_mutex_unlock (&cache_lock);
- ERROR ("unixsock plugin: malloc failed: %s",
- sstrerror (errno, errbuf, sizeof (errbuf)));
- free (vc->gauge);
- free (vc);
- return (-1);
- }
-
- if (FORMAT_VL (vc->name, sizeof (vc->name), vl, ds))
- {
- pthread_mutex_unlock (&cache_lock);
- ERROR ("unixsock plugin: FORMAT_VL failed.");
- free (vc->counter);
- free (vc->gauge);
- free (vc);
- return (-1);
- }
-
- for (i = 0; i < ds->ds_num; i++)
- {
- if (ds->ds[i].type == DS_TYPE_COUNTER)
- {
- vc->gauge[i] = 0.0;
- vc->counter[i] = vl->values[i].counter;
- }
- else if (ds->ds[i].type == DS_TYPE_GAUGE)
- {
- vc->gauge[i] = vl->values[i].gauge;
- vc->counter[i] = 0;
- }
- else
- {
- vc->gauge[i] = 0.0;
- vc->counter[i] = 0;
- }
- }
- vc->values_num = ds->ds_num;
- vc->ds = ds;
-
- vc->next = cache_head;
- cache_head = vc;
-
- vc->time = vl->time;
- if ((vc->time < cache_oldest) || (-1 == cache_oldest))
- cache_oldest = vc->time;
-
- pthread_mutex_unlock (&cache_lock);
- return (0);
-} /* int cache_insert */
-
-static int cache_update (const data_set_t *ds, const value_list_t *vl)
-{
- char name[4*DATA_MAX_NAME_LEN];;
- value_cache_t *vc;
- int i;
-
- if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
- return (-1);
-
- pthread_mutex_lock (&cache_lock);
-
- vc = cache_search (name);
-
- /* pthread_mutex_lock is called by cache_insert. */
- if (vc == NULL)
- return (cache_insert (ds, vl));
-
- assert (vc->values_num == ds->ds_num);
- assert (vc->values_num == vl->values_len);
-
- /* Avoid floating-point exceptions due to division by zero. */
- if (vc->time >= vl->time)
- {
- pthread_mutex_unlock (&cache_lock);
- ERROR ("unixsock plugin: vc->time >= vl->time. vc->time = %u; "
- "vl->time = %u; vl = %s;",
- (unsigned int) vc->time, (unsigned int) vl->time,
- name);
- return (-1);
- } /* if (vc->time >= vl->time) */
-
- /*
- * Update the values. This is possibly a lot more that you'd expect
- * because we honor min and max values and handle counter overflows here.
- */
- for (i = 0; i < ds->ds_num; i++)
- {
- if (ds->ds[i].type == DS_TYPE_COUNTER)
- {
- if (vl->values[i].counter < vc->counter[i])
- {
- if (vl->values[i].counter <= 4294967295U)
- {
- vc->gauge[i] = ((4294967295U - vl->values[i].counter)
- + vc->counter[i]) / (vl->time - vc->time);
- }
- else
- {
- vc->gauge[i] = ((18446744073709551615ULL - vl->values[i].counter)
- + vc->counter[i]) / (vl->time - vc->time);
- }
- }
- else
- {
- vc->gauge[i] = (vl->values[i].counter - vc->counter[i])
- / (vl->time - vc->time);
- }
-
- vc->counter[i] = vl->values[i].counter;
- }
- else if (ds->ds[i].type == DS_TYPE_GAUGE)
- {
- vc->gauge[i] = vl->values[i].gauge;
- vc->counter[i] = 0;
- }
- else
- {
- vc->gauge[i] = NAN;
- vc->counter[i] = 0;
- }
-
- if (isnan (vc->gauge[i])
- || (!isnan (ds->ds[i].min) && (vc->gauge[i] < ds->ds[i].min))
- || (!isnan (ds->ds[i].max) && (vc->gauge[i] > ds->ds[i].max)))
- vc->gauge[i] = NAN;
- } /* for i = 0 .. ds->ds_num */
-
- vc->ds = ds;
- vc->time = vl->time;
-
- if ((vc->time < cache_oldest) || (-1 == cache_oldest))
- cache_oldest = vc->time;
-
- pthread_mutex_unlock (&cache_lock);
- return (0);
-} /* int cache_update */
-
-static void cache_flush (int max_age)
-{
- value_cache_t *this;
- value_cache_t *prev;
- time_t now;
-
- pthread_mutex_lock (&cache_lock);
-
- now = time (NULL);
-
- if ((now - cache_oldest) <= max_age)
- {
- pthread_mutex_unlock (&cache_lock);
- return;
- }
-
- cache_oldest = now;
-
- prev = NULL;
- this = cache_head;
-
- while (this != NULL)
- {
- if ((now - this->time) <= max_age)
- {
- if (this->time < cache_oldest)
- cache_oldest = this->time;
-
- prev = this;
- this = this->next;
- continue;
- }
-
- if (prev == NULL)
- cache_head = this->next;
- else
- prev->next = this->next;
-
- free (this->gauge);
- free (this->counter);
- free (this);
-
- if (prev == NULL)
- this = cache_head;
- else
- this = prev->next;
- } /* while (this != NULL) */
-
- pthread_mutex_unlock (&cache_lock);
-} /* void cache_flush */
-
static int us_open_socket (void)
{
struct sockaddr_un sa;
return (0);
} /* int us_open_socket */
-static int us_handle_getval (FILE *fh, char **fields, int fields_num)
-{
- char *hostname;
- char *plugin;
- char *plugin_instance;
- char *type;
- char *type_instance;
- char name[4*DATA_MAX_NAME_LEN];
- value_cache_t *vc;
- int status;
- int i;
-
- if (fields_num != 2)
- {
- DEBUG ("unixsock plugin: Wrong number of fields: %i", fields_num);
- fprintf (fh, "-1 Wrong number of fields: Got %i, expected 2.\n",
- fields_num);
- fflush (fh);
- return (-1);
- }
- DEBUG ("unixsock plugin: Got query for `%s'", fields[1]);
-
- status = parse_identifier (fields[1], &hostname,
- &plugin, &plugin_instance,
- &type, &type_instance);
- if (status != 0)
- {
- DEBUG ("unixsock plugin: Cannot parse `%s'", fields[1]);
- fprintf (fh, "-1 Cannot parse identifier.\n");
- fflush (fh);
- return (-1);
- }
-
- status = format_name (name, sizeof (name),
- hostname, plugin, plugin_instance, type, type_instance);
- if (status != 0)
- {
- fprintf (fh, "-1 format_name failed.\n");
- return (-1);
- }
-
- pthread_mutex_lock (&cache_lock);
-
- DEBUG ("vc = cache_search (%s)", name);
- vc = cache_search (name);
-
- if (vc == NULL)
- {
- DEBUG ("Did not find cache entry.");
- fprintf (fh, "-1 No such value");
- }
- else
- {
- DEBUG ("Found cache entry.");
- fprintf (fh, "%i", vc->values_num);
- for (i = 0; i < vc->values_num; i++)
- {
- fprintf (fh, " %s=", vc->ds->ds[i].name);
- if (isnan (vc->gauge[i]))
- fprintf (fh, "NaN");
- else
- fprintf (fh, "%12e", vc->gauge[i]);
- }
- }
-
- /* Free the mutex as soon as possible and definitely before flushing */
- pthread_mutex_unlock (&cache_lock);
-
- fprintf (fh, "\n");
- fflush (fh);
-
- return (0);
-} /* int us_handle_getval */
-
-static int us_handle_listval (FILE *fh, char **fields, int fields_num)
-{
- char buffer[1024];
- char **value_list = NULL;
- int value_list_len = 0;
- value_cache_t *entry;
- int i;
-
- if (fields_num != 1)
- {
- DEBUG ("unixsock plugin: us_handle_listval: "
- "Wrong number of fields: %i", fields_num);
- fprintf (fh, "-1 Wrong number of fields: Got %i, expected 1.\n",
- fields_num);
- fflush (fh);
- return (-1);
- }
-
- pthread_mutex_lock (&cache_lock);
-
- for (entry = cache_head; entry != NULL; entry = entry->next)
- {
- char **tmp;
-
- snprintf (buffer, sizeof (buffer), "%u %s\n",
- (unsigned int) entry->time, entry->name);
- buffer[sizeof (buffer) - 1] = '\0';
-
- tmp = realloc (value_list, sizeof (char *) * (value_list_len + 1));
- if (tmp == NULL)
- continue;
- value_list = tmp;
-
- value_list[value_list_len] = strdup (buffer);
-
- if (value_list[value_list_len] != NULL)
- value_list_len++;
- } /* for (entry) */
-
- pthread_mutex_unlock (&cache_lock);
-
- DEBUG ("unixsock plugin: us_handle_listval: value_list_len = %i", value_list_len);
- fprintf (fh, "%i Values found\n", value_list_len);
- for (i = 0; i < value_list_len; i++)
- fputs (value_list[i], fh);
- fflush (fh);
-
- return (0);
-} /* int us_handle_listval */
-
static void *us_handle_client (void *arg)
{
int fd;
if (strcasecmp (fields[0], "getval") == 0)
{
- us_handle_getval (fhout, fields, fields_num);
+ handle_getval (fhout, fields, fields_num);
}
else if (strcasecmp (fields[0], "putval") == 0)
{
}
else if (strcasecmp (fields[0], "listval") == 0)
{
- us_handle_listval (fhout, fields, fields_num);
+ handle_listval (fhout, fields, fields_num);
}
else if (strcasecmp (fields[0], "putnotif") == 0)
{
handle_putnotif (fhout, fields, fields_num);
}
+ else if (strcasecmp (fields[0], "flush") == 0)
+ {
+ handle_flush (fhout, fields, fields_num);
+ }
else
{
fprintf (fhout, "-1 Unknown command: %s\n", fields[0]);
{
if (strcasecmp (key, "SocketFile") == 0)
{
+ char *new_sock_file = strdup (val);
+ if (new_sock_file == NULL)
+ return (1);
+
sfree (sock_file);
- sock_file = strdup (val);
+ sock_file = new_sock_file;
}
else if (strcasecmp (key, "SocketGroup") == 0)
{
+ char *new_sock_group = strdup (val);
+ if (new_sock_group == NULL)
+ return (1);
+
sfree (sock_group);
- sock_group = strdup (val);
+ sock_group = new_sock_group;
}
else if (strcasecmp (key, "SocketPerms") == 0)
{
return (0);
} /* int us_shutdown */
-static int us_write (const data_set_t *ds, const value_list_t *vl)
-{
- cache_update (ds, vl);
- cache_flush (2 * interval_g);
-
- return (0);
-}
-
void module_register (void)
{
plugin_register_config ("unixsock", us_config,
config_keys, config_keys_num);
plugin_register_init ("unixsock", us_init);
- plugin_register_write ("unixsock", us_write);
plugin_register_shutdown ("unixsock", us_shutdown);
} /* void module_register (void) */
diff --git a/src/utils_cache.c b/src/utils_cache.c
index b9b896215be1cb631f5c1b0252bcf4c341af6846..9f7e3b686e52083f37871afc4f69391d1dd191bc 100644 (file)
--- a/src/utils_cache.c
+++ b/src/utils_cache.c
/**
* collectd - src/utils_cache.c
- * Copyright (C) 2007 Florian octo Forster
+ * Copyright (C) 2007,2008 Florian octo Forster
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
return (0);
} /* int uc_update */
-gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl)
+int uc_get_rate_by_name (const char *name, gauge_t **ret_values, size_t *ret_values_num)
{
- char name[6 * DATA_MAX_NAME_LEN];
gauge_t *ret = NULL;
+ size_t ret_num = 0;
cache_entry_t *ce = NULL;
-
- if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
- {
- ERROR ("uc_get_rate: FORMAT_VL failed.");
- return (NULL);
- }
+ int status = 0;
pthread_mutex_lock (&cache_lock);
if (c_avl_get (cache_tree, name, (void *) &ce) == 0)
{
assert (ce != NULL);
- assert (ce->values_num == ds->ds_num);
- ret = (gauge_t *) malloc (ce->values_num * sizeof (gauge_t));
+ ret_num = ce->values_num;
+ ret = (gauge_t *) malloc (ret_num * sizeof (gauge_t));
if (ret == NULL)
{
- ERROR ("uc_get_rate: malloc failed.");
+ ERROR ("utils_cache: uc_get_rate_by_name: malloc failed.");
+ status = -1;
}
else
{
- memcpy (ret, ce->values_gauge, ce->values_num * sizeof (gauge_t));
+ memcpy (ret, ce->values_gauge, ret_num * sizeof (gauge_t));
}
}
+ else
+ {
+ DEBUG ("utils_cache: uc_get_rate_by_name: No such value: %s", name);
+ status = -1;
+ }
pthread_mutex_unlock (&cache_lock);
+ if (status == 0)
+ {
+ *ret_values = ret;
+ *ret_values_num = ret_num;
+ }
+
+ return (status);
+} /* gauge_t *uc_get_rate_by_name */
+
+gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl)
+{
+ char name[6 * DATA_MAX_NAME_LEN];
+ gauge_t *ret = NULL;
+ size_t ret_num = 0;
+ int status;
+
+ if (FORMAT_VL (name, sizeof (name), vl, ds) != 0)
+ {
+ ERROR ("uc_insert: FORMAT_VL failed.");
+ return (NULL);
+ }
+
+ status = uc_get_rate_by_name (name, &ret, &ret_num);
+ if (status != 0)
+ return (NULL);
+
+ /* This is important - the caller has no other way of knowing how many
+ * values are returned. */
+ if (ret_num != ds->ds_num)
+ {
+ ERROR ("utils_cache: uc_get_rate: ds[%s] has %i values, "
+ "but uc_get_rate_by_name returned %i.",
+ ds->type, ds->ds_num, ret_num);
+ sfree (ret);
+ return (NULL);
+ }
+
return (ret);
} /* gauge_t *uc_get_rate */
+int uc_get_names (char ***ret_names, time_t **ret_times, size_t *ret_number)
+{
+ c_avl_iterator_t *iter;
+ char *key;
+ cache_entry_t *value;
+
+ char **names = NULL;
+ time_t *times = NULL;
+ size_t number = 0;
+
+ int status = 0;
+
+ if ((ret_names == NULL) || (ret_number == NULL))
+ return (-1);
+
+ pthread_mutex_lock (&cache_lock);
+
+ iter = c_avl_get_iterator (cache_tree);
+ while (c_avl_iterator_next (iter, (void *) &key, (void *) &value) == 0)
+ {
+ char **temp;
+
+ if (ret_times != NULL)
+ {
+ time_t *tmp_times;
+
+ tmp_times = (time_t *) realloc (times, sizeof (time_t) * (number + 1));
+ if (tmp_times == NULL)
+ {
+ status = -1;
+ break;
+ }
+ times = tmp_times;
+ times[number] = value->last_time;
+ }
+
+ temp = (char **) realloc (names, sizeof (char *) * (number + 1));
+ if (temp == NULL)
+ {
+ status = -1;
+ break;
+ }
+ names = temp;
+ names[number] = strdup (key);
+ if (names[number] == NULL)
+ {
+ status = -1;
+ break;
+ }
+ number++;
+ } /* while (c_avl_iterator_next) */
+
+ c_avl_iterator_destroy (iter);
+ pthread_mutex_unlock (&cache_lock);
+
+ if (status != 0)
+ {
+ size_t i;
+
+ for (i = 0; i < number; i++)
+ {
+ sfree (names[i]);
+ }
+ sfree (names);
+
+ return (-1);
+ }
+
+ *ret_names = names;
+ if (ret_times != NULL)
+ *ret_times = times;
+ *ret_number = number;
+
+ return (0);
+} /* int uc_get_names */
+
int uc_get_state (const data_set_t *ds, const value_list_t *vl)
{
char name[6 * DATA_MAX_NAME_LEN];
diff --git a/src/utils_cache.h b/src/utils_cache.h
index 51d9c031385d026da27203bb8d12b7c4c40c928a..a965febe9cd445a3ef25bded87603615e3030ec5 100644 (file)
--- a/src/utils_cache.h
+++ b/src/utils_cache.h
int uc_init (void);
int uc_check_timeout (void);
int uc_update (const data_set_t *ds, const value_list_t *vl);
+int uc_get_rate_by_name (const char *name, gauge_t **ret_values, size_t *ret_values_num);
gauge_t *uc_get_rate (const data_set_t *ds, const value_list_t *vl);
+int uc_get_names (char ***ret_names, time_t **ret_times, size_t *ret_number);
+
int uc_get_state (const data_set_t *ds, const value_list_t *vl);
int uc_set_state (const data_set_t *ds, const value_list_t *vl, int state);
diff --git a/src/utils_cmd_flush.c b/src/utils_cmd_flush.c
--- /dev/null
+++ b/src/utils_cmd_flush.c
@@ -0,0 +1,90 @@
+/**
+ * collectd - src/utils_cmd_flush.c
+ * Copyright (C) 2008 Sebastian Harl
+ * Copyright (C) 2008 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Sebastian "tokkee" Harl <sh at tokkee.org>
+ * Florian "octo" Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+int handle_flush (FILE *fh, char **fields, int fields_num)
+{
+ int success = 0;
+ int error = 0;
+
+ int timeout = -1;
+
+ int i;
+
+ for (i = 1; i < fields_num; i++)
+ {
+ char *option = fields[i];
+ int status = 0;
+
+ if (strncasecmp ("plugin=", option, strlen ("plugin=")) == 0)
+ {
+ char *plugin = option + strlen ("plugin=");
+
+ if (0 == plugin_flush_one (timeout, plugin))
+ ++success;
+ else
+ ++error;
+ }
+ else if (strncasecmp ("timeout=", option, strlen ("timeout=")) == 0)
+ {
+ char *endptr = NULL;
+ char *value = option + strlen ("timeout=");
+
+ errno = 0;
+ timeout = strtol (value, &endptr, 0);
+
+ if ((endptr == value) || (0 != errno))
+ status = -1;
+ else if (0 >= timeout)
+ timeout = -1;
+ }
+ else
+ status = -1;
+
+ if (status != 0)
+ {
+ fprintf (fh, "-1 Cannot parse option %s\n", option);
+ fflush (fh);
+ return (-1);
+ }
+ }
+
+ if ((success + error) > 0)
+ {
+ fprintf (fh, "0 Done: %i successful, %i errors\n", success, error);
+ }
+ else
+ {
+ plugin_flush_all (timeout);
+ fprintf (fh, "0 Done\n");
+ }
+ fflush (fh);
+
+ return (0);
+} /* int handle_flush */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_cmd_flush.h b/src/utils_cmd_flush.h
--- /dev/null
+++ b/src/utils_cmd_flush.h
@@ -0,0 +1,30 @@
+/**
+ * collectd - src/utils_cmd_flush.h
+ * Copyright (C) 2008 Sebastian Harl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Sebastian "tokkee" Harl <sh at tokkee.org>
+ **/
+
+#ifndef UTILS_CMD_FLUSH_H
+#define UTILS_CMD_FLUSH_H 1
+
+int handle_flush (FILE *fh, char **fields, int fields_num);
+
+#endif /* UTILS_CMD_FLUSH_H */
+
+/* vim: set sw=4 ts=4 tw=78 noexpandtab : */
+
diff --git a/src/utils_cmd_getval.c b/src/utils_cmd_getval.c
--- /dev/null
+++ b/src/utils_cmd_getval.c
@@ -0,0 +1,129 @@
+/**
+ * collectd - src/utils_cms_getval.c
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_cache.h"
+
+int handle_getval (FILE *fh, char **fields, int fields_num)
+{
+ char *hostname;
+ char *plugin;
+ char *plugin_instance;
+ char *type;
+ char *type_instance;
+ gauge_t *values;
+ size_t values_num;
+
+ char *identifier_copy;
+
+ const data_set_t *ds;
+
+ int status;
+ int i;
+
+ if (fields_num != 2)
+ {
+ DEBUG ("unixsock plugin: Wrong number of fields: %i", fields_num);
+ fprintf (fh, "-1 Wrong number of fields: Got %i, expected 2.\n",
+ fields_num);
+ fflush (fh);
+ return (-1);
+ }
+ DEBUG ("unixsock plugin: Got query for `%s'", fields[1]);
+
+ if (strlen (fields[1]) < strlen ("h/p/t"))
+ {
+ fprintf (fh, "-1 Invalied identifier, %s\n", fields[1]);
+ fflush (fh);
+ return (-1);
+ }
+
+ /* parse_identifier() modifies its first argument,
+ * returning pointers into it */
+ identifier_copy = sstrdup (fields[1]);
+
+ status = parse_identifier (identifier_copy, &hostname,
+ &plugin, &plugin_instance,
+ &type, &type_instance);
+ if (status != 0)
+ {
+ DEBUG ("unixsock plugin: Cannot parse `%s'", fields[1]);
+ fprintf (fh, "-1 Cannot parse identifier.\n");
+ fflush (fh);
+ sfree (identifier_copy);
+ return (-1);
+ }
+
+ ds = plugin_get_ds (type);
+ if (ds == NULL)
+ {
+ DEBUG ("unixsock plugin: plugin_get_ds (%s) == NULL;", type);
+ fprintf (fh, "-1 Type `%s' is unknown.\n", type);
+ fflush (fh);
+ sfree (identifier_copy);
+ return (-1);
+ }
+
+ values = NULL;
+ values_num = 0;
+ status = uc_get_rate_by_name (fields[1], &values, &values_num);
+ if (status != 0)
+ {
+ fprintf (fh, "-1 No such value\n");
+ fflush (fh);
+ sfree (identifier_copy);
+ return (-1);
+ }
+
+ if (ds->ds_num != values_num)
+ {
+ ERROR ("ds[%s]->ds_num = %i, "
+ "but uc_get_rate_by_name returned %i values.",
+ ds->type, ds->ds_num, values_num);
+ fprintf (fh, "-1 Error reading value from cache.\n");
+ fflush (fh);
+ sfree (values);
+ sfree (identifier_copy);
+ return (-1);
+ }
+
+ fprintf (fh, "%u Value%s found\n", (unsigned int) values_num,
+ (values_num == 1) ? "" : "s");
+ for (i = 0; i < values_num; i++)
+ {
+ fprintf (fh, "%s=", ds->ds[i].name);
+ if (isnan (values[i]))
+ fprintf (fh, "NaN\n");
+ else
+ fprintf (fh, "%12e\n", values[i]);
+ }
+ fflush (fh);
+
+ sfree (values);
+ sfree (identifier_copy);
+
+ return (0);
+} /* int handle_getval */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_getval.h b/src/utils_cmd_getval.h
--- /dev/null
+++ b/src/utils_cmd_getval.h
@@ -0,0 +1,29 @@
+/**
+ * collectd - src/utils_cms_getval.h
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_GETVAL_H
+#define UTILS_CMD_GETVAL_H 1
+
+int handle_getval (FILE *fh, char **fields, int fields_num);
+
+#endif /* UTILS_CMD_GETVAL_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_listval.c b/src/utils_cmd_listval.c
--- /dev/null
+++ b/src/utils_cmd_listval.c
@@ -0,0 +1,64 @@
+/**
+ * collectd - src/utils_cms_listval.c
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_cmd_listval.h"
+#include "utils_cache.h"
+
+int handle_listval (FILE *fh, char **fields, int fields_num)
+{
+ char **names = NULL;
+ time_t *times = NULL;
+ size_t number = 0;
+ size_t i;
+ int status;
+
+ if (fields_num != 1)
+ {
+ DEBUG ("command listval: us_handle_listval: Wrong number of fields: %i",
+ fields_num);
+ fprintf (fh, "-1 Wrong number of fields: Got %i, expected 1.\n",
+ fields_num);
+ fflush (fh);
+ return (-1);
+ }
+
+ status = uc_get_names (&names, ×, &number);
+ if (status != 0)
+ {
+ DEBUG ("command listval: uc_get_names failed with status %i", status);
+ fprintf (fh, "-1 uc_get_names failed.\n");
+ fflush (fh);
+ return (-1);
+ }
+
+ fprintf (fh, "%i Value%s found\n", (int) number, (number == 1) ? "" : "s");
+ for (i = 0; i < number; i++)
+ fprintf (fh, "%u %s\n", (unsigned int) times[i], names[i]);
+ fflush (fh);
+
+ return (0);
+} /* int handle_listval */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_cmd_listval.h b/src/utils_cmd_listval.h
--- /dev/null
+++ b/src/utils_cmd_listval.h
@@ -0,0 +1,29 @@
+/**
+ * collectd - src/utils_cms_listval.h
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_CMD_LISTVAL_H
+#define UTILS_CMD_LISTVAL_H 1
+
+int handle_listval (FILE *fh, char **fields, int fields_num);
+
+#endif /* UTILS_CMD_LISTVAL_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
index a9531721d6807076e36a258c7912bf7547e36db5..08b3bb37682f96c0862e7d69d29bc57af0c038f9 100644 (file)
--- a/src/utils_cmd_putnotif.h
+++ b/src/utils_cmd_putnotif.h
#ifndef UTILS_CMD_PUTNOTIF_H
#define UTILS_CMD_PUTNOTIF_H 1
-#include "plugin.h"
-
int handle_putnotif (FILE *fh, char **fields, int fields_num);
/* vim: set shiftwidth=2 softtabstop=2 tabstop=8 : */
diff --git a/src/utils_cmd_putval.h b/src/utils_cmd_putval.h
index 609efcbd70e4fb0fe8ba3486819a766bfa0b033d..2ae45323eed7f2581b6a4d5cec489247ba39c4af 100644 (file)
--- a/src/utils_cmd_putval.h
+++ b/src/utils_cmd_putval.h
#ifndef UTILS_CMD_PUTVAL_H
#define UTILS_CMD_PUTVAL_H 1
-#include "plugin.h"
-
int handle_putval (FILE *fh, char **fields, int fields_num);
#endif /* UTILS_CMD_PUTVAL_H */
diff --git a/src/utils_ignorelist.c b/src/utils_ignorelist.c
index 94d6bdae7c92a5989149fb2ce73e0b3a62e4ecec..1d9467fe210d89e6ce0f9c6537e3be9e389d6e8e 100644 (file)
--- a/src/utils_ignorelist.c
+++ b/src/utils_ignorelist.c
/**
* collectd - src/utils_ignorelist.c
* Copyright (C) 2006 Lubos Stanek <lubek at users.sourceforge.net>
+ * Copyright (C) 2008 Florian Forster <octo at verplant.org>
*
* This program is free software; you can redistribute it and/
* or modify it under the terms of the GNU General Public Li-
*
* Authors:
* Lubos Stanek <lubek at users.sourceforge.net>
+ * Florian Forster <octo at verplant.org>
**/
/**
* ignorelist handles plugin's list of configured collectable
{
ignorelist_item_t *traverse;
- assert (il != NULL);
-
/* if no entries, collect all */
- if (il->head == NULL)
+ if ((il == NULL) || (il->head == NULL))
return (0);
if ((entry == NULL) || (strlen (entry) == 0))
diff --git a/src/utils_match.c b/src/utils_match.c
--- /dev/null
+++ b/src/utils_match.c
@@ -0,0 +1,288 @@
+/**
+ * collectd - src/utils_match.c
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#include "utils_match.h"
+
+#include <regex.h>
+
+#define UTILS_MATCH_FLAGS_FREE_USER_DATA 0x01
+
+struct cu_match_s
+{
+ regex_t regex;
+ int flags;
+
+ int (*callback) (const char *str, char * const *matches, size_t matches_num,
+ void *user_data);
+ void *user_data;
+};
+
+/*
+ * Private functions
+ */
+static char *match_substr (const char *str, int begin, int end)
+{
+ char *ret;
+ size_t ret_len;
+
+ if ((begin < 0) || (end < 0) || (begin >= end))
+ return (NULL);
+ if (end > (strlen (str) + 1))
+ {
+ ERROR ("utils_match: match_substr: `end' points after end of string.");
+ return (NULL);
+ }
+
+ ret_len = end - begin;
+ ret = (char *) malloc (sizeof (char) * (ret_len + 1));
+ if (ret == NULL)
+ {
+ ERROR ("utils_match: match_substr: malloc failed.");
+ return (NULL);
+ }
+
+ sstrncpy (ret, str + begin, ret_len + 1);
+ return (ret);
+} /* char *match_substr */
+
+static int default_callback (const char *str,
+ char * const *matches, size_t matches_num, void *user_data)
+{
+ cu_match_value_t *data = (cu_match_value_t *) user_data;
+
+ if (data->ds_type & UTILS_MATCH_DS_TYPE_GAUGE)
+ {
+ gauge_t value;
+ char *endptr = NULL;
+
+ if (matches_num < 2)
+ return (-1);
+
+ value = strtod (matches[1], &endptr);
+ if (matches[1] == endptr)
+ return (-1);
+
+ if ((data->values_num == 0)
+ || (data->ds_type & UTILS_MATCH_CF_GAUGE_LAST))
+ {
+ data->value.gauge = value;
+ }
+ else if (data->ds_type & UTILS_MATCH_CF_GAUGE_AVERAGE)
+ {
+ double f = ((double) data->values_num)
+ / ((double) (data->values_num + 1));
+ data->value.gauge = (data->value.gauge * f) + (value * (1.0 - f));
+ }
+ else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MIN)
+ {
+ if (data->value.gauge > value)
+ data->value.gauge = value;
+ }
+ else if (data->ds_type & UTILS_MATCH_CF_GAUGE_MAX)
+ {
+ if (data->value.gauge < value)
+ data->value.gauge = value;
+ }
+ else
+ {
+ ERROR ("utils_match: default_callback: obj->ds_type is invalid!");
+ return (-1);
+ }
+
+ data->values_num++;
+ }
+ else if (data->ds_type & UTILS_MATCH_DS_TYPE_COUNTER)
+ {
+ counter_t value;
+ char *endptr = NULL;
+
+ if (data->ds_type & UTILS_MATCH_CF_COUNTER_INC)
+ {
+ data->value.counter++;
+ data->values_num++;
+ return (0);
+ }
+
+ if (matches_num < 2)
+ return (-1);
+
+ value = strtoll (matches[1], &endptr, 0);
+ if (matches[1] == endptr)
+ return (-1);
+
+ if (data->ds_type & UTILS_MATCH_CF_COUNTER_SET)
+ data->value.counter = value;
+ else if (data->ds_type & UTILS_MATCH_CF_COUNTER_ADD)
+ data->value.counter += value;
+ else
+ {
+ ERROR ("utils_match: default_callback: obj->ds_type is invalid!");
+ return (-1);
+ }
+
+ data->values_num++;
+ }
+ else
+ {
+ ERROR ("utils_match: default_callback: obj->ds_type is invalid!");
+ return (-1);
+ }
+
+ return (0);
+} /* int default_callback */
+
+/*
+ * Public functions
+ */
+cu_match_t *match_create_callback (const char *regex,
+ int (*callback) (const char *str,
+ char * const *matches, size_t matches_num, void *user_data),
+ void *user_data)
+{
+ cu_match_t *obj;
+ int status;
+
+ DEBUG ("utils_match: match_create_callback: regex = %s", regex);
+
+ obj = (cu_match_t *) malloc (sizeof (cu_match_t));
+ if (obj == NULL)
+ return (NULL);
+ memset (obj, '\0', sizeof (cu_match_t));
+
+ status = regcomp (&obj->regex, regex, REG_EXTENDED);
+ if (status != 0)
+ {
+ ERROR ("Compiling the regular expression \"%s\" failed.", regex);
+ sfree (obj);
+ return (NULL);
+ }
+
+ obj->callback = callback;
+ obj->user_data = user_data;
+
+ return (obj);
+} /* cu_match_t *match_create_callback */
+
+cu_match_t *match_create_simple (const char *regex, int match_ds_type)
+{
+ cu_match_value_t *user_data;
+ cu_match_t *obj;
+
+ user_data = (cu_match_value_t *) malloc (sizeof (cu_match_value_t));
+ if (user_data == NULL)
+ return (NULL);
+ memset (user_data, '\0', sizeof (cu_match_value_t));
+ user_data->ds_type = match_ds_type;
+
+ obj = match_create_callback (regex, default_callback, user_data);
+ if (obj == NULL)
+ {
+ sfree (user_data);
+ return (NULL);
+ }
+
+ obj->flags |= UTILS_MATCH_FLAGS_FREE_USER_DATA;
+
+ return (obj);
+} /* cu_match_t *match_create_simple */
+
+void match_destroy (cu_match_t *obj)
+{
+ if (obj == NULL)
+ return;
+
+ if (obj->flags & UTILS_MATCH_FLAGS_FREE_USER_DATA)
+ {
+ sfree (obj->user_data);
+ }
+
+ sfree (obj);
+} /* void match_destroy */
+
+int match_apply (cu_match_t *obj, const char *str)
+{
+ int status;
+ regmatch_t re_match[32];
+ char *matches[32];
+ size_t matches_num;
+ int i;
+
+ if ((obj == NULL) || (str == NULL))
+ return (-1);
+
+ status = regexec (&obj->regex, str,
+ STATIC_ARRAY_SIZE (re_match), re_match,
+ /* eflags = */ 0);
+
+ /* Regex did not match */
+ if (status != 0)
+ return (0);
+
+ memset (matches, '\0', sizeof (matches));
+ for (matches_num = 0; matches_num < STATIC_ARRAY_SIZE (matches); matches_num++)
+ {
+ if ((re_match[matches_num].rm_so < 0)
+ || (re_match[matches_num].rm_eo < 0))
+ break;
+
+ matches[matches_num] = match_substr (str,
+ re_match[matches_num].rm_so, re_match[matches_num].rm_eo);
+ if (matches[matches_num] == NULL)
+ {
+ status = -1;
+ break;
+ }
+ }
+
+ if (status != 0)
+ {
+ ERROR ("utils_match: match_apply: match_substr failed.");
+ }
+ else
+ {
+ status = obj->callback (str, matches, matches_num, obj->user_data);
+ if (status != 0)
+ {
+ ERROR ("utils_match: match_apply: callback failed.");
+ }
+ }
+
+ for (i = 0; i < matches_num; i++)
+ {
+ sfree (matches[i]);
+ }
+
+ return (status);
+} /* int match_apply */
+
+void *match_get_user_data (cu_match_t *obj)
+{
+ if (obj == NULL)
+ return (NULL);
+ return (obj->user_data);
+} /* void *match_get_user_data */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_match.h b/src/utils_match.h
--- /dev/null
+++ b/src/utils_match.h
@@ -0,0 +1,142 @@
+/**
+ * collectd - src/utils_match.h
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#ifndef UTILS_MATCH_H
+#define UTILS_MATCH_H 1
+
+#include "plugin.h"
+
+/*
+ * Defines
+ */
+#define UTILS_MATCH_DS_TYPE_GAUGE 0x10
+#define UTILS_MATCH_DS_TYPE_COUNTER 0x20
+
+#define UTILS_MATCH_CF_GAUGE_AVERAGE 0x01
+#define UTILS_MATCH_CF_GAUGE_MIN 0x02
+#define UTILS_MATCH_CF_GAUGE_MAX 0x04
+#define UTILS_MATCH_CF_GAUGE_LAST 0x08
+
+#define UTILS_MATCH_CF_COUNTER_SET 0x01
+#define UTILS_MATCH_CF_COUNTER_ADD 0x02
+#define UTILS_MATCH_CF_COUNTER_INC 0x04
+
+/*
+ * Data types
+ */
+struct cu_match_s;
+typedef struct cu_match_s cu_match_t;
+
+struct cu_match_value_s
+{
+ int ds_type;
+ value_t value;
+ unsigned int values_num;
+};
+typedef struct cu_match_value_s cu_match_value_t;
+
+/*
+ * Prototypes
+ */
+/*
+ * NAME
+ * match_create_callback
+ *
+ * DESCRIPTION
+ * Creates a new `cu_match_t' object which will use the regular expression
+ * `regex' to match lines, see the `match_apply' method below. If the line
+ * matches, the callback passed in `callback' will be called along with the
+ * pointer `user_pointer'.
+ * The string that's passed to the callback depends on the regular expression:
+ * If the regular expression includes a sub-match, i. e. something like
+ * "value=([0-9][0-9]*)"
+ * then only the submatch (the part in the parenthesis) will be passed to the
+ * callback. If there is no submatch, then the entire string is passed to the
+ * callback.
+ */
+cu_match_t *match_create_callback (const char *regex,
+ int (*callback) (const char *str,
+ char * const *matches, size_t matches_num, void *user_data),
+ void *user_data);
+
+/*
+ * NAME
+ * match_create_simple
+ *
+ * DESCRIPTION
+ * Creates a new `cu_match_t' with a default callback. The user data for that
+ * default callback will be a `cu_match_value_t' structure, with
+ * `ds_type' copied to the structure. The default callback will handle the
+ * string as containing a number (see strtoll(3) and strtod(3)) and store that
+ * number in the `value' member. How that is done depends on `ds_type':
+ *
+ * UTILS_MATCH_DS_TYPE_GAUGE
+ * The function will search for a floating point number in the string and
+ * store it in value.gauge.
+ * UTILS_MATCH_DS_TYPE_COUNTER_SET
+ * The function will search for an integer in the string and store it in
+ * value.counter.
+ * UTILS_MATCH_DS_TYPE_COUNTER_ADD
+ * The function will search for an integer in the string and add it to the
+ * value in value.counter.
+ * UTILS_MATCH_DS_TYPE_COUNTER_INC
+ * The function will not search for anything in the string and increase
+ * value.counter by one.
+ */
+cu_match_t *match_create_simple (const char *regex, int ds_type);
+
+/*
+ * NAME
+ * match_destroy
+ *
+ * DESCRIPTION
+ * Destroys the object and frees all internal resources.
+ */
+void match_destroy (cu_match_t *obj);
+
+/*
+ * NAME
+ * match_apply
+ *
+ * DESCRIPTION
+ * Tries to match the string `str' with the regular expression of `obj'. If
+ * the string matches, calls the callback in `obj' with the (sub-)match.
+ *
+ * The user_data pointer passed to `match_create_callback' is NOT freed
+ * automatically. The `cu_match_value_t' structure allocated by
+ * `match_create_callback' is freed automatically.
+ */
+int match_apply (cu_match_t *obj, const char *str);
+
+/*
+ * NAME
+ * match_get_user_data
+ *
+ * DESCRIPTION
+ * Returns the pointer passed to `match_create_callback' or a pointer to the
+ * `cu_match_value_t' structure allocated by `match_create_callback'.
+ */
+void *match_get_user_data (cu_match_t *obj);
+
+#endif /* UTILS_MATCH_H */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_tail.c b/src/utils_tail.c
--- /dev/null
+++ b/src/utils_tail.c
@@ -0,0 +1,244 @@
+/**
+ * collectd - src/utils_tail.c
+ * Copyright (C) 2007-2008 C-Ware, Inc.
+ * Copyright (C) 2008 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Luke Heberling <lukeh at c-ware.com>
+ * Florian Forster <octo at verplant.org>
+ *
+ * Description:
+ * Encapsulates useful code for plugins which must watch for appends to
+ * the end of a file.
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "utils_tail.h"
+
+struct cu_tail_s
+{
+ char *file;
+ FILE *fh;
+ struct stat stat;
+};
+
+static int cu_tail_reopen (cu_tail_t *obj)
+{
+ int seek_end = 0;
+ FILE *fh;
+ struct stat stat_buf;
+ int status;
+
+ memset (&stat_buf, 0, sizeof (stat_buf));
+ status = stat (obj->file, &stat_buf);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("utils_tail: stat (%s) failed: %s", obj->file,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ /* The file is already open.. */
+ if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino))
+ {
+ /* Seek to the beginning if file was truncated */
+ if (stat_buf.st_size < obj->stat.st_size)
+ {
+ INFO ("utils_tail: File `%s' was truncated.", obj->file);
+ status = fseek (obj->fh, 0, SEEK_SET);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ fclose (obj->fh);
+ obj->fh = NULL;
+ return (-1);
+ }
+ }
+ memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
+ return (1);
+ }
+
+ /* Seek to the end if we re-open the same file again or the file opened
+ * is the first at all or the first after an error */
+ if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
+ seek_end = 1;
+
+ fh = fopen (obj->file, "r");
+ if (fh == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("utils_tail: fopen (%s) failed: %s", obj->file,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ if (seek_end != 0)
+ {
+ status = fseek (fh, 0, SEEK_END);
+ if (status != 0)
+ {
+ char errbuf[1024];
+ ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ fclose (fh);
+ return (-1);
+ }
+ }
+
+ if (obj->fh != NULL)
+ fclose (obj->fh);
+ obj->fh = fh;
+ memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
+
+ return (0);
+} /* int cu_tail_reopen */
+
+cu_tail_t *cu_tail_create (const char *file)
+{
+ cu_tail_t *obj;
+
+ obj = (cu_tail_t *) malloc (sizeof (cu_tail_t));
+ if (obj == NULL)
+ return (NULL);
+ memset (obj, '\0', sizeof (cu_tail_t));
+
+ obj->file = strdup (file);
+ if (obj->file == NULL)
+ {
+ free (obj);
+ return (NULL);
+ }
+
+ obj->fh = NULL;
+
+ return (obj);
+} /* cu_tail_t *cu_tail_create */
+
+int cu_tail_destroy (cu_tail_t *obj)
+{
+ if (obj->fh != NULL)
+ fclose (obj->fh);
+ free (obj->file);
+ free (obj);
+
+ return (0);
+} /* int cu_tail_destroy */
+
+int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
+{
+ int status;
+
+ if (buflen < 1)
+ {
+ ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
+ buflen);
+ return (-1);
+ }
+
+ if (obj->fh == NULL)
+ {
+ status = cu_tail_reopen (obj);
+ if (status < 0)
+ return (status);
+ }
+ assert (obj->fh != NULL);
+
+ /* Try to read from the filehandle. If that succeeds, everything appears to
+ * be fine and we can return. */
+ if (fgets (buf, buflen, obj->fh) != NULL)
+ {
+ buf[buflen - 1] = 0;
+ return (0);
+ }
+
+ /* Check if we encountered an error */
+ if (ferror (obj->fh) != 0)
+ {
+ /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
+ fclose (obj->fh);
+ obj->fh = NULL;
+ }
+ /* else: eof -> check if the file was moved away and reopen the new file if
+ * so.. */
+
+ status = cu_tail_reopen (obj);
+ /* error -> return with error */
+ if (status < 0)
+ return (status);
+ /* file end reached and file not reopened -> nothing more to read */
+ else if (status > 0)
+ {
+ buf[0] = 0;
+ return (0);
+ }
+
+ /* If we get here: file was re-opened and there may be more to read.. Let's
+ * try again. */
+ if (fgets (buf, buflen, obj->fh) != NULL)
+ {
+ buf[buflen - 1] = 0;
+ return (0);
+ }
+
+ if (ferror (obj->fh) != 0)
+ {
+ char errbuf[1024];
+ WARNING ("utils_tail: fgets (%s) returned an error: %s", obj->file,
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ fclose (obj->fh);
+ obj->fh = NULL;
+ return (-1);
+ }
+
+ /* EOf, well, apparently the new file is empty.. */
+ buf[0] = 0;
+ return (0);
+} /* int cu_tail_readline */
+
+int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+ void *data)
+{
+ int status;
+
+ while (42)
+ {
+ status = cu_tail_readline (obj, buf, buflen);
+ if (status != 0)
+ {
+ ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
+ "failed.");
+ break;
+ }
+
+ /* check for EOF */
+ if (buf[0] == 0)
+ break;
+
+ status = callback (data, buf, buflen);
+ if (status != 0)
+ {
+ ERROR ("utils_tail: cu_tail_read: callback returned "
+ "status %i.", status);
+ break;
+ }
+ }
+
+ return status;
+} /* int cu_tail_read */
diff --git a/src/utils_tail.h b/src/utils_tail.h
--- /dev/null
+++ b/src/utils_tail.h
@@ -0,0 +1,83 @@
+/**
+ * collectd - src/utils_tail.h
+ * Copyright (C) 2007-2008 C-Ware, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Luke Heberling <lukeh at c-ware.com>
+ *
+ * DESCRIPTION
+ * Facilitates reading information that is appended to a file, taking into
+ * account that the file may be rotated and a new file created under the
+ * same name.
+ **/
+
+#ifndef UTILS_TAIL_H
+#define UTILS_TAIL_H 1
+
+struct cu_tail_s;
+typedef struct cu_tail_s cu_tail_t;
+
+typedef int tailfunc_t(void *data, char *buf, int buflen);
+
+/*
+ * NAME
+ * cu_tail_create
+ *
+ * DESCRIPTION
+ * Allocates a new tail object..
+ *
+ * PARAMETERS
+ * `file' The name of the file to be tailed.
+ */
+cu_tail_t *cu_tail_create (const char *file);
+
+/*
+ * cu_tail_destroy
+ *
+ * Takes a tail object returned by `cu_tail_create' and destroys it, freeing
+ * all internal memory.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_destroy (cu_tail_t *obj);
+
+/*
+ * cu_tail_readline
+ *
+ * Reads from the file until `buflen' characters are read, a newline
+ * character is read, or an eof condition is encountered. `buf' is
+ * always null-terminated on successful return and isn't touched when non-zero
+ * is returned.
+ *
+ * You can check if the EOF condition is reached by looking at the buffer: If
+ * the length of the string stored in the buffer is zero, EOF occurred.
+ * Otherwise at least the newline character will be in the buffer.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen);
+
+/*
+ * cu_tail_readline
+ *
+ * Reads from the file until eof condition or an error is encountered.
+ *
+ * Returns 0 when successful and non-zero otherwise.
+ */
+int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+ void *data);
+
+#endif /* UTILS_TAIL_H */
diff --git a/src/utils_tail_match.c b/src/utils_tail_match.c
--- /dev/null
+++ b/src/utils_tail_match.c
@@ -0,0 +1,262 @@
+/*
+ * collectd - src/utils_tail_match.c
+ * Copyright (C) 2007-2008 C-Ware, Inc.
+ * Copyright (C) 2008 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:
+ * Luke Heberling <lukeh at c-ware.com>
+ * Florian Forster <octo at verplant.org>
+ *
+ * Description:
+ * Encapsulates useful code to plugins which must parse a log file.
+ */
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+#include "utils_match.h"
+#include "utils_tail.h"
+#include "utils_tail_match.h"
+
+struct cu_tail_match_simple_s
+{
+ char plugin[DATA_MAX_NAME_LEN];
+ char plugin_instance[DATA_MAX_NAME_LEN];
+ char type[DATA_MAX_NAME_LEN];
+ char type_instance[DATA_MAX_NAME_LEN];
+};
+typedef struct cu_tail_match_simple_s cu_tail_match_simple_t;
+
+struct cu_tail_match_match_s
+{
+ cu_match_t *match;
+ void *user_data;
+ int (*submit) (cu_match_t *match, void *user_data);
+ void (*free) (void *user_data);
+};
+typedef struct cu_tail_match_match_s cu_tail_match_match_t;
+
+struct cu_tail_match_s
+{
+ int flags;
+ cu_tail_t *tail;
+
+ cu_tail_match_match_t *matches;
+ size_t matches_num;
+};
+
+/*
+ * Private functions
+ */
+static int simple_submit_match (cu_match_t *match, void *user_data)
+{
+ cu_tail_match_simple_t *data = (cu_tail_match_simple_t *) user_data;
+ cu_match_value_t *match_value;
+ value_list_t vl = VALUE_LIST_INIT;
+ value_t values[1];
+
+ match_value = (cu_match_value_t *) match_get_user_data (match);
+ if (match_value == NULL)
+ return (-1);
+
+ if ((match_value->ds_type & UTILS_MATCH_DS_TYPE_GAUGE)
+ && (match_value->values_num == 0))
+ values[0].gauge = NAN;
+ else
+ values[0] = match_value->value;
+
+ vl.values = values;
+ vl.values_len = 1;
+ vl.time = time (NULL);
+ sstrncpy (vl.host, hostname_g, sizeof (vl.host));
+ sstrncpy (vl.plugin, data->plugin, sizeof (vl.plugin));
+ sstrncpy (vl.plugin_instance, data->plugin_instance,
+ sizeof (vl.plugin_instance));
+ sstrncpy (vl.type_instance, data->type_instance,
+ sizeof (vl.type_instance));
+
+ plugin_dispatch_values (data->type, &vl);
+
+ if (match_value->ds_type & UTILS_MATCH_DS_TYPE_GAUGE)
+ {
+ match_value->value.gauge = NAN;
+ match_value->values_num = 0;
+ }
+
+ return (0);
+} /* int simple_submit_match */
+
+static int tail_callback (void *data, char *buf, int buflen)
+{
+ cu_tail_match_t *obj = (cu_tail_match_t *) data;
+ int i;
+
+ for (i = 0; i < obj->matches_num; i++)
+ match_apply (obj->matches[i].match, buf);
+
+ return (0);
+} /* int tail_callback */
+
+/*
+ * Public functions
+ */
+cu_tail_match_t *tail_match_create (const char *filename)
+{
+ cu_tail_match_t *obj;
+
+ obj = (cu_tail_match_t *) malloc (sizeof (cu_tail_match_t));
+ if (obj == NULL)
+ return (NULL);
+ memset (obj, '\0', sizeof (cu_tail_match_t));
+
+ obj->tail = cu_tail_create (filename);
+ if (obj->tail == NULL)
+ {
+ sfree (obj);
+ return (NULL);
+ }
+
+ return (obj);
+} /* cu_tail_match_t *tail_match_create */
+
+void tail_match_destroy (cu_tail_match_t *obj)
+{
+ int i;
+
+ if (obj == NULL)
+ return;
+
+ if (obj->tail != NULL)
+ {
+ cu_tail_destroy (obj->tail);
+ obj->tail = NULL;
+ }
+
+ for (i = 0; i < obj->matches_num; i++)
+ {
+ cu_tail_match_match_t *match = obj->matches + i;
+ if (match->match != NULL)
+ {
+ match_destroy (match->match);
+ match->match = NULL;
+ }
+
+ if ((match->user_data != NULL)
+ && (match->free != NULL))
+ (*match->free) (match->user_data);
+ match->user_data = NULL;
+ }
+
+ sfree (obj->matches);
+ sfree (obj);
+} /* void tail_match_destroy */
+
+int tail_match_add_match (cu_tail_match_t *obj, cu_match_t *match,
+ int (*submit_match) (cu_match_t *match, void *user_data),
+ void *user_data,
+ void (*free_user_data) (void *user_data))
+{
+ cu_tail_match_match_t *temp;
+
+ temp = (cu_tail_match_match_t *) realloc (obj->matches,
+ sizeof (cu_tail_match_match_t) * (obj->matches_num + 1));
+ if (temp == NULL)
+ return (-1);
+
+ obj->matches = temp;
+ obj->matches_num++;
+
+ temp = obj->matches + (obj->matches_num - 1);
+
+ temp->match = match;
+ temp->user_data = user_data;
+ temp->submit = submit_match;
+ temp->free = free_user_data;
+
+ return (0);
+} /* int tail_match_add_match */
+
+int tail_match_add_match_simple (cu_tail_match_t *obj,
+ const char *regex, int ds_type,
+ const char *plugin, const char *plugin_instance,
+ const char *type, const char *type_instance)
+{
+ cu_match_t *match;
+ cu_tail_match_simple_t *user_data;
+ int status;
+
+ match = match_create_simple (regex, ds_type);
+ if (match == NULL)
+ return (-1);
+
+ user_data = (cu_tail_match_simple_t *) malloc (sizeof (cu_tail_match_simple_t));
+ if (user_data == NULL)
+ {
+ match_destroy (match);
+ return (-1);
+ }
+ memset (user_data, '\0', sizeof (cu_tail_match_simple_t));
+
+ sstrncpy (user_data->plugin, plugin, sizeof (user_data->plugin));
+ if (plugin_instance != NULL)
+ sstrncpy (user_data->plugin_instance, plugin_instance,
+ sizeof (user_data->plugin_instance));
+
+ sstrncpy (user_data->type, type, sizeof (user_data->type));
+ if (type_instance != NULL)
+ sstrncpy (user_data->type_instance, type_instance,
+ sizeof (user_data->type_instance));
+
+ status = tail_match_add_match (obj, match, simple_submit_match,
+ user_data, free);
+
+ if (status != 0)
+ {
+ match_destroy (match);
+ sfree (user_data);
+ }
+
+ return (status);
+} /* int tail_match_add_match_simple */
+
+int tail_match_read (cu_tail_match_t *obj)
+{
+ char buffer[4096];
+ int status;
+ int i;
+
+ status = cu_tail_read (obj->tail, buffer, sizeof (buffer), tail_callback,
+ (void *) obj);
+ if (status != 0)
+ {
+ ERROR ("tail_match: cu_tail_read failed.");
+ return (status);
+ }
+
+ for (i = 0; i < obj->matches_num; i++)
+ {
+ cu_tail_match_match_t *lt_match = obj->matches + i;
+
+ if (lt_match->submit == NULL)
+ continue;
+
+ (*lt_match->submit) (lt_match->match, lt_match->user_data);
+ }
+
+ return (0);
+} /* int tail_match_read */
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_tail_match.h b/src/utils_tail_match.h
--- /dev/null
+++ b/src/utils_tail_match.h
@@ -0,0 +1,126 @@
+/*
+ * collectd - src/utils_tail_match.h
+ * Copyright (C) 2007-2008 C-Ware, Inc.
+ * Copyright (C) 2008 Florian Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Luke Heberling <lukeh at c-ware.com>
+ * Florian Forster <octo at verplant.org>
+ *
+ * Description:
+ * `tail_match' uses `utils_tail' and `utils_match' to tail a file and try to
+ * match it using several regular expressions. Matches are then passed to
+ * user-provided callback functions or default handlers. This should keep all
+ * of the parsing logic out of the actual plugin, which only operate with
+ * regular expressions.
+ */
+
+#include "utils_match.h"
+
+struct cu_tail_match_s;
+typedef struct cu_tail_match_s cu_tail_match_t;
+
+/*
+ * NAME
+ * tail_match_create
+ *
+ * DESCRIPTION
+ * Allocates, initializes and returns a new `cu_tail_match_t' object.
+ *
+ * PARAMETERS
+ * `filename' The name to read data from.
+ *
+ * RETURN VALUE
+ * Returns NULL upon failure, non-NULL otherwise.
+ */
+cu_tail_match_t *tail_match_create (const char *filename);
+
+/*
+ * NAME
+ * tail_match_destroy
+ *
+ * DESCRIPTION
+ * Releases resources used by the `cu_tail_match_t' object.
+ *
+ * PARAMETERS
+ * The object to destroy.
+ */
+void tail_match_destroy (cu_tail_match_t *obj);
+
+/*
+ * NAME
+ * tail_match_add_match
+ *
+ * DESCRIPTION
+ * Adds a match, in form of a `cu_match_t' object, to the object.
+ * After data has been read from the logfile (using utils_tail) the callback
+ * function `submit_match' is called with the match object and the user
+ * supplied data.
+ * Please note that his function is called regardless whether this match
+ * matched any lines recently or not.
+ * When `tail_match_destroy' is called the `user_data' pointer is freed using
+ * the `free_user_data' callback - if it is not NULL.
+ * When using this interface the `tail_match' module doesn't dispatch any values
+ * itself - all that has to happen in either the match-callbacks or the
+ * submit_match callback.
+ *
+ * RETURN VALUE
+ * Zero upon success, non-zero otherwise.
+ */
+int tail_match_add_match (cu_tail_match_t *obj, cu_match_t *match,
+ int (*submit_match) (cu_match_t *match, void *user_data),
+ void *user_data,
+ void (*free_user_data) (void *user_data));
+
+/*
+ * NAME
+ * tail_match_add_match_simple
+ *
+ * DESCRIPTION
+ * A simplified version of `tail_match_add_match'. The regular expressen `regex'
+ * must match a number, which is then dispatched according to `ds_type'. See
+ * the `match_create_simple' function in utils_match.h for a description how
+ * this flag effects calculation of a new value.
+ * The values gathered are dispatched by the tail_match module in this case. The
+ * passed `plugin', `plugin_instance', `type', and `type_instance' are
+ * directly used when submitting these values.
+ *
+ * RETURN VALUE
+ * Zero upon success, non-zero otherwise.
+ */
+int tail_match_add_match_simple (cu_tail_match_t *obj,
+ const char *regex, int ds_type,
+ const char *plugin, const char *plugin_instance,
+ const char *type, const char *type_instance);
+
+/*
+ * NAME
+ * tail_match_read
+ *
+ * DESCRIPTION
+ * This function should be called periodically by plugins. It reads new lines
+ * from the logfile using `utils_tail' and tries to match them using all
+ * added `utils_match' objects.
+ * After all lines have been read and processed, the submit_match callback is
+ * called or, in case of tail_match_add_match_simple, the data is dispatched to
+ * the daemon directly.
+ *
+ * RETURN VALUE
+ * Zero on success, nonzero on failure.
+*/
+int tail_match_read (cu_tail_match_t *obj);
+
+/* vim: set sw=2 sts=2 ts=8 : */
diff --git a/src/utils_threshold.c b/src/utils_threshold.c
index 778b40bbeb9b5f8b8c3ea7904b2b8d97f2e58b8a..6c131ba62644c3c9f99228e53b812a5fb6ade513 100644 (file)
--- a/src/utils_threshold.c
+++ b/src/utils_threshold.c
char plugin_instance[DATA_MAX_NAME_LEN];
char type[DATA_MAX_NAME_LEN];
char type_instance[DATA_MAX_NAME_LEN];
+ char data_source[DATA_MAX_NAME_LEN];
gauge_t warning_min;
gauge_t warning_max;
gauge_t failure_min;
gauge_t failure_max;
int flags;
+ struct threshold_s *next;
} threshold_t;
/* }}} */
* The following functions add, delete, search, etc. configured thresholds to
* the underlying AVL trees.
* {{{ */
+static threshold_t *threshold_get (const char *hostname,
+ const char *plugin, const char *plugin_instance,
+ const char *type, const char *type_instance)
+{
+ char name[6 * DATA_MAX_NAME_LEN];
+ threshold_t *th = NULL;
+
+ format_name (name, sizeof (name),
+ (hostname == NULL) ? "" : hostname,
+ (plugin == NULL) ? "" : plugin, plugin_instance,
+ (type == NULL) ? "" : type, type_instance);
+ name[sizeof (name) - 1] = '\0';
+
+ if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
+ return (th);
+ else
+ return (NULL);
+} /* threshold_t *threshold_get */
+
static int ut_threshold_add (const threshold_t *th)
{
char name[6 * DATA_MAX_NAME_LEN];
char *name_copy;
threshold_t *th_copy;
+ threshold_t *th_ptr;
int status = 0;
if (format_name (name, sizeof (name), th->host,
return (-1);
}
memcpy (th_copy, th, sizeof (threshold_t));
+ th_ptr = NULL;
DEBUG ("ut_threshold_add: Adding entry `%s'", name);
pthread_mutex_lock (&threshold_lock);
- status = c_avl_insert (threshold_tree, name_copy, th_copy);
+
+ th_ptr = threshold_get (th->host, th->plugin, th->plugin_instance,
+ th->type, th->type_instance);
+
+ while ((th_ptr != NULL) && (th_ptr->next != NULL))
+ th_ptr = th_ptr->next;
+
+ if (th_ptr == NULL) /* no such threshold yet */
+ {
+ status = c_avl_insert (threshold_tree, name_copy, th_copy);
+ }
+ else /* th_ptr points to the last threshold in the list */
+ {
+ th_ptr->next = th_copy;
+ /* name_copy isn't needed */
+ sfree (name_copy);
+ }
+
pthread_mutex_unlock (&threshold_lock);
if (status != 0)
* The following approximately two hundred functions are used to handle the
* configuration and fill the threshold list.
* {{{ */
+static int ut_config_type_datasource (threshold_t *th, oconfig_item_t *ci)
+{
+ if ((ci->values_num != 1)
+ || (ci->values[0].type != OCONFIG_TYPE_STRING))
+ {
+ WARNING ("threshold values: The `DataSource' option needs exactly one "
+ "string argument.");
+ return (-1);
+ }
+
+ sstrncpy (th->data_source, ci->values[0].value.string,
+ sizeof (th->data_source));
+
+ return (0);
+} /* int ut_config_type_datasource */
+
static int ut_config_type_instance (threshold_t *th, oconfig_item_t *ci)
{
if ((ci->values_num != 1)
if (strcasecmp ("Instance", option->key) == 0)
status = ut_config_type_instance (&th, option);
+ if (strcasecmp ("DataSource", option->key) == 0)
+ status = ut_config_type_datasource (&th, option);
else if ((strcasecmp ("WarningMax", option->key) == 0)
|| (strcasecmp ("FailureMax", option->key) == 0))
status = ut_config_type_max (&th, option);
*/
/* }}} */
-static threshold_t *threshold_get (const char *hostname,
- const char *plugin, const char *plugin_instance,
- const char *type, const char *type_instance)
-{
- char name[6 * DATA_MAX_NAME_LEN];
- threshold_t *th = NULL;
-
- format_name (name, sizeof (name),
- (hostname == NULL) ? "" : hostname,
- (plugin == NULL) ? "" : plugin, plugin_instance,
- (type == NULL) ? "" : type, type_instance);
- name[sizeof (name) - 1] = '\0';
-
- if (c_avl_get (threshold_tree, name, (void *) &th) == 0)
- return (th);
- else
- return (NULL);
-} /* threshold_t *threshold_get */
-
static threshold_t *threshold_search (const data_set_t *ds,
const value_list_t *vl)
{
return (NULL);
} /* threshold_t *threshold_search */
-int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
-{
+/*
+ * int ut_report_state
+ *
+ * Checks if the `state' differs from the old state and creates a notification
+ * if appropriate.
+ * Does not fail.
+ */
+static int ut_report_state (const data_set_t *ds,
+ const value_list_t *vl,
+ const threshold_t *th,
+ const gauge_t *values,
+ int ds_index,
+ int state)
+{ /* {{{ */
+ int state_old;
notification_t n;
- threshold_t *th;
- gauge_t *values;
- int i;
-
- int state_orig;
- int state_new = STATE_OKAY;
- int ds_index = 0;
char *buf;
size_t bufsize;
- int status;
-
- if (threshold_tree == NULL)
- return (0);
- /* Is this lock really necessary? So far, thresholds are only inserted at
- * startup. -octo */
- pthread_mutex_lock (&threshold_lock);
- th = threshold_search (ds, vl);
- pthread_mutex_unlock (&threshold_lock);
- if (th == NULL)
- return (0);
-
- DEBUG ("ut_check_threshold: Found matching threshold");
-
- values = uc_get_rate (ds, vl);
- if (values == NULL)
- return (0);
+ int status;
- state_orig = uc_get_state (ds, vl);
+ state_old = uc_get_state (ds, vl);
- for (i = 0; i < ds->ds_num; i++)
+ /* If the state didn't change, only report if `persistent' is specified and
+ * the state is not `okay'. */
+ if (state == state_old)
{
- int is_inverted = 0;
- int is_warning = 0;
- int is_failure = 0;
-
- if ((th->flags & UT_FLAG_INVERT) != 0)
- {
- is_inverted = 1;
- is_warning--;
- is_failure--;
- }
- if ((!isnan (th->failure_min) && (th->failure_min > values[i]))
- || (!isnan (th->failure_max) && (th->failure_max < values[i])))
- is_failure++;
- if ((!isnan (th->warning_min) && (th->warning_min > values[i]))
- || (!isnan (th->warning_max) && (th->warning_max < values[i])))
- is_warning++;
-
- if ((is_failure != 0) && (state_new != STATE_ERROR))
- {
- state_new = STATE_ERROR;
- ds_index = i;
- }
- else if ((is_warning != 0)
- && (state_new != STATE_ERROR)
- && (state_new != STATE_WARNING))
- {
- state_new = STATE_WARNING;
- ds_index = i;
- }
+ if ((th->flags & UT_FLAG_PERSIST) == 0)
+ return (0);
+ else if (state == STATE_OKAY)
+ return (0);
}
- if (state_new != state_orig)
- uc_set_state (ds, vl, state_new);
-
- /* Return here if we're not going to send a notification */
- if ((state_new == state_orig)
- && ((state_new == STATE_OKAY)
- || ((th->flags & UT_FLAG_PERSIST) == 0)))
- {
- sfree (values);
- return (0);
- }
+ if (state != state_old)
+ uc_set_state (ds, vl, state);
NOTIFICATION_INIT_VL (&n, vl, ds);
- {
- /* Copy the associative members */
- if (state_new == STATE_OKAY)
- n.severity = NOTIF_OKAY;
- else if (state_new == STATE_WARNING)
- n.severity = NOTIF_WARNING;
- else
- n.severity = NOTIF_FAILURE;
- n.time = vl->time;
+ buf = n.message;
+ bufsize = sizeof (n.message);
+
+ if (state == STATE_OKAY)
+ n.severity = NOTIF_OKAY;
+ else if (state == STATE_WARNING)
+ n.severity = NOTIF_WARNING;
+ else
+ n.severity = NOTIF_FAILURE;
+
+ n.time = vl->time;
- buf = n.message;
- bufsize = sizeof (n.message);
+ status = snprintf (buf, bufsize, "Host %s, plugin %s",
+ vl->host, vl->plugin);
+ buf += status;
+ bufsize -= status;
- status = snprintf (buf, bufsize, "Host %s, plugin %s",
- vl->host, vl->plugin);
+ if (vl->plugin_instance[0] != '\0')
+ {
+ status = snprintf (buf, bufsize, " (instance %s)",
+ vl->plugin_instance);
buf += status;
bufsize -= status;
+ }
- if (vl->plugin_instance[0] != '\0')
- {
- status = snprintf (buf, bufsize, " (instance %s)",
- vl->plugin_instance);
- buf += status;
- bufsize -= status;
- }
+ status = snprintf (buf, bufsize, " type %s", ds->type);
+ buf += status;
+ bufsize -= status;
- status = snprintf (buf, bufsize, " type %s", ds->type);
+ if (vl->type_instance[0] != '\0')
+ {
+ status = snprintf (buf, bufsize, " (instance %s)",
+ vl->type_instance);
buf += status;
bufsize -= status;
-
- if (vl->type_instance[0] != '\0')
- {
- status = snprintf (buf, bufsize, " (instance %s)",
- vl->type_instance);
- buf += status;
- bufsize -= status;
- }
}
- /* Send a okay notification */
- if (state_new == STATE_OKAY)
+ /* Send an okay notification */
+ if (state == STATE_OKAY)
{
status = snprintf (buf, bufsize, ": All data sources are within range again.");
buf += status;
double min;
double max;
- min = (state_new == STATE_ERROR) ? th->failure_min : th->warning_min;
- max = (state_new == STATE_ERROR) ? th->failure_max : th->warning_max;
+ min = (state == STATE_ERROR) ? th->failure_min : th->warning_min;
+ max = (state == STATE_ERROR) ? th->failure_max : th->warning_max;
if (th->flags & UT_FLAG_INVERT)
{
status = snprintf (buf, bufsize, ": Data source \"%s\" is currently "
"%f. That is within the %s region of %f and %f.",
ds->ds[ds_index].name, values[ds_index],
- (state_new == STATE_ERROR) ? "failure" : "warning",
+ (state == STATE_ERROR) ? "failure" : "warning",
min, min);
}
else
"%f. That is %s the %s threshold of %f.",
ds->ds[ds_index].name, values[ds_index],
isnan (min) ? "below" : "above",
- (state_new == STATE_ERROR) ? "failure" : "warning",
+ (state == STATE_ERROR) ? "failure" : "warning",
isnan (min) ? max : min);
}
}
"%f. That is %s the %s threshold of %f.",
ds->ds[ds_index].name, values[ds_index],
(values[ds_index] < min) ? "below" : "above",
- (state_new == STATE_ERROR) ? "failure" : "warning",
+ (state == STATE_ERROR) ? "failure" : "warning",
(values[ds_index] < min) ? min : max);
}
buf += status;
plugin_dispatch_notification (&n);
+ return (0);
+} /* }}} int ut_report_state */
+
+/*
+ * int ut_check_one_data_source
+ *
+ * Checks one data source against the given threshold configuration. If the
+ * `DataSource' option is set in the threshold, and the name does NOT match,
+ * `okay' is returned. If the threshold does match, its failure and warning
+ * min and max values are checked and `failure' or `warning' is returned if
+ * appropriate.
+ * Does not fail.
+ */
+static int ut_check_one_data_source (const data_set_t *ds,
+ const value_list_t *vl,
+ const threshold_t *th,
+ const gauge_t *values,
+ int ds_index)
+{ /* {{{ */
+ const char *ds_name;
+ int is_warning = 0;
+ int is_failure = 0;
+
+ /* check if this threshold applies to this data source */
+ ds_name = ds->ds[ds_index].name;
+ if ((th->data_source[0] != 0)
+ && (strcmp (ds_name, th->data_source) != 0))
+ return (STATE_OKAY);
+
+ if ((th->flags & UT_FLAG_INVERT) != 0)
+ {
+ is_warning--;
+ is_failure--;
+ }
+
+ if ((!isnan (th->failure_min) && (th->failure_min > values[ds_index]))
+ || (!isnan (th->failure_max) && (th->failure_max < values[ds_index])))
+ is_failure++;
+ if (is_failure != 0)
+ return (STATE_ERROR);
+
+ if ((!isnan (th->warning_min) && (th->warning_min > values[ds_index]))
+ || (!isnan (th->warning_max) && (th->warning_max < values[ds_index])))
+ is_warning++;
+ if (is_warning != 0)
+ return (STATE_WARNING);
+
+ return (STATE_OKAY);
+} /* }}} int ut_check_one_data_source */
+
+/*
+ * int ut_check_one_threshold
+ *
+ * Checks all data sources of a value list against the given threshold, using
+ * the ut_check_one_data_source function above. Returns the worst status,
+ * which is `okay' if nothing has failed.
+ * Returns less than zero if the data set doesn't have any data sources.
+ */
+static int ut_check_one_threshold (const data_set_t *ds,
+ const value_list_t *vl,
+ const threshold_t *th,
+ const gauge_t *values,
+ int *ret_ds_index)
+{ /* {{{ */
+ int ret = -1;
+ int ds_index = -1;
+ int i;
+
+ for (i = 0; i < ds->ds_num; i++)
+ {
+ int status;
+
+ status = ut_check_one_data_source (ds, vl, th, values, i);
+ if (ret < status)
+ {
+ ret = status;
+ ds_index = i;
+ }
+ } /* for (ds->ds_num) */
+
+ if (ret_ds_index != NULL)
+ *ret_ds_index = ds_index;
+
+ return (ret);
+} /* }}} int ut_check_one_threshold */
+
+/*
+ * int ut_check_threshold (PUBLIC)
+ *
+ * Gets a list of matching thresholds and searches for the worst status by one
+ * of the thresholds. Then reports that status using the ut_report_state
+ * function above.
+ * Returns zero on success and if no threshold has been configured. Returns
+ * less than zero on failure.
+ */
+int ut_check_threshold (const data_set_t *ds, const value_list_t *vl)
+{ /* {{{ */
+ threshold_t *th;
+ gauge_t *values;
+ int status;
+
+ int worst_state = -1;
+ threshold_t *worst_th = NULL;
+ int worst_ds_index = -1;
+
+ if (threshold_tree == NULL)
+ return (0);
+
+ /* Is this lock really necessary? So far, thresholds are only inserted at
+ * startup. -octo */
+ pthread_mutex_lock (&threshold_lock);
+ th = threshold_search (ds, vl);
+ pthread_mutex_unlock (&threshold_lock);
+ if (th == NULL)
+ return (0);
+
+ DEBUG ("ut_check_threshold: Found matching threshold(s)");
+
+ values = uc_get_rate (ds, vl);
+ if (values == NULL)
+ return (0);
+
+ while (th != NULL)
+ {
+ int ds_index = -1;
+
+ status = ut_check_one_threshold (ds, vl, th, values, &ds_index);
+ if (status < 0)
+ {
+ ERROR ("ut_check_threshold: ut_check_one_threshold failed.");
+ sfree (values);
+ return (-1);
+ }
+
+ if (worst_state < status)
+ {
+ worst_state = status;
+ worst_th = th;
+ worst_ds_index = ds_index;
+ }
+
+ th = th->next;
+ } /* while (th) */
+
+ status = ut_report_state (ds, vl, worst_th, values,
+ worst_ds_index, worst_state);
+ if (status != 0)
+ {
+ ERROR ("ut_check_threshold: ut_report_state failed.");
+ sfree (values);
+ return (-1);
+ }
+
sfree (values);
return (0);
-} /* int ut_check_threshold */
+} /* }}} int ut_check_threshold */
int ut_check_interesting (const char *name)
{
diff --git a/src/vmem.c b/src/vmem.c
--- /dev/null
+++ b/src/vmem.c
@@ -0,0 +1,284 @@
+/**
+ * collectd - src/vmem.c
+ * Copyright (C) 2008 Florian octo Forster
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; only version 2 of the License is applicable.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Authors:
+ * Florian octo Forster <octo at verplant.org>
+ **/
+
+#include "collectd.h"
+#include "common.h"
+#include "plugin.h"
+
+#if KERNEL_LINUX
+static const char *config_keys[] =
+{
+ "Verbose"
+};
+static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
+
+static int verbose_output = 0;
+/* #endif KERNEL_LINUX */
+
+#else
+# error "No applicable input method."
+#endif /* HAVE_LIBSTATGRAB */
+
+static void submit (const char *plugin_instance, const char *type,
+ const char *type_instance, value_t *values, int values_len)
+{
+ value_list_t vl = VALUE_LIST_INIT;
+
+ vl.values = values;
+ vl.values_len = values_len;
+
+ vl.time = time (NULL);
+ strcpy (vl.host, hostname_g);
+ strcpy (vl.plugin, "vmem");
+ if (plugin_instance != NULL)
+ sstrncpy (vl.plugin_instance, plugin_instance, sizeof (vl.plugin_instance));
+ if (type_instance != NULL)
+ sstrncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
+
+ plugin_dispatch_values (type, &vl);
+} /* void vmem_submit */
+
+static void submit_two (const char *plugin_instance, const char *type,
+ const char *type_instance, counter_t c0, counter_t c1)
+{
+ value_t values[2];
+
+ values[0].counter = c0;
+ values[1].counter = c1;
+
+ submit (plugin_instance, type, type_instance, values, 2);
+} /* void submit_one */
+
+static void submit_one (const char *plugin_instance, const char *type,
+ const char *type_instance, value_t value)
+{
+ submit (plugin_instance, type, type_instance, &value, 1);
+} /* void submit_one */
+
+static int vmem_config (const char *key, const char *value)
+{
+ if (strcasecmp ("Verbose", key) == 0)
+ {
+ if ((strcasecmp ("true", value) == 0)
+ || (strcasecmp ("yes", value) == 0)
+ || (strcasecmp ("on", value) == 0))
+ verbose_output = 1;
+ else
+ verbose_output = 0;
+ }
+ else
+ {
+ return (-1);
+ }
+
+ return (0);
+} /* int vmem_config */
+
+static int vmem_read (void)
+{
+#if KERNEL_LINUX
+ counter_t pgpgin = 0;
+ counter_t pgpgout = 0;
+ int pgpgvalid = 0;
+
+ counter_t pswpin = 0;
+ counter_t pswpout = 0;
+ int pswpvalid = 0;
+
+ counter_t pgfault = 0;
+ counter_t pgmajfault = 0;
+ int pgfaultvalid = 0;
+
+ FILE *fh;
+ char buffer[1024];
+
+ fh = fopen ("/proc/vmstat", "r");
+ if (fh == NULL)
+ {
+ char errbuf[1024];
+ ERROR ("vmem plugin: fopen (/proc/vmstat) failed: %s",
+ sstrerror (errno, errbuf, sizeof (errbuf)));
+ return (-1);
+ }
+
+ while (fgets (buffer, sizeof (buffer), fh) != NULL)
+ {
+ char *fields[4];
+ int fields_num;
+ char *key;
+ char *endptr;
+ counter_t counter;
+ gauge_t gauge;
+
+ fields_num = strsplit (buffer, fields, STATIC_ARRAY_SIZE (fields));
+ if (fields_num != 2)
+ continue;
+
+ key = fields[0];
+
+ endptr = NULL;
+ counter = strtoll (fields[1], &endptr, 10);
+ if (fields[1] == endptr)
+ continue;
+
+ endptr = NULL;
+ gauge = strtod (fields[1], &endptr);
+ if (fields[1] == endptr)
+ continue;
+
+ /*
+ * Number of pages
+ *
+ * The total number of {inst} pages, e. g dirty pages.
+ */
+ if (strncmp ("nr_", key, strlen ("nr_")) == 0)
+ {
+ char *inst = key + strlen ("nr_");
+ value_t value = { .gauge = gauge };
+ submit_one (NULL, "vmpage_number", inst, value);
+ }
+
+ /*
+ * Page in and page outs. For memory and swap.
+ */
+ else if (strcmp ("pgpgin", key) == 0)
+ {
+ pgpgin = counter;
+ pgpgvalid |= 0x01;
+ }
+ else if (strcmp ("pgpgout", key) == 0)
+ {
+ pgpgout = counter;
+ pgpgvalid |= 0x02;
+ }
+ else if (strcmp ("pswpin", key) == 0)
+ {
+ pswpin = counter;
+ pswpvalid |= 0x01;
+ }
+ else if (strcmp ("pswpout", key) == 0)
+ {
+ pswpout = counter;
+ pswpvalid |= 0x02;
+ }
+
+ /*
+ * Pagefaults
+ */
+ else if (strcmp ("pgfault", key) == 0)
+ {
+ pgfault = counter;
+ pgfaultvalid |= 0x01;
+ }
+ else if (strcmp ("pgmajfault", key) == 0)
+ {
+ pgmajfault = counter;
+ pgfaultvalid |= 0x02;
+ }
+
+ /*
+ * Skip the other statistics if verbose output is disabled.
+ */
+ else if (verbose_output == 0)
+ continue;
+
+ /*
+ * Number of page allocations, refills, steals and scans. This is collected
+ * ``per zone'', i. e. for DMA, DMA32, normal and possibly highmem.
+ */
+ else if (strncmp ("pgalloc_", key, strlen ("pgalloc_")) == 0)
+ {
+ char *inst = key + strlen ("pgalloc_");
+ value_t value = { .counter = counter };
+ submit_one (inst, "vmpage_action", "alloc", value);
+ }
+ else if (strncmp ("pgrefill_", key, strlen ("pgrefill_")) == 0)
+ {
+ char *inst = key + strlen ("pgrefill_");
+ value_t value = { .counter = counter };
+ submit_one (inst, "vmpage_action", "refill", value);
+ }
+ else if (strncmp ("pgsteal_", key, strlen ("pgsteal_")) == 0)
+ {
+ char *inst = key + strlen ("pgsteal_");
+ value_t value = { .counter = counter };
+ submit_one (inst, "vmpage_action", "steal", value);
+ }
+ else if (strncmp ("pgscan_kswapd_", key, strlen ("pgscan_kswapd_")) == 0)
+ {
+ char *inst = key + strlen ("pgscan_kswapd_");
+ value_t value = { .counter = counter };
+ submit_one (inst, "vmpage_action", "scan_kswapd", value);
+ }
+ else if (strncmp ("pgscan_direct_", key, strlen ("pgscan_direct_")) == 0)
+ {
+ char *inst = key + strlen ("pgscan_direct_");
+ value_t value = { .counter = counter };
+ submit_one (inst, "vmpage_action", "scan_direct", value);
+ }
+
+ /*
+ * Page action
+ *
+ * number of pages moved to the active or inactive lists and freed, i. e.
+ * removed from either list.
+ */
+ else if (strcmp ("pgfree", key) == 0)
+ {
+ value_t value = { .counter = counter };
+ submit_one (NULL, "vmpage_action", "free", value);
+ }
+ else if (strcmp ("pgactivate", key) == 0)
+ {
+ value_t value = { .counter = counter };
+ submit_one (NULL, "vmpage_action", "activate", value);
+ }
+ else if (strcmp ("pgdeactivate", key) == 0)
+ {
+ value_t value = { .counter = counter };
+ submit_one (NULL, "vmpage_action", "deactivate", value);
+ }
+ } /* while (fgets) */
+
+ fclose (fh);
+ fh = NULL;
+
+ if (pgfaultvalid == 0x03)
+ submit_two (NULL, "vmpage_faults", NULL, pgfault, pgmajfault);
+
+ if (pgpgvalid == 0x03)
+ submit_two (NULL, "vmpage_io", "memory", pgpgin, pgpgout);
+
+ if (pswpvalid == 0x03)
+ submit_two (NULL, "vmpage_io", "swap", pswpin, pswpout);
+#endif /* KERNEL_LINUX */
+
+ return (0);
+} /* int vmem_read */
+
+void module_register (void)
+{
+ plugin_register_config ("vmem", vmem_config,
+ config_keys, config_keys_num);
+ plugin_register_read ("vmem", vmem_read);
+} /* void module_register */
+
+/* vim: set sw=2 sts=2 ts=8 : */