From: Florian Forster Date: Tue, 6 May 2008 11:45:14 +0000 (+0200) Subject: Merge branch 'collectd-4.3' into collectd-4.4 X-Git-Tag: collectd-4.4.0~9 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=af46a5f31a0e8d4279d63d8ca9232dbd433dfb25;hp=53bd9256920f01cc5b835639c7e7971979ed3350;p=collectd.git Merge branch 'collectd-4.3' into collectd-4.4 --- diff --git a/AUTHORS b/AUTHORS index bd5daa42..2004dff7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -40,6 +40,9 @@ serial plugin by: tape plugin by: Scott Garrett +teamspeak2 plugin by: + Stefan Hacker + users plugin by: Sebastian Harl diff --git a/README b/README index 8c0be1da..904e7c73 100644 --- a/README +++ b/README @@ -26,6 +26,9 @@ Features 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. @@ -156,6 +159,10 @@ Features - 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. @@ -165,6 +172,10 @@ Features - 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 . @@ -305,7 +316,7 @@ Prerequisites 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. @@ -363,7 +374,7 @@ Prerequisites 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 diff --git a/TODO b/TODO index 2a5b7ca4..08fcec1e 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,6 @@ +For version 4.4: +* PowerDNS plugin + For version 4.3: * unixsock plugin: Remove the custom cache if possible. diff --git a/bindings/perl/Collectd.pm b/bindings/perl/Collectd.pm index 4ba9751b..0da6c23b 100644 --- a/bindings/perl/Collectd.pm +++ b/bindings/perl/Collectd.pm @@ -42,6 +42,9 @@ our %EXPORT_TAGS = ( plugin_register plugin_unregister plugin_dispatch_values + plugin_flush + plugin_flush_one + plugin_flush_all plugin_dispatch_notification plugin_log ) ], @@ -52,6 +55,7 @@ our %EXPORT_TAGS = ( TYPE_SHUTDOWN TYPE_LOG TYPE_NOTIF + TYPE_FLUSH TYPE_DATASET ) ], 'ds_types' => [ qw( @@ -101,7 +105,8 @@ my %types = ( TYPE_WRITE, "write", TYPE_SHUTDOWN, "shutdown", TYPE_LOG, "log", - TYPE_NOTIF, "notify" + TYPE_NOTIF, "notify", + TYPE_FLUSH, "flush" ); foreach my $type (keys %types) { @@ -290,6 +295,34 @@ sub plugin_unregister { } } +sub plugin_flush { + my %args = @_; + + my $timeout = -1; + + DEBUG ("Collectd::plugin_flush:" + . (defined ($args{'timeout'}) ? " timeout = $args{'timeout'}" : "") + . (defined ($args{'plugins'}) ? " plugins = $args{'plugins'}" : "")); + + if (defined ($args{'timeout'}) && ($args{'timeout'} > 0)) { + $timeout = $args{'timeout'}; + } + + if (! defined $args{'plugins'}) { + plugin_flush_all ($timeout); + } + else { + if ("ARRAY" eq ref ($args{'plugins'})) { + foreach my $plugin (@{$args{'plugins'}}) { + plugin_flush_one ($timeout, $plugin); + } + } + else { + plugin_flush_one ($timeout, $args{'plugins'}); + } + } +} + 1; # vim: set sw=4 ts=4 tw=78 noexpandtab : diff --git a/bindings/perl/Collectd/Unixsock.pm b/bindings/perl/Collectd/Unixsock.pm index 8749c1ab..f21ebfe9 100644 --- a/bindings/perl/Collectd/Unixsock.pm +++ b/bindings/perl/Collectd/Unixsock.pm @@ -400,6 +400,63 @@ sub putnotif return; } # putnotif +=item I<$obj>-EB (B =E I<$timeout>, B =E [...]); + +Flush cached data. + +Valid options are: + +=over 4 + +=item B + +If this option is specified, only data older than I<$timeout> seconds is +flushed. + +=item B + +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>-Edestroy (); Closes the socket before the object is destroyed. This function is also diff --git a/configure.in b/configure.in index 4da57fcc..d817becc 100644 --- a/configure.in +++ b/configure.in @@ -1956,6 +1956,85 @@ then 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 +#include +#include +#include +]) + + 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="" @@ -1963,7 +2042,6 @@ with_libxml2_ldflags="" with_libvirt="no (pkg-config isn't available)" with_libvirt_cflags="" with_libvirt_ldflags="" -PKG_PROG_PKG_CONFIG if test "x$PKG_CONFIG" != "x" then pkg-config --exists 'libxml-2.0' 2>/dev/null @@ -2178,6 +2256,7 @@ AC_COLLECTD([debug], [enable], [feature], [debugging]) 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" @@ -2185,6 +2264,7 @@ plugin_df="no" plugin_disk="no" plugin_entropy="no" plugin_interface="no" +plugin_ipmi="no" plugin_ipvs="no" plugin_irq="no" plugin_libvirt="no" @@ -2199,6 +2279,7 @@ plugin_swap="no" plugin_tape="no" plugin_tcpconns="no" plugin_users="no" +plugin_vmem="no" plugin_vserver="no" plugin_wireless="no" @@ -2219,6 +2300,7 @@ then plugin_serial="yes" plugin_swap="yes" plugin_tcpconns="yes" + plugin_vmem="yes" plugin_vserver="yes" plugin_wireless="yes" @@ -2249,10 +2331,23 @@ fi # libstatgrab if test "x$with_libstatgrab" = "xyes" then + plugin_cpu="yes" + plugin_disk="yes" plugin_interface="yes" plugin_load="yes" plugin_memory="yes" plugin_swap="yes" + plugin_users="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" @@ -2333,6 +2428,7 @@ collectd plugins:]) 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]) @@ -2346,6 +2442,7 @@ AC_PLUGIN([exec], [yes], [Execution of external programs]) 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]) @@ -2364,6 +2461,7 @@ AC_PLUGIN([ntpd], [yes], [NTPd 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]) @@ -2371,11 +2469,14 @@ AC_PLUGIN([serial], [$plugin_serial], [serial port traffic]) AC_PLUGIN([snmp], [$with_libnetsnmp], [SNMP querying plugin]) AC_PLUGIN([swap], [$plugin_swap], [Swap usage statistics]) AC_PLUGIN([syslog], [$have_syslog], [Syslog logging plugin]) +AC_PLUGIN([tail], [yes], [Parsing of logfiles]) AC_PLUGIN([tape], [$plugin_tape], [Tape drive statistics]) AC_PLUGIN([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]) @@ -2453,6 +2554,7 @@ Configuration: libnetlink . . . . $with_libnetlink libnetsnmp . . . . $with_libnetsnmp liboconfig . . . . $with_liboconfig + libopenipmi . . . . $with_libopenipmipthread liboping . . . . . $with_liboping libpcap . . . . . . $with_libpcap libperl . . . . . . $with_libperl @@ -2476,6 +2578,7 @@ Configuration: apache . . . . . . $enable_apache apcups . . . . . . $enable_apcups apple_sensors . . . $enable_apple_sensors + ascent . . . . . . $enable_ascent battery . . . . . . $enable_battery cpu . . . . . . . . $enable_cpu cpufreq . . . . . . $enable_cpufreq @@ -2489,6 +2592,7 @@ Configuration: hddtemp . . . . . . $enable_hddtemp interface . . . . . $enable_interface iptables . . . . . $enable_iptables + ipmi . . . . . . . $enable_ipmi ipvs . . . . . . . $enable_ipvs irq . . . . . . . . $enable_irq libvirt . . . . . . $enable_libvirt @@ -2507,6 +2611,7 @@ Configuration: nut . . . . . . . . $enable_nut perl . . . . . . . $enable_perl ping . . . . . . . $enable_ping + powerdns . . . . . $enable_powerdns processes . . . . . $enable_processes rrdtool . . . . . . $enable_rrdtool sensors . . . . . . $enable_sensors @@ -2514,11 +2619,14 @@ Configuration: 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 65c634e0..6da2856d 100755 --- a/contrib/cussh.pl +++ b/contrib/cussh.pl @@ -1,7 +1,7 @@ #!/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 @@ -56,12 +56,19 @@ use Collectd::Unixsock(); 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"; @@ -70,20 +77,21 @@ use Collectd::Unixsock(); print "cussh> "; my $line = ; - 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"; @@ -105,7 +113,7 @@ sub getid { 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)); @@ -119,17 +127,31 @@ sub getid { 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 I -=item B I I - -These commands follow the exact same syntax as described in -L. - =cut sub putval { @@ -138,23 +160,36 @@ 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 I I + +=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"; @@ -162,6 +197,75 @@ sub getval { return 1; } +=item B [B=I<$timeout>] [B=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 + +=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. + =head1 SEE ALSO L, L diff --git a/contrib/migrate-3-4.px b/contrib/migrate-3-4.px index ed418273..ed19a7b6 100755 --- a/contrib/migrate-3-4.px +++ b/contrib/migrate-3-4.px @@ -170,22 +170,23 @@ for (@Files) 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 02b85fcd..1f7caef7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -30,7 +30,10 @@ collectd_SOURCES = collectd.c collectd.h \ 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 @@ -118,6 +121,16 @@ collectd_LDADD += "-dlopen" apple_sensors.la 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 @@ -132,13 +145,19 @@ endif if BUILD_PLUGIN_CPU pkglib_LTLIBRARIES += cpu.la cpu_la_SOURCES = cpu.c +cpu_la_CFLAGS = cpu_la_LDFLAGS = -module -avoid-version +cpu_la_LIBADD = if BUILD_WITH_LIBKSTAT cpu_la_LDFLAGS += -lkstat endif if BUILD_WITH_LIBDEVINFO cpu_la_LDFLAGS += -ldevinfo endif +if BUILD_WITH_LIBSTATGRAB +cpu_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS) +cpu_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS) +endif collectd_LDADD += "-dlopen" cpu.la collectd_DEPENDENCIES += cpu.la endif @@ -170,7 +189,9 @@ endif if BUILD_PLUGIN_DISK pkglib_LTLIBRARIES += disk.la disk_la_SOURCES = disk.c +disk_la_CFLAGS = disk_la_LDFLAGS = -module -avoid-version +disk_la_LIBADD = if BUILD_WITH_LIBKSTAT disk_la_LDFLAGS += -lkstat endif @@ -180,6 +201,10 @@ endif if BUILD_WITH_LIBIOKIT disk_la_LDFLAGS += -lIOKit endif +if BUILD_WITH_LIBSTATGRAB +disk_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS) +disk_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS) +endif collectd_LDADD += "-dlopen" disk.la collectd_DEPENDENCIES += disk.la endif @@ -264,6 +289,16 @@ collectd_LDADD += "-dlopen" iptables.la 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 @@ -470,6 +505,14 @@ collectd_LDADD += "-dlopen" ping.la 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 @@ -554,6 +597,14 @@ collectd_LDADD += "-dlopen" syslog.la 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 @@ -570,9 +621,22 @@ collectd_LDADD += "-dlopen" tcpconns.la 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 @@ -581,7 +645,13 @@ endif if BUILD_PLUGIN_USERS pkglib_LTLIBRARIES += users.la users_la_SOURCES = users.c +users_la_CFLAGS = users_la_LDFLAGS = -module -avoid-version +users_la_LIBADD = +if BUILD_WITH_LIBSTATGRAB +users_la_CFLAGS += $(BUILD_WITH_LIBSTATGRAB_CFLAGS) +users_la_LIBADD += $(BUILD_WITH_LIBSTATGRAB_LDFLAGS) +endif collectd_LDADD += "-dlopen" users.la collectd_DEPENDENCIES += users.la endif @@ -596,6 +666,14 @@ collectd_LDADD += "-dlopen" uuid.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 2a7e0b80..3cda5650 100644 --- a/src/apache.c +++ b/src/apache.c @@ -29,10 +29,12 @@ #include -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; @@ -46,6 +48,8 @@ static const char *config_keys[] = "URL", "User", "Password", + "VerifyPeer", + "VerifyHost", "CACert" }; static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); @@ -102,6 +106,10 @@ static int config (const char *key, const char *value) 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 @@ -154,6 +162,24 @@ static int init (void) 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 new file mode 100644 index 00000000..94691d6b --- /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 + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "configfile.h" + +#include +#include + +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 : */ diff --git a/src/collectd-perl.pod b/src/collectd-perl.pod index 4a01d146..8cdf08bb 100644 --- a/src/collectd-perl.pod +++ b/src/collectd-perl.pod @@ -67,7 +67,7 @@ registered by the plugins. Any plugin basically consists of the implementation of these callback functions and initializing code which registers the functions with collectd. See the section "EXAMPLES" below for a really basic example. The following types of B are known to collectd -(all of these are optional): +(all of them are optional): =over 4 @@ -92,6 +92,12 @@ amount of time until it returns B again. This type of function is used to write the dispatched values. It is called once for each call to B. +=item flush functions + +This type of function is used to flush internal caches of plugins. It is +usually triggered by the user only. Any plugin which caches data before +writing it to disk should provide this kind of callback function. + =item log functions This type of function is used to pass messages of plugins or the daemon itself @@ -203,6 +209,8 @@ I can be one of: =item TYPE_WRITE +=item TYPE_FLUSH + =item TYPE_LOG =item TYPE_NOTIF @@ -245,13 +253,18 @@ arguments: =item TYPE_SHUTDOWN -No arguments are passed +No arguments are passed. =item TYPE_WRITE The arguments passed are I, I, and I. I is a string. For the layout of I and I see above. +=item TYPE_FLUSH + +The only argument passed is I which indicates that only data older +than I seconds is to be flushed. + =item TYPE_LOG The arguments are I and I. The log level is small for @@ -279,6 +292,22 @@ is found (and the number of values matches the number of data-sources) then the type, data-set and value-list is passed to all write-callbacks that are registered with the daemon. +=item B ([B => I,] [B => I<...>]) + +Flush one or more plugins. I is passed on to the registered +flush-callbacks. If omitted, C<-1> is used. If the I argument has +been specified, only named plugins will be flushed. The argument's value may +either be a string or a reference to an array of strings. + +=item B (I, I) + +This is identical to using "plugin_flush (timeout =E I, plugins +=E I". + +=item B (I) + +This is identical to using "plugin_flush (timeout =E I)". + =item B (I) Submits a I to the daemon which will then pass it to all @@ -332,6 +361,12 @@ available (B<:all> will export all of them): =item B () +=item B () + +=item B () + +=item B () + =item B () =item B () @@ -348,6 +383,8 @@ available (B<:all> will export all of them): =item B +=item B + =item B =item B @@ -504,6 +541,18 @@ instead. =back +=head1 KNOWN BUGS + +=over 4 + +=item + +Currently, it is not possible to flush a single Perl plugin only. You can +either flush all Perl plugins or none at all and you have to use C as +plugin name when doing so. + +=back + =head1 SEE ALSO L, diff --git a/src/collectd-unixsock.pod b/src/collectd-unixsock.pod index 3ef24386..971cb36d 100644 --- a/src/collectd-unixsock.pod +++ b/src/collectd-unixsock.pod @@ -29,6 +29,18 @@ Upon start the C opens a UNIX-socket and waits for 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 I + +If I is greater than or equal to zero the message indicates success, +if I is less than zero the message indicates failure. I is a +human-readable string that further describes the return value. + +On success, I 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 @@ -36,37 +48,34 @@ The following commands are implemented: =item B I If the value identified by I (see below) is found the complete -value-list is returned. The response is a space separated list of -name-value-pairs: - -I IB<=>I[ IB<=>I[ ...]] - -If I is less then zero, an error occurred. Otherwise it contains the -number of values that follow. Each value is of the form IB<=>I. +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 IB<=>I. Counter-values are converted to a rate, e.Eg. bytes per second. Undefined values are returned as B. Example: -> | GETVAL myhost/cpu-0/cpu-user - <- | 1 value=1.260000e+00 + <- | 1 Value found + <- | value=1.260000e+00 =item B 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 -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 I [I] I @@ -174,6 +183,20 @@ Example: -> | PUTNOTIF type=temperature severity=warning time=1201094702 message=The roof is on fire! <- | 0 Success +=item B [BI] [BI [...]] + +Flushes all cached data older than I seconds. If no timeout has been +specified, it defaults to -1 which causes all data to be flushed. B +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 @@ -191,20 +214,6 @@ some examples: 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 I - -If I is zero the message indicates success, if I is non-zero the -message indicates failure. I is a human-readable string that describes -the return value further. - -Commands that return values may use I to return the number of values that -follow, such as the B command. These commands usually return a negative -value on failure and never return zero. - =head1 ABSTRACTION LAYER B ships the Perl-Module L which diff --git a/src/collectd.c b/src/collectd.c index 4e521f9f..f556fc5f 100644 --- a/src/collectd.c +++ b/src/collectd.c @@ -27,6 +27,8 @@ #include #include +#include + #include "plugin.h" #include "configfile.h" @@ -45,16 +47,37 @@ kstat_ctl_t *kc; 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; @@ -231,11 +254,13 @@ static void exit_usage (void) " General:\n" " -C Configuration file.\n" " Default: "CONFIGFILE"\n" + " -t Test config and exit.\n" " -P 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" @@ -369,8 +394,9 @@ static int pidfile_remove (void) 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; @@ -515,13 +541,32 @@ int main (int argc, char **argv) /* * 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 e125bb63..036916e7 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -16,6 +16,7 @@ FQDNLookup true @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 @@ -29,6 +30,7 @@ FQDNLookup true @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 @@ -56,9 +58,11 @@ FQDNLookup true @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 @@ -75,6 +79,13 @@ FQDNLookup true # Port "3551" # +# +# URL "http://localhost/ascent/status/" +# User "www-user" +# Password "secret" +# CACert "/etc/ssl/ca.crt" +# + # # DataDir "@prefix@/var/lib/@PACKAGE_NAME@/csv" # StoreRates false @@ -277,6 +288,12 @@ FQDNLookup true # RemotePort "25" # +# +# Host "127.0.0.1" +# Port "51234" +# Server "8767" +# + # # SocketFile "@prefix@/var/run/@PACKAGE_NAME@-unixsock" # SocketGroup "collectd" @@ -287,3 +304,7 @@ FQDNLookup true # UUIDFile "/etc/uuid" # +# +# Verbose false +# + diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index aa4421dc..afa05711 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -173,6 +173,19 @@ Optional user name needed for authentication. Optional password needed for authentication. +=item B B + +Enable or disable peer SSL certificate verification. See +L for details. Enabled by default. + +=item B B + +Enable or disable peer host name verification. If enabled, the plugin checks +if the C or a C field of the SSL +certificate matches the host name provided by the B 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 I File that holds one or more SSL certificates. If you want to use HTTPS you will @@ -197,6 +210,36 @@ TCP-Port to connect to. Defaults to B<3551>. =back +=head2 Plugin C + +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 and parses it using C. + +The configuration options are the same as for the C plugin above: + +=over 4 + +=item B I + +Sets the URL of the XML status output. + +=item B I + +Optional user name needed for authentication. + +=item B I + +Optional password needed for authentication. + +=item B I + +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 +and are checked by default depends on the distribution you use. + +=back + =head2 Plugin C This plugin doesn't have any options. It reads @@ -247,6 +290,40 @@ at all, B partitions are selected. =back +=head2 Plugin C + +The C 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 I + +Select the disk I. Whether it is collected or ignored depends on the +B 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 B|B + +Sets whether selected disks, i.Ee. the ones matches by any of the B +statements, are ignored or if all other disks are ignored. The behavior +(hopefully) is intuitive: If no B option is configured, all disks are +collected. If at least one B option is given and no B or +set to B, B matching disks will be collected. If B +is set to B, all disks are collected B the ones matched. + +=back + =head2 Plugin C =over 4 @@ -759,6 +836,19 @@ Optional user name needed for authentication. Optional password needed for authentication. +=item B B + +Enable or disable peer SSL certificate verification. See +L for details. Enabled by default. + +=item B B + +Enable or disable peer host name verification. If enabled, the plugin checks +if the C or a C field of the SSL +certificate matches the host name provided by the B 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 I File that holds one or more SSL certificates. If you want to use HTTPS you will @@ -819,6 +909,139 @@ Sets the Time-To-Live of generated ICMP packets. =back +=head2 Plugin C + +The C plugin queries statistics from an authoritative PowerDNS +nameserver and/or a PowerDNS recursor. Since both offer a wide variety of +values, many of which are probably meaningless to most users, but may be useful +for some. So you may chose which values to collect, but if you don't, some +reasonable defaults will be collected. + + + + Collect "latency" + Collect "udp-answers" "udp-queries" + Socket "/var/run/pdns.controlsocket" + + + Collect "questions" + Collect "cache-hits" "cache-misses" + Socket "/var/run/pdns_recursor.controlsocket" + + LocalSocket "/opt/collectd/var/run/collectd-powerdns" + + +=over 4 + +=item B and B block + +The B block defines one authoritative server to query, the B +does the same for an recursing server. The possible options in both blocks are +the same, though. The argument defines a name for the serverE/ recursor +and is required. + +=over 4 + +=item B I + +Using the B statement you can select which values to collect. Here, +you specify the name of the values as used by the PowerDNS servers, e.Eg. +C, C. + +The method of getting the values differs for B and B blocks: +When querying the server a C command is issued in any case, because +that's the only way of getting multiple values out of the server at once. +collectd then picks out the values you have selected. When querying the +recursor, a command is generated to query exactly these values. So if you +specify invalid fields when querying the recursor, a syntax error may be +returned by the daemon and collectd may not collect any values at all. + +If no B statement is given, the following B values will be +collected: + +=over 4 + +=item latency + +=item packetcache-hit + +=item packetcache-miss + +=item packetcache-size + +=item query-cache-hit + +=item query-cache-miss + +=item recursing-answers + +=item recursing-questions + +=item tcp-answers + +=item tcp-queries + +=item udp-answers + +=item udp-queries + +=back + +The following B values will be collected by default: + +=over 4 + +=item noerror-answers + +=item nxdomain-answers + +=item servfail-answers + +=item sys-msec + +=item user-msec + +=item qa-latency + +=item cache-entries + +=item cache-hits + +=item cache-misses + +=item questions + +=back + +Please note that up to that point collectd doesn't know what values are +available on the server and values that are added do not need a change of the +mechanism so far. However, the values must be mapped to collectd's naming +scheme, which is done using a lookup table that lists all known values. If +values are added in the future and collectd does not know about them, you will +get an error much like this: + + powerdns plugin: submit: Not found in lookup table: foobar = 42 + +In this case please file a bug report with the collectd team. + +=item B I + +Configures the path to the UNIX domain socket to be used when connecting to the +daemon. By default C will be used for an +authoritative server and C will be used +for the recursor. + +=back + +=item B I + +Querying the recursor is done using UDP. When using UDP over UNIX domain +sockets, the client socket needs a name in the file system, too. You can set +this local name to I using the B option. The default is +C/var/run/collectd-powerdns>. + +=back + =head2 Plugin C =over 4 @@ -963,6 +1186,143 @@ debugging support. =back +=head2 Plugin C + +The C plugins follows logfiles, just like L 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. + + + + Instance "exim" + + Regex "S=([1-9][0-9]*)" + DSType "CounterAdd" + Type "ipt_bytes" + Instance "total" + + + Regex "\\" + DSType "CounterInc" + Type "email_count" + Instance "local_user" + + + + +The config consists of one or more B blocks, each of which configures one +logfile to parse. Within each B block, there are one or more B +blocks, which configure a regular expression to search for. + +The B option in the B block may be used to set the plugin +instance. So in the above example the plugin name C would be used. +This plugin instance is for all B blocks that B it, until the +next B option. This way you can extract several plugin instances from +one logfile, handy when parsing syslog and the like. + +Each B block has the following options to describe how the match should +be performed: + +=over 4 + +=item B I + +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 or L, depending on the value of C, see +below. Because B regular expressions are used, you do not need to use +backslashes for subexpressions! If in doubt, please consult L. 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 I + +Sets how the values are cumulated. I is one of: + +=over 4 + +=item B + +Calculate the average. + +=item B + +Use the smallest number only. + +=item B + +Use the greatest number only. + +=item B + +Use the last number found. + +=item B + +The matched number is a counter. Simply sets the internal counter to this +value. + +=item B + +Add the matched value to the internal counter. + +=item B + +Increase the internal counter by one. This B 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 types interpret the submatch as a floating point +number, using L. The B and B interpret the +submatch as an integer using L. B does not use the +submatch at all and it may be omitted in this case. + +=item B I + +Sets the type used to dispatch this value. Detailed information about types and +their configuration can be found in L. + +=item B I + +This optional setting sets the type instance to use. + +=back + +=head2 Plugin C + +The C 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 I + +The hostname or ip which identifies the physical server. +Default: 127.0.0.1 + +=item B I + +The query port of the physical server. This needs to be a string. +Default: "51234" + +=item B I + +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.Ee. you B +use quotes around it! If no such statement is given only global information +will be collected. + =head2 Plugin C The C counts the number of currently established TCP @@ -973,6 +1333,8 @@ fine-tune the ports you are interested in: =over 4 +=back + =item B I|I If this option is set to I, statistics for all local ports for which a @@ -1063,6 +1425,24 @@ Take the UUID from the given file (default I). =back +=head2 Plugin C + +The C 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 B|B + +Enables verbose collection of information. This will start collecting page +"actions", e.Eg. page allocations, (de)activations, steals and so on. +Part of these statistics are collected on a "per zone" basis. + +=back + =head2 Plugin C This plugin doesn't have any options. B support is only available for @@ -1114,6 +1494,7 @@ information. Instance "eth0" FailureMax 10000000 + DataSource "rx" @@ -1164,6 +1545,19 @@ infinity. If a value is less than B a B notification will be created. If the value is less than B but greater than (or equal to) B a B notification will be created. +=item B I + +Some data sets have more than one "data source". Interesting examples are the +C data set, which has received (C) and sent (C) bytes and +the C data set, which holds C and C operations. The +system load data set, C, even has three data sources: C, +C, and C. + +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 option to have a threshold apply only to +one data source. + =item B B|B If set to B the range of acceptable values is inverted, i.Ee. diff --git a/src/collectd.pod b/src/collectd.pod index b55362a3..fa27e77a 100644 --- a/src/collectd.pod +++ b/src/collectd.pod @@ -99,6 +99,23 @@ the daemon, have manpages of their own to describe their functionality in more detail. In particular those are L, L, L, L, and L +=head1 SIGNALS + +B accepts the following signals: + +=over 4 + +=item B, B + +These signals cause B to shut down all plugins and terminate. + +=item B + +This signal causes B to signal all plugins to flush data from +internal caches. E.Eg. the C will write all pending data +to the RRD files. This is the same as using the C command of the +C. + =head1 SEE ALSO L, diff --git a/src/cpu.c b/src/cpu.c index c79c4b66..7692e2b5 100644 --- a/src/cpu.c +++ b/src/cpu.c @@ -68,7 +68,12 @@ # endif #endif /* HAVE_SYSCTLBYNAME */ -#if !PROCESSOR_CPU_LOAD_INFO && !KERNEL_LINUX && !HAVE_LIBKSTAT && !HAVE_SYSCTLBYNAME +#if HAVE_STATGRAB_H +# include +#endif + +#if !PROCESSOR_CPU_LOAD_INFO && !KERNEL_LINUX && !HAVE_LIBKSTAT \ + && !HAVE_SYSCTLBYNAME && !HAVE_LIBSTATGRAB # error "No applicable input method." #endif @@ -98,7 +103,11 @@ static int numcpu; #elif defined(HAVE_SYSCTLBYNAME) static int numcpu; -#endif /* HAVE_SYSCTLBYNAME */ +/* #endif HAVE_SYSCTLBYNAME */ + +#elif defined(HAVE_LIBSTATGRAB) +/* no variables needed */ +#endif /* HAVE_LIBSTATGRAB */ static int init (void) { @@ -152,7 +161,11 @@ static int init (void) if (numcpu != 1) NOTICE ("cpu: Only one processor supported when using `sysctlbyname' (found %i)", numcpu); -#endif +/* #endif HAVE_SYSCTLBYNAME */ + +#elif defined(HAVE_LIBSTATGRAB) + /* nothing to initialize */ +#endif /* HAVE_LIBSTATGRAB */ return (0); } /* int init */ @@ -370,7 +383,25 @@ static int cpu_read (void) submit (0, "nice", cpuinfo[CP_NICE]); submit (0, "system", cpuinfo[CP_SYS]); submit (0, "idle", cpuinfo[CP_IDLE]); -#endif +/* #endif HAVE_SYSCTLBYNAME */ + +#elif defined(HAVE_LIBSTATGRAB) + sg_cpu_stats *cs; + cs = sg_get_cpu_stats (); + + if (cs == NULL) + { + ERROR ("cpu plugin: sg_get_cpu_stats failed."); + return (-1); + } + + submit (0, "idle", (counter_t) cs->idle); + submit (0, "nice", (counter_t) cs->nice); + submit (0, "swap", (counter_t) cs->swap); + submit (0, "system", (counter_t) cs->kernel); + submit (0, "user", (counter_t) cs->user); + submit (0, "wait", (counter_t) cs->iowait); +#endif /* HAVE_LIBSTATGRAB */ return (0); } diff --git a/src/disk.c b/src/disk.c index 8feaa8df..6ed71b28 100644 --- a/src/disk.c +++ b/src/disk.c @@ -1,6 +1,6 @@ /** * 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 @@ -22,6 +22,7 @@ #include "collectd.h" #include "common.h" #include "plugin.h" +#include "utils_ignorelist.h" #if HAVE_MACH_MACH_TYPES_H # include @@ -58,6 +59,10 @@ # define UINT_MAX 4294967295U #endif +#if HAVE_STATGRAB_H +# include +#endif + #if HAVE_IOKIT_IOKITLIB_H static mach_port_t io_master_port = MACH_PORT_NULL; /* #endif HAVE_IOKIT_IOKITLIB_H */ @@ -97,10 +102,50 @@ static kstat_t *ksp[MAX_NUMDISK]; static int numdisk = 0; /* #endif HAVE_LIBKSTAT */ +#elif defined(HAVE_LIBSTATGRAB) +/* #endif HAVE_LIBKSTATGRAB */ + #else # 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 @@ -158,6 +203,10 @@ static void disk_submit (const char *plugin_instance, 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; @@ -617,13 +666,31 @@ static int disk_read (void) kio.KIO_ROPS, kio.KIO_WOPS); } } -#endif /* defined(HAVE_LIBKSTAT) */ +/* #endif defined(HAVE_LIBKSTAT) */ + +#elif defined(HAVE_LIBSTATGRAB) + sg_disk_io_stats *ds; + int disks, counter; + char name[DATA_MAX_NAME_LEN]; + + if ((ds = sg_get_disk_io_stats(&disks)) == NULL) + return (0); + + for (counter=0; counter < disks; counter++) { + strncpy(name, ds->disk_name, sizeof(name)); + name[sizeof(name)-1] = '\0'; /* strncpy doesn't terminate longer strings */ + disk_submit (name, "disk_octets", ds->read_bytes, ds->write_bytes); + ds++; + } +#endif /* defined(HAVE_LIBSTATGRAB) */ return (0); } /* int disk_read */ 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/email.c b/src/email.c index 869b7c36..87daed11 100644 --- a/src/email.c +++ b/src/email.c @@ -1,6 +1,6 @@ /** * collectd - src/email.c - * Copyright (C) 2006,2007 Sebastian Harl + * Copyright (C) 2006-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 @@ -58,17 +58,13 @@ # include #endif /* HAVE_GRP_H */ -#define MODULE_NAME "email" - -/* 256 bytes ought to be enough for anybody ;-) */ -#define BUFSIZE 256 - #define SOCK_PATH LOCALSTATEDIR"/run/"PACKAGE_NAME"-email" #define MAX_CONNS 5 #define MAX_CONNS_LIMIT 16384 -#define log_err(...) ERROR (MODULE_NAME": "__VA_ARGS__) -#define log_warn(...) WARNING (MODULE_NAME": "__VA_ARGS__) +#define log_debug(...) DEBUG ("email: "__VA_ARGS__) +#define log_err(...) ERROR ("email: "__VA_ARGS__) +#define log_warn(...) WARNING ("email: "__VA_ARGS__) /* * Private data structures @@ -90,19 +86,15 @@ typedef struct collector { pthread_t thread; /* socket descriptor of the current/last connection */ - int socket; + FILE *socket; } collector_t; /* linked list of pending connections */ typedef struct conn { /* socket to read data from */ - int socket; - - /* buffer to read data to */ - char *buffer; - int idx; /* current write position in buffer */ - int length; /* length of the current line, i.e. index of '\0' */ + FILE *socket; + /* linked list of connections */ struct conn *next; } conn_t; @@ -125,8 +117,8 @@ static const char *config_keys[] = static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); /* socket configuration */ -static char *sock_file = SOCK_PATH; -static char *sock_group = COLLECTD_GRP_NAME; +static char *sock_file = NULL; +static char *sock_group = NULL; static int sock_perms = S_IRWXU | S_IRWXG; static int max_conns = MAX_CONNS; @@ -154,17 +146,20 @@ static pthread_mutex_t available_mutex = PTHREAD_MUTEX_INITIALIZER; static int available_collectors; static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER; -static type_list_t count; +static type_list_t list_count; +static type_list_t list_count_copy; static pthread_mutex_t size_mutex = PTHREAD_MUTEX_INITIALIZER; -static type_list_t size; +static type_list_t list_size; +static type_list_t list_size_copy; static pthread_mutex_t score_mutex = PTHREAD_MUTEX_INITIALIZER; static double score; static int score_count; static pthread_mutex_t check_mutex = PTHREAD_MUTEX_INITIALIZER; -static type_list_t check; +static type_list_t list_check; +static type_list_t list_check_copy; /* * Private functions @@ -172,9 +167,13 @@ static type_list_t check; static int email_config (const char *key, const char *value) { if (0 == strcasecmp (key, "SocketFile")) { + if (NULL != sock_file) + free (sock_file); sock_file = sstrdup (value); } else if (0 == strcasecmp (key, "SocketGroup")) { + if (NULL != sock_group) + free (sock_group); sock_group = sstrdup (value); } else if (0 == strcasecmp (key, "SocketPerms")) { @@ -241,139 +240,10 @@ static void type_list_incr (type_list_t *list, char *name, int incr) return; } /* static void type_list_incr (type_list_t *, char *) */ -/* Read a single character from the socket. If an error occurs or end-of-file - * is reached return '\0'. */ -static char read_char (conn_t *src) -{ - char ret = '\0'; - - fd_set fdset; - - FD_ZERO (&fdset); - FD_SET (src->socket, &fdset); - - if (-1 == select (src->socket + 1, &fdset, NULL, NULL, NULL)) { - char errbuf[1024]; - log_err ("select() failed: %s", - sstrerror (errno, errbuf, sizeof (errbuf))); - return '\0'; - } - - assert (FD_ISSET (src->socket, &fdset)); - - do { - ssize_t len = 0; - - errno = 0; - if (0 > (len = read (src->socket, (void *)&ret, 1))) { - if (EINTR != errno) { - char errbuf[1024]; - log_err ("read() failed: %s", - sstrerror (errno, errbuf, sizeof (errbuf))); - return '\0'; - } - } - - if (0 == len) - return '\0'; - } while (EINTR == errno); - return ret; -} /* static char read_char (conn_t *) */ - -/* Read a single line (terminated by '\n') from the the socket. - * - * The return value is zero terminated and does not contain any newline - * characters. - * - * If an error occurs or end-of-file is reached return NULL. - * - * IMPORTANT NOTE: If there is no newline character found in BUFSIZE - * characters of the input stream, the line will will be ignored! By - * definition we should not get any longer input lines, thus this is - * acceptable in this case ;-) */ -static char *read_line (conn_t *src) -{ - int i = 0; - - assert ((BUFSIZE >= src->idx) && (src->idx >= 0)); - assert ((src->idx > src->length) || (src->length == 0)); - - if (src->length > 0) { /* remove old line */ - src->idx -= (src->length + 1); - memmove (src->buffer, src->buffer + src->length + 1, src->idx); - src->length = 0; - } - - for (i = 0; i < src->idx; ++i) { - if ('\n' == src->buffer[i]) - break; - } - - if (i == src->idx) { - fd_set fdset; - - ssize_t len = 0; - - FD_ZERO (&fdset); - FD_SET (src->socket, &fdset); - - if (-1 == select (src->socket + 1, &fdset, NULL, NULL, NULL)) { - char errbuf[1024]; - log_err ("select() failed: %s", - sstrerror (errno, errbuf, sizeof (errbuf))); - return NULL; - } - - assert (FD_ISSET (src->socket, &fdset)); - - do { - errno = 0; - if (0 > (len = read (src->socket, - (void *)(src->buffer + src->idx), - BUFSIZE - src->idx))) { - if (EINTR != errno) { - char errbuf[1024]; - log_err ("read() failed: %s", - sstrerror (errno, errbuf, sizeof (errbuf))); - return NULL; - } - } - - if (0 == len) - return NULL; - } while (EINTR == errno); - - src->idx += len; - - for (i = src->idx - len; i < src->idx; ++i) { - if ('\n' == src->buffer[i]) - break; - } - - if (i == src->idx) { - src->length = 0; - - if (BUFSIZE == src->idx) { /* no space left in buffer */ - while ('\n' != read_char (src)) - /* ignore complete line */; - - src->idx = 0; - } - return read_line (src); - } - } - - src->buffer[i] = '\0'; - src->length = i; - - return src->buffer; -} /* static char *read_line (conn_t *) */ - static void *collect (void *arg) { collector_t *this = (collector_t *)arg; - - char *buffer = (char *)smalloc (BUFSIZE); + pthread_t self = pthread_self (); while (1) { int loop = 1; @@ -393,44 +263,51 @@ static void *collect (void *arg) conns.tail = NULL; } - this->socket = connection->socket; - pthread_mutex_unlock (&conns_mutex); - connection->buffer = buffer; - connection->idx = 0; - connection->length = 0; + /* make the socket available to the global + * thread and connection management */ + this->socket = connection->socket; - { /* put the socket in non-blocking mode */ - int flags = 0; + log_debug ("[thread #%5lu] handling connection on fd #%i", + self, fileno (this->socket)); - errno = 0; - if (-1 == fcntl (connection->socket, F_GETFL, &flags)) { - char errbuf[1024]; - log_err ("fcntl() failed: %s", - sstrerror (errno, errbuf, sizeof (errbuf))); - loop = 0; - } + while (loop) { + /* 256 bytes ought to be enough for anybody ;-) */ + char line[256 + 1]; /* line + '\0' */ + int len = 0; errno = 0; - if (-1 == fcntl (connection->socket, F_SETFL, flags | O_NONBLOCK)) { - char errbuf[1024]; - log_err ("fcntl() failed: %s", - sstrerror (errno, errbuf, sizeof (errbuf))); + if (NULL == fgets (line, sizeof (line), this->socket)) { loop = 0; + + if (0 != errno) { + char errbuf[1024]; + log_err ("[thread #%5lu] reading from socket (fd #%i) " + "failed: %s", self, fileno (this->socket), + sstrerror (errno, errbuf, sizeof (errbuf))); + } + break; } - } - while (loop) { - char *line = read_line (connection); + len = strlen (line); + if (('\n' != line[len - 1]) && ('\r' != line[len - 1])) { + log_warn ("[thread #%5lu] line too long (> %i characters): " + "'%s' (truncated)", self, sizeof (line) - 1, line); - if (NULL == line) { - loop = 0; - break; + while (NULL != fgets (line, sizeof (line), this->socket)) + if (('\n' == line[len - 1]) || ('\r' == line[len - 1])) + break; + continue; } + line[len - 1] = '\0'; + + log_debug ("[thread #%5lu] line = '%s'", self, line); + if (':' != line[1]) { - log_err ("syntax error in line '%s'", line); + log_err ("[thread #%5lu] syntax error in line '%s'", + self, line); continue; } @@ -441,19 +318,20 @@ static void *collect (void *arg) int bytes = 0; if (NULL == tmp) { - log_err ("syntax error in line '%s'", line); + log_err ("[thread #%5lu] syntax error in line '%s'", + self, line); continue; } bytes = atoi (tmp); pthread_mutex_lock (&count_mutex); - type_list_incr (&count, type, 1); + type_list_incr (&list_count, type, 1); pthread_mutex_unlock (&count_mutex); if (bytes > 0) { pthread_mutex_lock (&size_mutex); - type_list_incr (&size, type, bytes); + type_list_incr (&list_size, type, bytes); pthread_mutex_unlock (&size_mutex); } } @@ -470,19 +348,22 @@ static void *collect (void *arg) do { pthread_mutex_lock (&check_mutex); - type_list_incr (&check, type, 1); + type_list_incr (&list_check, type, 1); pthread_mutex_unlock (&check_mutex); } while (NULL != (type = strtok_r (NULL, ",", &ptr))); } else { - log_err ("unknown type '%c'", line[0]); + log_err ("[thread #%5lu] unknown type '%c'", self, line[0]); } } /* while (loop) */ - close (connection->socket); + log_debug ("[thread #%5lu] shutting down connection on fd #%i", + pthread_self (), fileno (this->socket)); + + fclose (connection->socket); free (connection); - this->socket = -1; + this->socket = NULL; pthread_mutex_lock (&available_mutex); ++available_collectors; @@ -491,7 +372,6 @@ static void *collect (void *arg) pthread_cond_signal (&collector_available); } /* while (1) */ - free (buffer); pthread_exit ((void *)0); } /* static void *collect (void *) */ @@ -499,6 +379,9 @@ static void *open_connection (void *arg) { struct sockaddr_un addr; + char *path = (NULL == sock_file) ? SOCK_PATH : sock_file; + char *group = (NULL == sock_group) ? COLLECTD_GRP_NAME : sock_group; + /* create UNIX socket */ errno = 0; if (-1 == (connector_socket = socket (PF_UNIX, SOCK_STREAM, 0))) { @@ -511,7 +394,7 @@ static void *open_connection (void *arg) addr.sun_family = AF_UNIX; - strncpy (addr.sun_path, sock_file, (size_t)(UNIX_PATH_MAX - 1)); + strncpy (addr.sun_path, path, (size_t)(UNIX_PATH_MAX - 1)); addr.sun_path[UNIX_PATH_MAX - 1] = '\0'; unlink (addr.sun_path); @@ -521,7 +404,8 @@ static void *open_connection (void *arg) + strlen(addr.sun_path))) { char errbuf[1024]; disabled = 1; - connector_socket = -1; /* TODO: close? */ + close (connector_socket); + connector_socket = -1; log_err ("bind() failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); pthread_exit ((void *)1); @@ -531,13 +415,13 @@ static void *open_connection (void *arg) if (-1 == listen (connector_socket, 5)) { char errbuf[1024]; disabled = 1; - connector_socket = -1; /* TODO: close? */ + close (connector_socket); + connector_socket = -1; log_err ("listen() failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); pthread_exit ((void *)1); } - if ((uid_t) 0 == geteuid ()) { struct group sg; struct group *grp; @@ -545,36 +429,32 @@ static void *open_connection (void *arg) int status; grp = NULL; - status = getgrnam_r (sock_group, &sg, grbuf, sizeof (grbuf), &grp); + status = getgrnam_r (group, &sg, grbuf, sizeof (grbuf), &grp); if (status != 0) { char errbuf[1024]; - log_warn ("getgrnam_r (%s) failed: %s", sock_group, + log_warn ("getgrnam_r (%s) failed: %s", group, sstrerror (errno, errbuf, sizeof (errbuf))); } else if (grp == NULL) { - log_warn ("No such group: `%s'", sock_group); + log_warn ("No such group: `%s'", group); } else { - status = chown (sock_file, (uid_t) -1, grp->gr_gid); + status = chown (path, (uid_t) -1, grp->gr_gid); if (status != 0) { char errbuf[1024]; log_warn ("chown (%s, -1, %i) failed: %s", - sock_file, (int) grp->gr_gid, + path, (int) grp->gr_gid, sstrerror (errno, errbuf, sizeof (errbuf))); } } } - else /* geteuid != 0 */ - { - log_warn ("not running as root"); - } errno = 0; - if (0 != chmod (sock_file, sock_perms)) { + if (0 != chmod (path, sock_perms)) { char errbuf[1024]; log_warn ("chmod() failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); @@ -599,7 +479,7 @@ static void *open_connection (void *arg) for (i = 0; i < max_conns; ++i) { collectors[i] = (collector_t *)smalloc (sizeof (collector_t)); - collectors[i]->socket = -1; + collectors[i]->socket = NULL; if (0 != (err = pthread_create (&collectors[i]->thread, &ptattr, collect, collectors[i]))) { @@ -634,7 +514,8 @@ static void *open_connection (void *arg) if (EINTR != errno) { char errbuf[1024]; disabled = 1; - connector_socket = -1; /* TODO: close? */ + close (connector_socket); + connector_socket = -1; log_err ("accept() failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); pthread_exit ((void *)1); @@ -644,9 +525,14 @@ static void *open_connection (void *arg) connection = (conn_t *)smalloc (sizeof (conn_t)); - connection->socket = remote; + connection->socket = fdopen (remote, "r"); connection->next = NULL; + if (NULL == connection->socket) { + close (remote); + continue; + } + pthread_mutex_lock (&conns_mutex); if (NULL == conns.head) { @@ -683,6 +569,8 @@ static int email_init (void) static int email_shutdown (void) { + type_t *ptr = NULL; + int i = 0; if (connector != ((pthread_t) 0)) { @@ -698,6 +586,8 @@ static int email_shutdown (void) /* don't allow any more connections to be processed */ pthread_mutex_lock (&conns_mutex); + available_collectors = 0; + if (collectors != NULL) { for (i = 0; i < max_conns; ++i) { if (collectors[i] == NULL) @@ -708,18 +598,52 @@ static int email_shutdown (void) collectors[i]->thread = (pthread_t) 0; } - if (collectors[i]->socket >= 0) { - close (collectors[i]->socket); - collectors[i]->socket = -1; + if (collectors[i]->socket != NULL) { + fclose (collectors[i]->socket); + collectors[i]->socket = NULL; } + + sfree (collectors[i]); } + sfree (collectors); } /* if (collectors != NULL) */ pthread_mutex_unlock (&conns_mutex); - unlink (sock_file); - errno = 0; + for (ptr = list_count.head; NULL != ptr; ptr = ptr->next) { + free (ptr->name); + free (ptr); + } + + for (ptr = list_count_copy.head; NULL != ptr; ptr = ptr->next) { + free (ptr->name); + free (ptr); + } + + for (ptr = list_size.head; NULL != ptr; ptr = ptr->next) { + free (ptr->name); + free (ptr); + } + for (ptr = list_size_copy.head; NULL != ptr; ptr = ptr->next) { + free (ptr->name); + free (ptr); + } + + for (ptr = list_check.head; NULL != ptr; ptr = ptr->next) { + free (ptr->name); + free (ptr); + } + + for (ptr = list_check_copy.head; NULL != ptr; ptr = ptr->next) { + free (ptr->name); + free (ptr); + } + + unlink ((NULL == sock_file) ? SOCK_PATH : sock_file); + + sfree (sock_file); + sfree (sock_group); return (0); } /* static void email_shutdown (void) */ @@ -785,47 +709,28 @@ static int email_read (void) double score_old; int score_count_old; - static type_list_t *cnt; - static type_list_t *sz; - static type_list_t *chk; - if (disabled) return (-1); - if (NULL == cnt) { - cnt = (type_list_t *)smalloc (sizeof (type_list_t)); - cnt->head = NULL; - } - - if (NULL == sz) { - sz = (type_list_t *)smalloc (sizeof (type_list_t)); - sz->head = NULL; - } - - if (NULL == chk) { - chk = (type_list_t *)smalloc (sizeof (type_list_t)); - chk->head = NULL; - } - /* email count */ pthread_mutex_lock (&count_mutex); - copy_type_list (&count, cnt); + copy_type_list (&list_count, &list_count_copy); pthread_mutex_unlock (&count_mutex); - for (ptr = cnt->head; NULL != ptr; ptr = ptr->next) { + for (ptr = list_count_copy.head; NULL != ptr; ptr = ptr->next) { email_submit ("email_count", ptr->name, ptr->value); } /* email size */ pthread_mutex_lock (&size_mutex); - copy_type_list (&size, sz); + copy_type_list (&list_size, &list_size_copy); pthread_mutex_unlock (&size_mutex); - for (ptr = sz->head; NULL != ptr; ptr = ptr->next) { + for (ptr = list_size_copy.head; NULL != ptr; ptr = ptr->next) { email_submit ("email_size", ptr->name, ptr->value); } @@ -845,11 +750,11 @@ static int email_read (void) /* spam checks */ pthread_mutex_lock (&check_mutex); - copy_type_list (&check, chk); + copy_type_list (&list_check, &list_check_copy); pthread_mutex_unlock (&check_mutex); - for (ptr = chk->head; NULL != ptr; ptr = ptr->next) + for (ptr = list_check_copy.head; NULL != ptr; ptr = ptr->next) email_submit ("spam_check", ptr->name, ptr->value); return (0); diff --git a/src/exec.c b/src/exec.c index 5eae906b..07c35c9b 100644 --- a/src/exec.c +++ b/src/exec.c @@ -379,14 +379,17 @@ static void exec_child (program_list_t *pl) /* {{{ */ } /* 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; @@ -396,7 +399,6 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ status = pipe (fd_pipe_in); if (status != 0) { - char errbuf[1024]; ERROR ("exec plugin: pipe failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); return (-1); @@ -405,7 +407,14 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ 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); @@ -414,7 +423,6 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ pid = fork (); if (pid < 0) { - char errbuf[1024]; ERROR ("exec plugin: fork failed: %s", sstrerror (errno, errbuf, sizeof (errbuf))); return (-1); @@ -428,45 +436,32 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ 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); @@ -475,6 +470,7 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ 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]; @@ -486,6 +482,11 @@ static int fork_child (program_list_t *pl, int *fd_in, int *fd_out) /* {{{ */ 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 }}} */ @@ -509,48 +510,112 @@ static int parse_line (char *buffer) /* {{{ */ 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; @@ -564,6 +629,9 @@ static void *exec_read_one (void *arg) /* {{{ */ pl->flags &= ~PL_RUNNING; pthread_mutex_unlock (&pl_lock); + close (fd); + close (fd_err); + pthread_exit ((void *) 0); return (NULL); } /* void *exec_read_one }}} */ @@ -578,7 +646,7 @@ static void *exec_notification_one (void *arg) /* {{{ */ 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 new file mode 100644 index 00000000..95e2e7c1 --- /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 + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "utils_ignorelist.h" + +#include + +#include +#include +#include +#include +#include + +/* + * 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 b67928c7..e1503642 100644 --- a/src/network.c +++ b/src/network.c @@ -1759,9 +1759,25 @@ static int network_init (void) 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 a44e8a57..3b107fb7 100644 --- a/src/nginx.c +++ b/src/nginx.c @@ -27,10 +27,12 @@ #include -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; @@ -44,6 +46,8 @@ static const char *config_keys[] = "URL", "User", "Password", + "VerifyPeer", + "VerifyHost", "CACert" }; static int config_keys_num = STATIC_ARRAY_SIZE (config_keys); @@ -89,6 +93,10 @@ static int config (const char *key, const char *value) 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 @@ -128,6 +136,24 @@ static int init (void) 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/perl.c b/src/perl.c index 877bc008..bd6345ba 100644 --- a/src/perl.c +++ b/src/perl.c @@ -61,8 +61,9 @@ #define PLUGIN_SHUTDOWN 3 #define PLUGIN_LOG 4 #define PLUGIN_NOTIF 5 +#define PLUGIN_FLUSH 6 -#define PLUGIN_TYPES 6 +#define PLUGIN_TYPES 7 #define PLUGIN_DATASET 255 @@ -77,6 +78,8 @@ void boot_DynaLoader (PerlInterpreter *, CV *); static XS (Collectd_plugin_register_ds); static XS (Collectd_plugin_unregister_ds); static XS (Collectd_plugin_dispatch_values); +static XS (Collectd_plugin_flush_one); +static XS (Collectd_plugin_flush_all); static XS (Collectd_plugin_dispatch_notification); static XS (Collectd_plugin_log); static XS (Collectd_call_by_name); @@ -130,6 +133,8 @@ static struct { { "Collectd::plugin_register_data_set", Collectd_plugin_register_ds }, { "Collectd::plugin_unregister_data_set", Collectd_plugin_unregister_ds }, { "Collectd::plugin_dispatch_values", Collectd_plugin_dispatch_values }, + { "Collectd::plugin_flush_one", Collectd_plugin_flush_one }, + { "Collectd::plugin_flush_all", Collectd_plugin_flush_all }, { "Collectd::plugin_dispatch_notification", Collectd_plugin_dispatch_notification }, { "Collectd::plugin_log", Collectd_plugin_log }, @@ -148,6 +153,7 @@ struct { { "Collectd::TYPE_SHUTDOWN", PLUGIN_SHUTDOWN }, { "Collectd::TYPE_LOG", PLUGIN_LOG }, { "Collectd::TYPE_NOTIF", PLUGIN_NOTIF }, + { "Collectd::TYPE_FLUSH", PLUGIN_FLUSH }, { "Collectd::TYPE_DATASET", PLUGIN_DATASET }, { "Collectd::DS_TYPE_COUNTER", DS_TYPE_COUNTER }, { "Collectd::DS_TYPE_GAUGE", DS_TYPE_GAUGE }, @@ -755,6 +761,12 @@ static int pplugin_call_all (pTHX_ int type, ...) XPUSHs (sv_2mortal (newRV_noinc ((SV *)notif))); } + else if (PLUGIN_FLUSH == type) { + /* + * $_[0] = $timeout; + */ + XPUSHs (sv_2mortal (newSViv (va_arg (ap, int)))); + } PUTBACK; @@ -891,6 +903,54 @@ static XS (Collectd_plugin_dispatch_values) } /* static XS (Collectd_plugin_dispatch_values) */ /* + * Collectd::plugin_flush_one (timeout, name). + * + * timeout: + * timeout to use when flushing the data + * + * name: + * name of the plugin to flush + */ +static XS (Collectd_plugin_flush_one) +{ + dXSARGS; + + if (2 != items) { + log_err ("Usage: Collectd::plugin_flush_one(timeout, name)"); + XSRETURN_EMPTY; + } + + log_debug ("Collectd::plugin_flush_one: timeout = %i, name = \"%s\"", + (int)SvIV (ST (0)), SvPV_nolen (ST (1))); + + if (0 == plugin_flush_one ((int)SvIV (ST (0)), SvPV_nolen (ST (1)))) + XSRETURN_YES; + else + XSRETURN_EMPTY; +} /* static XS (Collectd_plugin_flush_one) */ + +/* + * Collectd::plugin_flush_all (timeout). + * + * timeout: + * timeout to use when flushing the data + */ +static XS (Collectd_plugin_flush_all) +{ + dXSARGS; + + if (1 != items) { + log_err ("Usage: Collectd::plugin_flush_all(timeout)"); + XSRETURN_EMPTY; + } + + log_debug ("Collectd::plugin_flush_all: timeout = %i", (int)SvIV (ST (0))); + + plugin_flush_all ((int)SvIV (ST (0))); + XSRETURN_YES; +} /* static XS (Collectd_plugin_flush_all) */ + +/* * Collectd::plugin_dispatch_notification (notif). * * notif: @@ -1195,6 +1255,25 @@ static int perl_notify (const notification_t *notif) return pplugin_call_all (aTHX_ PLUGIN_NOTIF, notif); } /* static int perl_notify (const notification_t *) */ +static int perl_flush (const int timeout) +{ + dTHX; + + if (NULL == perl_threads) + return 0; + + if (NULL == aTHX) { + c_ithread_t *t = NULL; + + pthread_mutex_lock (&perl_threads->mutex); + t = c_ithread_create (perl_threads->head->interp); + pthread_mutex_unlock (&perl_threads->mutex); + + aTHX = t->interp; + } + return pplugin_call_all (aTHX_ PLUGIN_FLUSH, timeout); +} /* static int perl_flush (const int) */ + static int perl_shutdown (void) { c_ithread_t *t = NULL; @@ -1226,6 +1305,7 @@ static int perl_shutdown (void) plugin_unregister_init ("perl"); plugin_unregister_read ("perl"); plugin_unregister_write ("perl"); + plugin_unregister_flush ("perl"); ret = pplugin_call_all (aTHX_ PLUGIN_SHUTDOWN); @@ -1411,6 +1491,7 @@ static int init_pi (int argc, char **argv) plugin_register_read ("perl", perl_read); plugin_register_write ("perl", perl_write); + plugin_register_flush ("perl", perl_flush); plugin_register_shutdown ("perl", perl_shutdown); return 0; } /* static int init_pi (const char **, const int) */ diff --git a/src/plugin.c b/src/plugin.c index 8b2803df..1aad97c1 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -1,6 +1,6 @@ /** * 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 @@ -17,6 +17,7 @@ * * Authors: * Florian octo Forster + * Sebastian Harl **/ #include "collectd.h" @@ -53,6 +54,7 @@ typedef struct read_func_s read_func_t; 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; @@ -437,6 +439,11 @@ int plugin_register_write (const char *name, 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)) { @@ -531,6 +538,11 @@ int plugin_unregister_write (const char *name) 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)); @@ -643,6 +655,43 @@ void plugin_read_all (void) pthread_mutex_unlock (&read_lock); } /* void plugin_read_all */ +int plugin_flush_one (int timeout, const char *name) +{ + int (*callback) (int); + llentry_t *le; + int status; + + if (list_flush == NULL) + return (-1); + + le = llist_search (list_flush, name); + if (le == NULL) + return (-1); + callback = (int (*) (int)) le->value; + + status = (*callback) (timeout); + + return (status); +} /* int plugin_flush_ont */ + +void plugin_flush_all (int timeout) +{ + int (*callback) (int); + llentry_t *le; + + if (list_flush == NULL) + return; + + le = llist_head (list_flush); + while (le != NULL) + { + callback = (int (*) (int)) le->value; + le = le->next; + + (*callback) (timeout); + } +} /* void plugin_flush_all */ + void plugin_shutdown_all (void) { int (*callback) (void); diff --git a/src/plugin.h b/src/plugin.h index 25c745cb..7b59930d 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -2,7 +2,7 @@ #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 @@ -19,6 +19,7 @@ * * Authors: * Florian octo Forster + * Sebastian Harl **/ #include "collectd.h" @@ -149,8 +150,11 @@ int plugin_load (const char *name); 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 @@ -167,6 +171,8 @@ int plugin_register_read (const char *name, 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); @@ -180,6 +186,7 @@ int plugin_unregister_complex_config (const char *name); 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 new file mode 100644 index 00000000..ca3f38c1 --- /dev/null +++ b/src/powerdns.c @@ -0,0 +1,972 @@ +/** + * 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 + * Florian Forster + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 noerror-answers nxdomain-answers " \ + "servfail-answers sys-msec user-msec qa-latency cache-entries cache-hits " \ + "cache-misses questions" + +struct list_item_s; +typedef struct list_item_s list_item_t; + +struct list_item_s +{ + enum + { + SRV_AUTHORITATIVE, + SRV_RECURSOR + } server_type; + int (*func) (list_item_t *item); + char *instance; + + char **fields; + int fields_num; + 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 +}}} */ + +const char* const default_server_fields[] = /* {{{ */ +{ + "latency" + "packetcache-hit", + "packetcache-miss", + "packetcache-size", + "query-cache-hit", + "query-cache-miss", + "recursing-answers", + "recursing-questions", + "tcp-answers", + "tcp-queries", + "udp-answers", + "udp-queries", +}; /* }}} */ +int default_server_fields_num = STATIC_ARRAY_SIZE (default_server_fields); + +statname_lookup_t lookup_table[] = /* {{{ */ +{ + /********************* + * Server statistics * + *********************/ + /* 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}, + + /* Other stuff.. */ + {"corrupt-packets", "io_packets", "corrupt"}, + {"deferred-cache-inserts", "counter", "cache-deferred_insert"}, + {"deferred-cache-lookup", "counter", "cache-deferred_lookup"}, + {"qsize-a", "cache_size", "answers"}, + {"qsize-q", "cache_size", "questions"}, + {"servfail-packets", "io_packets", "servfail"}, + {"timedout-packets", "io_packets", "timeout"}, + {"udp4-answers", "dns_answer", "udp4"}, + {"udp4-queries", "dns_question", "queries-udp4"}, + {"udp6-answers", "dns_answer", "udp6"}, + {"udp6-queries", "dns_question", "queries-udp6"}, + + /*********************** + * 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"}, + + /* All the other stuff.. */ + {"all-outqueries", "dns_question", "outgoing"}, + {"answers0-1", "dns_answer", "0_1"}, + {"answers1-10", "dns_answer", "1_10"}, + {"answers10-100", "dns_answer", "10_100"}, + {"answers100-1000", "dns_answer", "100_1000"}, + {"answers-slow", "dns_answer", "slow"}, + {"chain-resends", "dns_question", "chained"}, + {"client-parse-errors", "counter", "drops-client_parse_error"}, + {"concurrent-queries", "dns_question", "concurrent"}, + {"dlg-only-drops", "counter", "drops-delegation_only"}, + {"negcache-entries", "cache_size", "negative"}, + {"nsspeeds-entries", "gauge", "entries-ns_speeds"}, + {"nsset-invalidations", "counter", "ns_set_invalidation"}, + {"outgoing-timeouts", "counter", "drops-timeout_outgoing"}, + {"resource-limits", "counter", "drops-resource_limit"}, + {"server-parse-errors", "counter", "drops-server_parse_error"}, + {"spoof-prevents", "counter", "drops-spoofed"}, + {"tcp-client-overflow", "counter", "denied-client_overflow_tcp"}, + {"tcp-outqueries", "dns_question", "outgoing-tcp"}, + {"tcp-questions", "dns_question", "incoming-tcp"}, + {"throttled-out", "dns_question", "outgoing-throttled"}, + {"throttle-entries", "gauge", "entries-throttle"}, + {"unauthorized-tcp", "counter", "denied-unauthorized_tcp"}, + {"unauthorized-udp", "counter", "denied-unauthorized_udp"}, + {"unexpected-packets", "dns_answer", "unexpected"} + /* {"uptime", "", ""} */ +}; /* }}} */ +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; + +/* TODO: Do this before 4.4: + * - Recursor: + * - Complete list of known pdns -> collectd mappings. + * - Update the collectd.conf(5) manpage. + * + * -octo + */ + +/* */ +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) + { + INFO ("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; + + const char* const *fields; + int fields_num; + + if (item->command == NULL) + item->command = strdup ("SHOW *"); + if (item->command == NULL) + { + ERROR ("powerdns plugin: strdup failed."); + return (-1); + } + + status = powerdns_get_data (item, &buffer, &buffer_size); + if (status != 0) + return (-1); + + if (item->fields_num != 0) + { + fields = (const char* const *) item->fields; + fields_num = item->fields_num; + } + else + { + fields = default_server_fields; + fields_num = default_server_fields_num; + } + + assert (fields != NULL); + assert (fields_num > 0); + + /* 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) + { + int i; + + dummy = NULL; + + value = strchr (key, '='); + if (value == NULL) + break; + + *value = '\0'; + value++; + + if (value[0] == '\0') + continue; + + /* Check if this item was requested. */ + for (i = 0; i < fields_num; i++) + if (strcasecmp (key, fields[i]) == 0) + break; + if (i >= fields_num) + continue; + + submit (item->instance, key, value); + } /* while (strtok_r) */ + + sfree (buffer); + + return (0); +} /* }}} int powerdns_read_server */ + +/* + * powerdns_update_recursor_command + * + * Creates a string that holds the command to be sent to the recursor. This + * string is stores in the `command' member of the `list_item_t' passed to the + * function. This function is called by `powerdns_read_recursor'. + */ +static int powerdns_update_recursor_command (list_item_t *li) /* {{{ */ +{ + char buffer[4096]; + int status; + + if (li == NULL) + return (0); + + if (li->fields_num < 1) + { + sstrncpy (buffer, RECURSOR_COMMAND, sizeof (buffer)); + } + else + { + strcpy (buffer, "get "); + status = strjoin (&buffer[4], sizeof (buffer) - strlen ("get "), + li->fields, li->fields_num, + /* seperator = */ " "); + if (status < 0) + { + ERROR ("powerdns plugin: strjoin failed."); + return (-1); + } + } + + buffer[sizeof (buffer) - 1] = 0; + li->command = strdup (buffer); + if (li->command == NULL) + { + ERROR ("powerdns plugin: strdup failed."); + return (-1); + } + + return (0); +} /* }}} int powerdns_update_recursor_command */ + +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; + + if (item->command == NULL) + { + status = powerdns_update_recursor_command (item); + if (status != 0) + { + ERROR ("powerdns plugin: powerdns_update_recursor_command failed."); + return (-1); + } + } + assert (item->command != NULL); + + 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 powerdns_config_add_string */ + +static int powerdns_config_add_collect (list_item_t *li, /* {{{ */ + oconfig_item_t *ci) +{ + int i; + char **temp; + + if (ci->values_num < 1) + { + WARNING ("powerdns plugin: The `Collect' option needs " + "at least one argument."); + return (-1); + } + + for (i = 0; i < ci->values_num; i++) + if (ci->values[i].type != OCONFIG_TYPE_STRING) + { + WARNING ("powerdns plugin: Only string arguments are allowed to " + "the `Collect' option."); + return (-1); + } + + temp = (char **) realloc (li->fields, + sizeof (char *) * (li->fields_num + ci->values_num)); + if (temp == NULL) + { + WARNING ("powerdns plugin: realloc failed."); + return (-1); + } + li->fields = temp; + + for (i = 0; i < ci->values_num; i++) + { + li->fields[li->fields_num] = strdup (ci->values[i].value.string); + if (li->fields[li->fields_num] == NULL) + { + WARNING ("powerdns plugin: strdup failed."); + continue; + } + li->fields_num++; + } + + /* Invalidate a previously computed command */ + sfree (li->command); + + return (0); +} /* }}} int powerdns_config_add_collect */ + +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->server_type = SRV_AUTHORITATIVE; + item->func = powerdns_read_server; + item->socktype = SOCK_STREAM; + socket_temp = strdup (SERVER_SOCKET); + } + else if (strcasecmp ("Recursor", ci->key) == 0) + { + item->server_type = SRV_RECURSOR; + item->func = powerdns_read_recursor; + item->socktype = SOCK_DGRAM; + socket_temp = strdup (RECURSOR_SOCKET); + } + else + { + /* We must never get here.. */ + assert (0); + return (-1); + } + + status = 0; + for (i = 0; i < ci->children_num; i++) + { + oconfig_item_t *option = ci->children + i; + + if (strcasecmp ("Collect", option->key) == 0) + status = powerdns_config_add_collect (item, 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); + else 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 3bb5a9e7..93c9d7a8 100644 --- a/src/rrdtool.c +++ b/src/rrdtool.c @@ -195,7 +195,7 @@ static int rra_get (char ***ret, const value_list_t *vl) span = rts[i]; if ((span / ss) < rrarows) - continue; + span = ss * rrarows; if (cdp_len == 0) cdp_len = 1; @@ -947,6 +947,20 @@ static int rrd_write (const data_set_t *ds, const value_list_t *vl) 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) @@ -1152,5 +1166,6 @@ void module_register (void) 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 173ed9c1..0968e311 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -1,6 +1,6 @@ /** * 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 new file mode 100644 index 00000000..01bf6292 --- /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 + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" +#include "utils_tail_match.h" + +/* + * + * + * Instance "exim" + * + * Regex "S=([1-9][0-9]*)" + * DSType "CounterAdd" + * Type "ipt_bytes" + * Instance "total" + * + * + * + */ + +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 new file mode 100644 index 00000000..b7992d80 --- /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 + * Florian Forster + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" + +#include +#include +#include +#include +#include + +/* + * 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 a19f26dc..97c170b7 100644 --- a/src/types.db +++ b/src/types.db @@ -3,6 +3,8 @@ apache_connections count:GAUGE:0:65535 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 @@ -15,9 +17,11 @@ disk_merged read:COUNTER:0:4294967295, write:COUNTER:0:4294967295 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 @@ -36,9 +40,12 @@ if_octets rx:COUNTER:0:4294967295, tx:COUNTER:0:4294967295 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 @@ -57,6 +64,7 @@ nginx_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 @@ -77,6 +85,10 @@ time_offset seconds:GAUGE:-1000000:1000000 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 ff842628..3be792d5 100644 --- a/src/types_list.c +++ b/src/types_list.c @@ -85,9 +85,6 @@ static int parse_ds (data_source_t *dsrc, char *buf, size_t buf_len) 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 */ @@ -125,9 +122,6 @@ static void parse_line (char *buf) 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 45ed9c69..025c91d5 100644 --- a/src/unixsock.c +++ b/src/unixsock.c @@ -24,6 +24,9 @@ #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" @@ -43,21 +46,6 @@ #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 */ /* valid configuration file keys */ @@ -65,10 +53,9 @@ static const char *config_keys[] = { "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; @@ -80,261 +67,9 @@ static int sock_perms = S_IRWXU | S_IRWXG; 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; @@ -420,130 +155,6 @@ static int us_open_socket (void) 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; @@ -603,7 +214,7 @@ static void *us_handle_client (void *arg) 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) { @@ -611,12 +222,16 @@ static void *us_handle_client (void *arg) } 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]); @@ -707,13 +322,21 @@ static int us_config (const char *key, const char *val) { 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) { @@ -764,20 +387,11 @@ static int us_shutdown (void) 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/users.c b/src/users.c index afe26e1e..4686443e 100644 --- a/src/users.c +++ b/src/users.c @@ -23,6 +23,10 @@ #include "common.h" #include "plugin.h" +#if HAVE_STATGRAB_H +# include +#endif /* HAVE_STATGRAB_H */ + #if HAVE_UTMPX_H # include /* #endif HAVE_UTMPX_H */ @@ -89,6 +93,16 @@ static int users_read (void) users_submit (users); /* #endif HAVE_GETUTENT */ +#elif HAVE_LIBSTATGRAB + sg_user_stats *us; + + us = sg_get_user_stats (); + if (us == NULL) + return (-1); + + users_submit ((gauge_t) us->num_entries); +/* #endif HAVE_LIBSTATGRAB */ + #else # error "No applicable input method." #endif diff --git a/src/utils_cache.c b/src/utils_cache.c index b9b89621..9f7e3b68 100644 --- a/src/utils_cache.c +++ b/src/utils_cache.c @@ -1,6 +1,6 @@ /** * 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 @@ -460,41 +460,155 @@ int uc_update (const data_set_t *ds, const value_list_t *vl) 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 51d9c031..a965febe 100644 --- a/src/utils_cache.h +++ b/src/utils_cache.h @@ -32,8 +32,11 @@ 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 new file mode 100644 index 00000000..b1973be5 --- /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 + * Florian "octo" Forster + **/ + +#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 new file mode 100644 index 00000000..334f0862 --- /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 + **/ + +#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 new file mode 100644 index 00000000..f110196a --- /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 + **/ + +#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 new file mode 100644 index 00000000..d7bd1151 --- /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 + **/ + +#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 new file mode 100644 index 00000000..8d6c7836 --- /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 + **/ + +#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 new file mode 100644 index 00000000..c9187962 --- /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 + **/ + +#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 : */ diff --git a/src/utils_cmd_putnotif.h b/src/utils_cmd_putnotif.h index a9531721..08b3bb37 100644 --- a/src/utils_cmd_putnotif.h +++ b/src/utils_cmd_putnotif.h @@ -22,8 +22,6 @@ #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 609efcbd..2ae45323 100644 --- a/src/utils_cmd_putval.h +++ b/src/utils_cmd_putval.h @@ -22,8 +22,6 @@ #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 94d6bdae..1d9467fe 100644 --- a/src/utils_ignorelist.c +++ b/src/utils_ignorelist.c @@ -1,6 +1,7 @@ /** * collectd - src/utils_ignorelist.c * Copyright (C) 2006 Lubos Stanek + * 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 Li- @@ -19,6 +20,7 @@ * * Authors: * Lubos Stanek + * Florian Forster **/ /** * ignorelist handles plugin's list of configured collectable @@ -332,10 +334,8 @@ int ignorelist_match (ignorelist_t *il, const char *entry) { 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 new file mode 100644 index 00000000..9e75e4e2 --- /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 + **/ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" + +#include "utils_match.h" + +#include + +#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 new file mode 100644 index 00000000..a39c8694 --- /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 + **/ + +#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 new file mode 100644 index 00000000..eaf8f738 --- /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 + * Florian Forster + * + * 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 new file mode 100644 index 00000000..c4793197 --- /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 + * + * 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 new file mode 100644 index 00000000..34fe2dce --- /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 + * Florian Forster + * + * 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 new file mode 100644 index 00000000..d08c728e --- /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 + * Florian Forster + * + * 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 778b40bb..6c131ba6 100644 --- a/src/utils_threshold.c +++ b/src/utils_threshold.c @@ -41,11 +41,13 @@ typedef struct threshold_s 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; /* }}} */ @@ -62,11 +64,31 @@ static pthread_mutex_t threshold_lock = PTHREAD_MUTEX_INITIALIZER; * 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, @@ -92,11 +114,29 @@ static int ut_threshold_add (const threshold_t *th) 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) @@ -118,6 +158,22 @@ static int ut_threshold_add (const threshold_t *th) * 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) @@ -243,6 +299,8 @@ static int ut_config_type (const threshold_t *th_orig, oconfig_item_t *ci) 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); @@ -443,25 +501,6 @@ int ut_config (const oconfig_item_t *ci) */ /* }}} */ -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) { @@ -507,128 +546,84 @@ static threshold_t *threshold_search (const data_set_t *ds, 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; @@ -639,8 +634,8 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl) 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) { @@ -649,7 +644,7 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl) 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 @@ -658,7 +653,7 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl) "%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); } } @@ -668,7 +663,7 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl) "%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; @@ -677,10 +672,163 @@ int ut_check_threshold (const data_set_t *ds, const value_list_t *vl) 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 new file mode 100644 index 00000000..e0f76e76 --- /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 + **/ + +#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 : */