From: Florian Forster Date: Thu, 21 Dec 2006 19:40:49 +0000 (+0100) Subject: Merge branch 'collectd-3.10' X-Git-Tag: collectd-3.11.0~9 X-Git-Url: https://git.octo.it/?a=commitdiff_plain;h=e913f3f6b71b0ba4abd6aaf2202100a5cef48b75;hp=6f8769a51a656fb136319ddc5e1d0ec7864ce510;p=collectd.git Merge branch 'collectd-3.10' Conflicts: ChangeLog --- diff --git a/AUTHORS b/AUTHORS index 178cf5c9..cde1d11a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,7 +4,7 @@ This package was written by: apcups plugin by: Anthony Gialluca -cpufreq module by: +cpufreq and multimeter module by: Peter Holik hddtemp module by: diff --git a/ChangeLog b/ChangeLog index 9238f770..2615eaf8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,27 @@ +pending, Version 3.11.0 + * collectd: The new command line option `-P' makes it easier for + distributors to change the location of PID-files. + * collectd: The daemon shuts down faster now which makes it easier to + write init.d-scripts for it. + * apache plugin: Increase the buffersize to 16k, because the 4k buffer + caused problems every now and then. + * df plugin: New config options allow to ignore certain mountpoints, + filesystem types or devices. + * dns plugin: The new dns plugin uses `libpcap' to capture DNS traffic + and interprets it. It collects traffic as well as qtype, opcode and + rcode counts. + * email plugin: Sebastian Harl has contributed this plugin which + counts received mails in categories (e. g. ham, spam, virus), spam + score (as given by SpamAssassin) and check types. + * processes plugin: Collect detailed statistics for configured + processes, that's process and thread count, CPU usage, resident + segment size and pagefaults. + * multimeter plugin: Peter Holik contributed a new plugin which + queries multimeters. + * sensors plugin: Lubos Stanek has put much effort into improving this + plugin, including `extended naming', collection of voltage values + and the possibility to ignore certain values. + 2006-12-21, Version 3.10.4 * Max Kellermann has identified a bug in the server routine: When opening a socket fails the daemon will (re)try opening the socket in diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..e69de29b diff --git a/README b/README index 511683f7..0fd37263 100644 --- a/README +++ b/README @@ -39,6 +39,12 @@ Features - Disk utilization (Sectors read/written, number of read/write actions, time spent doing IO) + - DNS traffic + (query types, response codes, opcodes and traffic) + + - Email statistics + (count, traffic, spam scores and checks) + - Harddisk temperatures (Uhm, yeah, temperature of harddisks that is ;) @@ -65,7 +71,7 @@ Features (Number of running, sleeping, zombie, ... processes) - Sensors - (System temperatured and fan rotation speeds) + (lm_sensors voltages, temperatures and fan rotation speeds) - Serial (RX and TX of serial interfaces) diff --git a/collectd.spec b/collectd.spec index e3050217..387763c0 100644 --- a/collectd.spec +++ b/collectd.spec @@ -80,6 +80,7 @@ rm -rf $RPM_BUILD_ROOT %attr(0444,root,root) %{_libdir}/%{name}/hddtemp.so* %attr(0444,root,root) %{_libdir}/%{name}/load.so* %attr(0444,root,root) %{_libdir}/%{name}/memory.so* +%attr(0444,root,root) %{_libdir}/%{name}/multimeter.so* %attr(0444,root,root) %{_libdir}/%{name}/nfs.so* %attr(0444,root,root) %{_libdir}/%{name}/ntpd.so* %attr(0444,root,root) %{_libdir}/%{name}/ping.so* diff --git a/configure.in b/configure.in index 80544171..27c29789 100644 --- a/configure.in +++ b/configure.in @@ -25,7 +25,7 @@ AC_SUBST(LIBLTDL) AC_LIBTOOL_DLOPEN AC_PROG_LIBTOOL #AC_PROG_RANLIB -AC_CONFIG_SUBDIRS(libltdl src/libconfig) +AC_CONFIG_SUBDIRS(libltdl) # # Checks for header files. @@ -197,6 +197,9 @@ AC_CHECK_HEADERS(IOKit/storage/IOBlockStorageDriver.h) # For load module AC_CHECK_HEADERS(sys/loadavg.h) +# For the processes plugin +AC_CHECK_HEADERS(linux/config.h) + # For the swap module AC_CHECK_HEADERS(sys/swap.h) @@ -266,9 +269,30 @@ AC_CHECK_HEADERS(sys/vfstab.h) # For the swap plugin, FreeBSD AC_CHECK_HEADERS(kvm.h) +# For the email plugin +AC_CHECK_HEADERS(linux/un.h, [], [], +[ +#if HAVE_SYS_SOCKET_H +# include +#endif +]) +AC_CHECK_HEADERS(sys/un.h) +AC_CHECK_HEADERS(grp.h) + # For debugging interface (variable number of arguments) AC_CHECK_HEADERS(stdarg.h) +# Regular expressions for the ignorelist. +AC_CHECK_HEADERS(regex.h) + +# For the dns plugin +AC_CHECK_HEADERS(arpa/nameser.h arpa/nameser_compat.h) + +AC_CHECK_HEADERS(net/if_arp.h) +AC_CHECK_HEADERS(net/if_ppp.h) +AC_CHECK_HEADERS(netinet/if_ether.h) +AC_CHECK_HEADERS(netinet/udp.h) + dnl Checking for libraries AC_CHECK_LIB(m, ext) @@ -299,6 +323,9 @@ nanosleep_needs_rt="no" AC_CHECK_FUNCS(nanosleep, [], AC_CHECK_LIB(rt, nanosleep, [nanosleep_needs_rt="yes"], AC_MSG_ERROR(cannot find nanosleep))) AM_CONDITIONAL(BUILD_WITH_LIBRT, test "x$nanosleep_needs_rt" = "xyes") +# Regular expressions for the ignorelist. +AC_CHECK_FUNCS(regcomp regerror regexec regfree) + # For cpu module AC_CHECK_FUNCS(sysctlbyname, [have_sysctlbyname="yes"], [have_sysctlbyname="no"]) @@ -483,31 +510,36 @@ AC_DEFINE_UNQUOTED(COLLECT_RRDTOOL, [$collect_rrdtool], [Wether or not to use rrdtool library]) AM_CONDITIONAL(BUILD_WITH_RRDTOOL, test "x$with_rrdtool" = "xyes") -#AC_ARG_WITH(pth, [AS_HELP_STRING([--with-pth=@<:@=PREFIX@:>@], [Path to pth (experimental).]), -#[ if test "x$withval" != "xno" && test "x$withval" != "xyes" -# then -# LDFLAGS="$LDFLAGS -L$withval/lib" -# CPPFLAGS="$CPPFLAGS -I$withval/include" -# with_pth="yes" -# fi -#], [with_pth="no"]) -#if test "x$with_pth" = "xyes" -#then -# AC_CHECK_LIB(pth, pth_init,, [with_pth="no (libpth not found)"], []) -#fi -#if test "x$with_pth" = "xyes" -#then -# AC_CHECK_HEADERS(pth.h,, [with_pth="no (pth.h not found)"]) -#fi -#if test "x$with_pth" = "xyes" -#then -# collect_pth=1 -#else -# collect_pth=0 -#fi -#AC_DEFINE_UNQUOTED(COLLECT_PTH, [$collect_pth], -# [Wether or not to use pth (portable threads) library]) -#AM_CONDITIONAL(BUILD_WITH_PTH, test "x$with_pth" = "xyes") +AC_ARG_WITH(libpthread, [AS_HELP_STRING([--with-libpthread=@<:@=PREFIX@:>@], [Path to libpthread.])], +[ if test "x$withval" != "xno" -a "x$withval" != "xyes" + then + LDFLAGS="$LDFLAGS -L$withval/lib" + CPPFLAGS="$CPPFLAGS -I$withval/include" + with_libpthread="yes" + else + if test "x$withval" = "xno" + then + with_libpthread="no (disabled)" + fi + fi +], [with_libpthread="yes"]) +if test "x$with_libpthread" = "xyes" +then + AC_CHECK_LIB(pthread, pthread_create, [with_libpthread="yes"], [with_libpthread="no (libpthread not found)"], []) +fi +if test "x$with_libpthread" = "xyes" +then + AC_CHECK_HEADERS(pthread.h,, [with_libpthread="no (pthread.h not found)"]) +fi +if test "x$with_libpthread" = "xyes" +then + collect_pthread=1 +else + collect_pthread=0 +fi +AC_DEFINE_UNQUOTED(HAVE_LIBPTHREAD, [$collect_pthread], + [Wether or not to use pthread (POSIX threads) library]) +AM_CONDITIONAL(BUILD_WITH_LIBPTHREAD, test "x$with_libpthread" = "xyes") if test "$ac_system" = "Solaris" then @@ -802,6 +834,42 @@ then with_liboping="yes (shipped version)" fi +AC_ARG_WITH(libpcap, [AS_HELP_STRING([--with-libpcap@<:@=PREFIX@:>@], [Path to libpcap.])], +[ + if test "x$withval" != "xno" && test "x$withval" != "xyes" + then + LDFLAGS="$LDFLAGS -L$withval/lib" + CPPFLAGS="$CPPFLAGS -I$withval/include" + with_libpcap="yes" + fi +], +[ + with_libpcap="yes" +]) +if test "x$with_libpcap" = "xyes" +then + AC_CHECK_LIB(pcap, pcap_open_live, + [ + AC_DEFINE(HAVE_LIBPCAP, 1, [Define to 1 if you have the pcap library (-lpcap).]) + ], [with_libpcap="no (libpcap not found)"]) +fi +if test "x$with_libpcap" = "xyes" +then + AC_CHECK_HEADERS(pcap.h, + [ + AC_DEFINE(HAVE_PCAP_H, 1, [Define to 1 if you have the header file.]) + ], [with_libpcap="no (pcap.h not found)"]) +fi +if test "x$with_libpcap" = "xyes" +then + collect_libpcap=1 +else + collect_libpcap=0 +fi +AC_DEFINE_UNQUOTED(COLLECT_LIBPCAP, [$collect_libpcap], + [Wether or not to use the pcap library]) +AM_CONDITIONAL(BUILD_WITH_LIBPCAP, test "x$with_libpcap" = "xyes") + # Define `step' and `hearbeat' values.. declare -i collectd_step=10 declare -i collectd_heartbeat=25 @@ -838,7 +906,6 @@ then AC_DEFINE_UNQUOTED(COLLECTD_HEARTBEAT, "$collectd_heartbeat", [Interval in which plugins are queried.]) fi -# # Check for enabled/disabled features # @@ -918,10 +985,13 @@ AC_COLLECTD([cpu], [disable], [module], [cpu usage statistics]) AC_COLLECTD([cpufreq], [disable], [module], [system cpu frequency statistics]) AC_COLLECTD([disk], [disable], [module], [disk/partition statistics]) AC_COLLECTD([df], [disable], [module], [df statistics]) +AC_COLLECTD([dns], [disable], [module], [dns statistics]) +AC_COLLECTD([email], [disable], [module], [email statistics]) AC_COLLECTD([quota], [enable], [module], [quota statistics (experimental)]) AC_COLLECTD([hddtemp], [disable], [module], [hdd temperature statistics]) AC_COLLECTD([load], [disable], [module], [system load statistics]) AC_COLLECTD([memory], [disable], [module], [memory statistics]) +AC_COLLECTD([multimeter],[disable], [module], [multimeter statistics]) AC_COLLECTD([mysql], [disable], [module], [mysql statistics]) AC_COLLECTD([nfs], [disable], [module], [nfs statistics]) AC_COLLECTD([ntpd], [disable], [module], [nfs statistics]) @@ -950,6 +1020,8 @@ Configuration: libstatgrab . . . . $with_libstatgrab libkstat . . . . . $with_kstat libmysql . . . . . $with_libmysql + libpcap . . . . . . $with_libpcap + libpthread . . . . $with_libpthread Features: debug . . . . . . . $enable_debug @@ -966,9 +1038,12 @@ Configuration: cpufreq . . . . . . $enable_cpufreq df . . . . . . . . $enable_df disk . . . . . . . $enable_disk + dns . . . . . . . . $enable_dns + email . . . . . . . $enable_email hddtemp . . . . . . $enable_hddtemp load . . . . . . . $enable_load memory . . . . . . $enable_memory + multimeter . . . . $enable_multimeter mysql . . . . . . . $enable_mysql nfs . . . . . . . . $enable_nfs ntpd . . . . . . . $enable_ntpd diff --git a/contrib/SpamAssassin/Collectd.pm b/contrib/SpamAssassin/Collectd.pm new file mode 100644 index 00000000..c76c44da --- /dev/null +++ b/contrib/SpamAssassin/Collectd.pm @@ -0,0 +1,218 @@ +#!/usr/bin/perl + +=head1 NAME + +Collectd - plugin for filling collectd with stats + +=head1 INSTALLATION + +Just copy Collectd.pm into your SpamAssassin Plugin path +(e.g /usr/share/perl5/Mail/SpamAssassin/Plugin/) and +add a loadplugin call into your init.pre file. + +=head1 SYNOPSIS + + loadplugin Mail::SpamAssassin::Plugin::Collectd + +=head1 USER SETTINGS + +=over 4 + +=item collectd_socket [ socket path ] (default: /tmp/.collectd-email) + +Where the collectd socket is + +=cut + +=item collectd_buffersize [ size ] (default: 256) + +the email plugin uses a fixed buffer, if a line exceeds this size +it has to be continued in another line. (This is of course handled internally) +If you have changed this setting please get it in sync with the SA Plugin +config. + +=cut + +=item collectd_timeout [ sec ] (default: 2) + +if sending data to to collectd takes too long the connection will be aborted. + +=cut + +=item collectd_retries [ tries ] (default: 3) + +the collectd plugin uses a tread pool, if this is empty the connection fails, +the SA Plugin then tries to reconnect. With this variable you can indicate how +often it should try. + +=cut + +=head1 DESCRIPTION + +This modules uses the email plugin of collectd from Sebastian Harl to +collect statistical informations in rrd files to create some nice looking +graphs with rrdtool. They communicate over a unix socket that the collectd +plugin creates. The generated graphs will be placed in /var/lib/collectd/email + +=head1 AUTHOR + +Alexander Wirt + +=head1 COPYRIGHT + + Copyright 2006 Alexander Wirt + + This program is free software; you can redistribute it and/or modify + it under the the terms of either: + + a) the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + + or + + b) the GPL (http://www.gnu.org/copyleft/gpl.html) + + use whatever you like more. + +=cut + +package Mail::SpamAssassin::Plugin::Collectd; + +use Mail::SpamAssassin::Plugin; +use Mail::SpamAssassin::Logger; +use strict; +use bytes; +use warnings; +use Time::HiRes qw(usleep); +use IO::Socket; + +use vars qw(@ISA); +@ISA = qw(Mail::SpamAssassin::Plugin); + +sub new { + my ($class, $mailsa) = @_; + + # the usual perlobj boilerplate to create a subclass object + $class = ref($class) || $class; + my $self = $class->SUPER::new($mailsa); + bless ($self, $class); + + # register our config options + $self->set_config($mailsa->{conf}); + + # and return the new plugin object + return $self; +} + +sub set_config { + my ($self, $conf) = @_; + my @cmds = (); + + push (@cmds, { + setting => 'collectd_buffersize', + default => 256, + type => + $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, + }); + + push (@cmds, { + setting => 'collectd_socket', + default => '/tmp/.collectd-email', + type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING, + }); + + push (@cmds, { + setting => 'collectd_timeout', + default => 2, + type => + $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, + }); + + push (@cmds, { + setting => 'collectd_retries', + default => 3, + type => + $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC, + }); + + + $conf->{parser}->register_commands(\@cmds); +} + +sub check_end { + my ($self, $params) = @_; + my $message_status = $params->{permsgstatus}; + #create new connection to our socket + eval { + local $SIG{ALRM} = sub { die "Sending to collectd timed out.\n" }; # NB: \n required + + #generate a timeout + alarm $self->{main}->{conf}->{collectd_timeout}; + + my $sock; + #try at least $self->{main}->{conf}->{collectd_retries} to get a + #connection + for (my $i = 0; $i < $self->{main}->{conf}->{collectd_retries} ; ++$i) { + last if $sock = new IO::Socket::UNIX + ($self->{main}->{conf}->{collectd_socket}); + #sleep a random value between 0 and 50 microsecs to try for a new + #thread + usleep(int(rand(50))); + } + + die("could not connect to " . + $self->{main}->{conf}->{collectd_socket} . ": $! - collectd plugin disabled") unless $sock; + + $sock->autoflush(1); + + my $score = $message_status->{score}; + #get the size of the message + my $body = $message_status->{msg}->{pristine_body}; + + my $len = length($body); + + if ($message_status->{score} >= $self->{main}->{conf}->{required_score} ) { + #hey we have spam + print $sock "e:spam:$len\n"; + } else { + print $sock "e:ham:$len\n"; + } + print $sock "s:$score\n"; + my @tmp_array; + my @tests = @{$message_status->{test_names_hit}}; + + my $buffersize = $self->{main}->{conf}->{collectd_buffersize}; + dbg("collectd: buffersize: $buffersize"); + + while (scalar(@tests) > 0) { + push (@tmp_array, pop(@tests)); + if (length(join(',', @tmp_array) . '\n') > $buffersize) { + push (@tests, pop(@tmp_array)); + if (length(join(',', @tmp_array) . '\n') > $buffersize or scalar(@tmp_array) == 0) { + dbg("collectd: this shouldn't happen. Do you have tests" + ." with names that have more than ~ $buffersize Bytes?"); + return 1; + } else { + dbg ( "collectd: c:" . join(',', @tmp_array) . "\n" ); + print $sock "c:" . join(',', @tmp_array) . "\n"; + #clean the array + @tmp_array = (); + } + } elsif ( scalar(@tests) == 0 ) { + dbg ( "collectd: c:" . join(',', @tmp_array) . '\n' ); + print $sock "c:" . join(',', @tmp_array) . "\n"; + } + } + close($sock); + alarm 0; + }; + if ($@) { + my $message = $@; + chomp($message); + info("collectd: $message"); + return -1; + } +} + +1; + +# vim: syntax=perl sw=4 ts=4 noet shiftround diff --git a/contrib/SpamAssassin/example.cf b/contrib/SpamAssassin/example.cf new file mode 100644 index 00000000..0abfb494 --- /dev/null +++ b/contrib/SpamAssassin/example.cf @@ -0,0 +1,5 @@ +collectd_buffersize 256 +collectd_socket /tmp/.collectd-email +collectd_timeout 2 +collectd_retries 3 + diff --git a/contrib/collection.cgi b/contrib/collection.cgi index 098c702d..5c8bd4dd 100755 --- a/contrib/collection.cgi +++ b/contrib/collection.cgi @@ -247,13 +247,96 @@ our $GraphDefs; 'GPRINT:total_max_ms:MAX:%5.1lf%s Max,', 'GPRINT:total_avg_ms:LAST:%5.1lf%s Last' ], + dns_traffic => ['DEF:rsp_min_raw={file}:responses:MIN', + 'DEF:rsp_avg_raw={file}:responses:AVERAGE', + 'DEF:rsp_max_raw={file}:responses:MAX', + 'DEF:qry_min_raw={file}:queries:MIN', + 'DEF:qry_avg_raw={file}:queries:AVERAGE', + 'DEF:qry_max_raw={file}:queries:MAX', + 'CDEF:rsp_min=rsp_min_raw,8,*', + 'CDEF:rsp_avg=rsp_avg_raw,8,*', + 'CDEF:rsp_max=rsp_max_raw,8,*', + 'CDEF:qry_min=qry_min_raw,8,*', + 'CDEF:qry_avg=qry_avg_raw,8,*', + 'CDEF:qry_max=qry_max_raw,8,*', + 'CDEF:overlap=rsp_avg,qry_avg,GT,qry_avg,rsp_avg,IF', + 'CDEF:mytime=rsp_avg_raw,TIME,TIME,IF', + 'CDEF:sample_len_raw=mytime,PREV(mytime),-', + 'CDEF:sample_len=sample_len_raw,UN,0,sample_len_raw,IF', + 'CDEF:rsp_avg_sample=rsp_avg_raw,UN,0,rsp_avg_raw,IF,sample_len,*', + 'CDEF:rsp_avg_sum=PREV,UN,0,PREV,IF,rsp_avg_sample,+', + 'CDEF:qry_avg_sample=qry_avg_raw,UN,0,qry_avg_raw,IF,sample_len,*', + 'CDEF:qry_avg_sum=PREV,UN,0,PREV,IF,qry_avg_sample,+', + "AREA:rsp_avg#$HalfGreen", + "AREA:qry_avg#$HalfBlue", + "AREA:overlap#$HalfBlueGreen", + "LINE1:rsp_avg#$FullGreen:Responses", + 'GPRINT:rsp_avg:AVERAGE:%5.1lf%s Avg,', + 'GPRINT:rsp_max:MAX:%5.1lf%s Max,', + 'GPRINT:rsp_avg:LAST:%5.1lf%s Last', + 'GPRINT:rsp_avg_sum:LAST:(ca. %5.1lf%sB Total)\l', + "LINE1:qry_avg#$FullBlue:Queries ", + #'GPRINT:qry_min:MIN:%5.1lf %s Min,', + 'GPRINT:qry_avg:AVERAGE:%5.1lf%s Avg,', + 'GPRINT:qry_max:MAX:%5.1lf%s Max,', + 'GPRINT:qry_avg:LAST:%5.1lf%s Last', + 'GPRINT:qry_avg_sum:LAST:(ca. %5.1lf%sB Total)\l' + ], + email => [ + 'DEF:avg={file}:count:AVERAGE', + 'DEF:min={file}:count:MIN', + 'DEF:max={file}:count:MAX', + "AREA:max#$HalfMagenta", + "AREA:min#$Canvas", + "LINE1:avg#$FullMagenta:Count ", + 'GPRINT:min:MIN:%4.1lf Min,', + 'GPRINT:avg:AVERAGE:%4.1lf Avg,', + 'GPRINT:max:MAX:%4.1lf Max,', + 'GPRINT:avg:LAST:%4.1lf Last\l' + ], + email_size => [ + 'DEF:avg={file}:size:AVERAGE', + 'DEF:min={file}:size:MIN', + 'DEF:max={file}:size:MAX', + "AREA:max#$HalfMagenta", + "AREA:min#$Canvas", + "LINE1:avg#$FullMagenta:Count ", + 'GPRINT:min:MIN:%4.1lf Min,', + 'GPRINT:avg:AVERAGE:%4.1lf Avg,', + 'GPRINT:max:MAX:%4.1lf Max,', + 'GPRINT:avg:LAST:%4.1lf Last\l' + ], + spam_score => [ + 'DEF:avg={file}:score:AVERAGE', + 'DEF:min={file}:score:MIN', + 'DEF:max={file}:score:MAX', + "AREA:max#$HalfMagenta", + "AREA:min#$Canvas", + "LINE1:avg#$FullMagenta:Count ", + 'GPRINT:min:MIN:%4.1lf Min,', + 'GPRINT:avg:AVERAGE:%4.1lf Avg,', + 'GPRINT:max:MAX:%4.1lf Max,', + 'GPRINT:avg:LAST:%4.1lf Last\l' + ], + spam_check => [ + 'DEF:avg={file}:hits:AVERAGE', + 'DEF:min={file}:hits:MIN', + 'DEF:max={file}:hits:MAX', + "AREA:max#$HalfMagenta", + "AREA:min#$Canvas", + "LINE1:avg#$FullMagenta:Count ", + 'GPRINT:min:MIN:%4.1lf Min,', + 'GPRINT:avg:AVERAGE:%4.1lf Avg,', + 'GPRINT:max:MAX:%4.1lf Max,', + 'GPRINT:avg:LAST:%4.1lf Last\l' + ], fanspeed => [ 'DEF:temp_avg={file}:value:AVERAGE', 'DEF:temp_min={file}:value:MIN', 'DEF:temp_max={file}:value:MAX', - "AREA:temp_max#$HalfBlue", + "AREA:temp_max#$HalfMagenta", "AREA:temp_min#$Canvas", - "LINE1:temp_avg#$FullBlue:RPM", + "LINE1:temp_avg#$FullMagenta:RPM", 'GPRINT:temp_min:MIN:%4.1lf Min,', 'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,', 'GPRINT:temp_max:MAX:%4.1lf Max,', @@ -275,9 +358,9 @@ our $GraphDefs; 'DEF:temp_avg={file}:value:AVERAGE', 'DEF:temp_min={file}:value:MIN', 'DEF:temp_max={file}:value:MAX', - "AREA:temp_max#$HalfBlue", + "AREA:temp_max#$HalfRed", "AREA:temp_min#$Canvas", - "LINE1:temp_avg#$FullBlue:Temperature", + "LINE1:temp_avg#$FullRed:Temperature", 'GPRINT:temp_min:MIN:%4.1lf Min,', 'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,', 'GPRINT:temp_max:MAX:%4.1lf Max,', @@ -304,13 +387,13 @@ our $GraphDefs; 'GPRINT:tx_avg:AVERAGE:%5.1lf%s Avg,', 'GPRINT:tx_max:MAX:%5.1lf%s Max,', 'GPRINT:tx_avg:LAST:%5.1lf%s Last', - 'GPRINT:tx_avg_sum:LAST:(ca. %.0lf Total)\l', + 'GPRINT:tx_avg_sum:LAST:(ca. %4.0lf%s Total)\l', "LINE1:rx_avg#$FullBlue:RX", #'GPRINT:rx_min:MIN:%5.1lf %s Min,', 'GPRINT:rx_avg:AVERAGE:%5.1lf%s Avg,', 'GPRINT:rx_max:MAX:%5.1lf%s Max,', 'GPRINT:rx_avg:LAST:%5.1lf%s Last', - 'GPRINT:rx_avg_sum:LAST:(ca. %.0lf Total)\l' + 'GPRINT:rx_avg_sum:LAST:(ca. %4.0lf%s Total)\l' ], load => ['DEF:s_avg={file}:shortterm:AVERAGE', 'DEF:s_min={file}:shortterm:MIN', @@ -618,6 +701,18 @@ our $GraphDefs; 'GPRINT:read_avg:AVERAGE:%5.1lf Avg,', 'GPRINT:read_avg:LAST:%5.1lf Last\l' ], + opcode => [ + 'DEF:avg={file}:value:AVERAGE', + 'DEF:min={file}:value:MIN', + 'DEF:max={file}:value:MAX', + "AREA:max#$HalfBlue", + "AREA:min#$Canvas", + "LINE1:avg#$FullBlue:Queries/s", + 'GPRINT:min:MIN:%9.3lf Min,', + 'GPRINT:avg:AVERAGE:%9.3lf Average,', + 'GPRINT:max:MAX:%9.3lf Max,', + 'GPRINT:avg:LAST:%9.3lf Last\l' + ], partition => [ "DEF:rbyte_avg={file}:rbytes:AVERAGE", "DEF:rbyte_min={file}:rbytes:MIN", @@ -712,6 +807,109 @@ our $GraphDefs; 'GPRINT:sleeping_max:MAX:%5.1lf Max,', 'GPRINT:sleeping_avg:LAST:%5.1lf Last\l' ], + ps_rss => [ + 'DEF:avg={file}:byte:AVERAGE', + 'DEF:min={file}:byte:MIN', + 'DEF:max={file}:byte:MAX', + "AREA:avg#$HalfBlue", + "LINE1:avg#$FullBlue:RSS", + 'GPRINT:min:MIN:%5.1lf%s Min,', + 'GPRINT:avg:AVERAGE:%5.1lf%s Avg,', + 'GPRINT:max:MAX:%5.1lf%s Max,', + 'GPRINT:avg:LAST:%5.1lf%s Last\l' + ], + ps_cputime => [ + 'DEF:user_avg_raw={file}:user:AVERAGE', + 'DEF:user_min_raw={file}:user:MIN', + 'DEF:user_max_raw={file}:user:MAX', + 'DEF:syst_avg_raw={file}:syst:AVERAGE', + 'DEF:syst_min_raw={file}:syst:MIN', + 'DEF:syst_max_raw={file}:syst:MAX', + 'CDEF:user_avg=user_avg_raw,1000000,/', + 'CDEF:user_min=user_min_raw,1000000,/', + 'CDEF:user_max=user_max_raw,1000000,/', + 'CDEF:syst_avg=syst_avg_raw,1000000,/', + 'CDEF:syst_min=syst_min_raw,1000000,/', + 'CDEF:syst_max=syst_max_raw,1000000,/', + 'CDEF:user_syst=syst_avg,UN,0,syst_avg,IF,user_avg,+', + "AREA:user_syst#$HalfBlue", + "AREA:syst_avg#$HalfRed", + "LINE1:user_syst#$FullBlue:User ", + 'GPRINT:user_min:MIN:%5.1lf%s Min,', + 'GPRINT:user_avg:AVERAGE:%5.1lf%s Avg,', + 'GPRINT:user_max:MAX:%5.1lf%s Max,', + 'GPRINT:user_avg:LAST:%5.1lf%s Last\l', + "LINE1:syst_avg#$FullRed:System", + 'GPRINT:syst_min:MIN:%5.1lf%s Min,', + 'GPRINT:syst_avg:AVERAGE:%5.1lf%s Avg,', + 'GPRINT:syst_max:MAX:%5.1lf%s Max,', + 'GPRINT:syst_avg:LAST:%5.1lf%s Last\l' + ], + ps_count => [ + 'DEF:procs_avg={file}:processes:AVERAGE', + 'DEF:procs_min={file}:processes:MIN', + 'DEF:procs_max={file}:processes:MAX', + 'DEF:thrds_avg={file}:threads:AVERAGE', + 'DEF:thrds_min={file}:threads:MIN', + 'DEF:thrds_max={file}:threads:MAX', + "AREA:thrds_avg#$HalfBlue", + "AREA:procs_avg#$HalfRed", + "LINE1:thrds_avg#$FullBlue:Threads ", + 'GPRINT:thrds_min:MIN:%5.1lf Min,', + 'GPRINT:thrds_avg:AVERAGE:%5.1lf Avg,', + 'GPRINT:thrds_max:MAX:%5.1lf Max,', + 'GPRINT:thrds_avg:LAST:%5.1lf Last\l', + "LINE1:procs_avg#$FullRed:Processes", + 'GPRINT:procs_min:MIN:%5.1lf Min,', + 'GPRINT:procs_avg:AVERAGE:%5.1lf Avg,', + 'GPRINT:procs_max:MAX:%5.1lf Max,', + 'GPRINT:procs_avg:LAST:%5.1lf Last\l' + ], + ps_pagefaults => [ + 'DEF:minor_avg={file}:minflt:AVERAGE', + 'DEF:minor_min={file}:minflt:MIN', + 'DEF:minor_max={file}:minflt:MAX', + 'DEF:major_avg={file}:majflt:AVERAGE', + 'DEF:major_min={file}:majflt:MIN', + 'DEF:major_max={file}:majflt:MAX', + 'CDEF:minor_major=major_avg,UN,0,major_avg,IF,minor_avg,+', + "AREA:minor_major#$HalfBlue", + "AREA:major_avg#$HalfRed", + "LINE1:minor_major#$FullBlue:Minor", + 'GPRINT:minor_min:MIN:%5.1lf%s Min,', + 'GPRINT:minor_avg:AVERAGE:%5.1lf%s Avg,', + 'GPRINT:minor_max:MAX:%5.1lf%s Max,', + 'GPRINT:minor_avg:LAST:%5.1lf%s Last\l', + "LINE1:major_avg#$FullRed:Major", + 'GPRINT:major_min:MIN:%5.1lf%s Min,', + 'GPRINT:major_avg:AVERAGE:%5.1lf%s Avg,', + 'GPRINT:major_max:MAX:%5.1lf%s Max,', + 'GPRINT:major_avg:LAST:%5.1lf%s Last\l' + ], + qtype => [ + 'DEF:avg={file}:value:AVERAGE', + 'DEF:min={file}:value:MIN', + 'DEF:max={file}:value:MAX', + "AREA:max#$HalfBlue", + "AREA:min#$Canvas", + "LINE1:avg#$FullBlue:Queries/s", + 'GPRINT:min:MIN:%9.3lf Min,', + 'GPRINT:avg:AVERAGE:%9.3lf Average,', + 'GPRINT:max:MAX:%9.3lf Max,', + 'GPRINT:avg:LAST:%9.3lf Last\l' + ], + rcode => [ + 'DEF:avg={file}:value:AVERAGE', + 'DEF:min={file}:value:MIN', + 'DEF:max={file}:value:MAX', + "AREA:max#$HalfBlue", + "AREA:min#$Canvas", + "LINE1:avg#$FullBlue:Queries/s", + 'GPRINT:min:MIN:%9.3lf Min,', + 'GPRINT:avg:AVERAGE:%9.3lf Average,', + 'GPRINT:max:MAX:%9.3lf Max,', + 'GPRINT:avg:LAST:%9.3lf Last\l' + ], swap => [ 'DEF:used_avg={file}:used:AVERAGE', 'DEF:used_min={file}:used:MIN', @@ -760,9 +958,9 @@ our $GraphDefs; 'DEF:temp_avg={file}:value:AVERAGE', 'DEF:temp_min={file}:value:MIN', 'DEF:temp_max={file}:value:MAX', - "AREA:temp_max#$HalfBlue", + "AREA:temp_max#$HalfRed", "AREA:temp_min#$Canvas", - "LINE1:temp_avg#$FullBlue:Value", + "LINE1:temp_avg#$FullRed:Temperature", 'GPRINT:temp_min:MIN:%4.1lf Min,', 'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,', 'GPRINT:temp_max:MAX:%4.1lf Max,', @@ -839,6 +1037,18 @@ our $GraphDefs; 'GPRINT:cpufreq_max:MAX:%5.1lf%s Max,', 'GPRINT:cpufreq_avg:LAST:%5.1lf%s Last\l' ], + multimeter => [ + 'DEF:multimeter_avg={file}:value:AVERAGE', + 'DEF:multimeter_min={file}:value:MIN', + 'DEF:multimeter_max={file}:value:MAX', + "AREA:multimeter_max#$HalfBlue", + "AREA:multimeter_min#$Canvas", + "LINE1:multimeter_avg#$FullBlue:Multimeter", + 'GPRINT:multimeter_min:MIN:%4.1lf Min,', + 'GPRINT:multimeter_avg:AVERAGE:%4.1lf Average,', + 'GPRINT:multimeter_max:MAX:%4.1lf Max,', + 'GPRINT:multimeter_avg:LAST:%4.1lf Last\l' + ], users => [ 'DEF:users_avg={file}:users:AVERAGE', 'DEF:users_min={file}:users:MIN', @@ -967,7 +1177,12 @@ our $GraphArgs = delay => ['-t', 'NTPd peer delay ({inst})', '-v', 'Seconds'], df => ['-t', '{host}:{inst} usage', '-v', 'Percent', '-l', '0'], disk => ['-t', '{host} disk {inst} usage', '-v', 'Byte/s'], - fanspeed => ['-t', '{host} fanspeed {inst}', '-v', 'rpm'], + dns_traffic => ['-t', '{host} DNS traffic', '-v', 'Bit/s'], + email => ['-t', '{host} E-Mail {inst} count', '-v', 'E-Mails'], + email_size => ['-t', '{host} E-Mail {inst} size', '-v', 'Bytes'], + spam_score => ['-t', '{host} spam score', '-v', 'score'], + spam_check => ['-t', '{host} spam checks {inst}', '-v', 'hits'], + fanspeed => ['-t', '{host} fanspeed {inst}', '-v', 'RPM'], frequency_offset => ['-t', 'NTPd frequency offset ({inst})', '-v', 'Parts per million'], hddtemp => ['-t', '{host} hdd temperature {inst}', '-v', '°Celsius'], if_errors => ['-t', '{host} {inst} errors', '-v', 'Errors/s'], @@ -981,9 +1196,16 @@ our $GraphArgs = mysql_qcache => ['-t', 'mysql query cache', '-v', 'Queries/s' ], mysql_threads => ['-t', 'mysql threads', '-v', 'Threads' ], nfs3_procedures => ['-t', '{host} NFSv3 {inst} procedures', '-v', 'Procedures/s' ], + opcode => ['-t', 'OpCode {inst}', '-v', 'Queries/s'], partition => ['-t', '{host} partition {inst} usage', '-v', 'Byte/s'], ping => ['-t', '{host} ping to {inst}', '-v', 'ms'], processes => ['-t', '{host} processes', '-v', 'Processes'], + ps_rss => ['-t', '{host} process {inst} RSS', '-v', 'Bytes', '-b', '1024'], + ps_cputime => ['-t', '{host} process {inst} CPU usage', '-v', 'Seconds'], + ps_count => ['-t', '{host} process {inst} count', '-v', 'Threads/Processes'], + ps_pagefaults => ['-t', '{host} process {inst} pagefaults', '-v', 'Pagefaults/s'], + qtype => ['-t', 'QType {inst}', '-v', 'Queries/s'], + rcode => ['-t', 'RCode {inst}', '-v', 'Queries/s'], sensors => ['-t', '{host} sensor {inst}', '-v', '°Celsius'], swap => ['-t', '{host} swap usage', '-v', 'Bytes', '-b', '1024', '-l', '0'], temperature => ['-t', '{host} temperature {inst}', '-v', '°Celsius'], @@ -992,7 +1214,8 @@ our $GraphArgs = time_dispersion => ['-t', 'NTPd time dispersion ({inst})', '-v', 'Seconds'], traffic => ['-t', '{host} {inst} traffic', '-v', 'Bit/s'], users => ['-t', '{host} users', '-v', 'Users'], - voltage => ['-t', '{host} voltage', '-v', 'Volts'], + multimeter => ['-t', '{host} multimeter', '-v', 'Value'], + voltage => ['-t', '{host} voltage {inst}', '-v', 'Volts'], vs_threads => ['-t', '{host} threads', '-v', 'Threads'], vs_memory => ['-t', '{host} memory usage', '-v', 'Bytes'], vs_processes => ['-t', '{host} processes', '-v', 'Processes'], @@ -1004,6 +1227,10 @@ our $GraphMulti = cpu => \&output_graph_cpu, cpufreq => 1, disk => 1, + email => \&output_graph_email_count, + email_size => \&output_graph_email_size, + spam_score => 1, + spam_check => \&output_graph_spam_check, load => 0, mails => 0, memory => 0, @@ -1011,9 +1238,12 @@ our $GraphMulti = mysql_handler => \&output_graph_mysql_handler, partition => 1, ping => \&output_graph_ping, + qtype => \&output_graph_named_qtype, + rcode => \&output_graph_named_rcode, sensors => 1, traffic => 1, - users => 1 + users => 1, + multimeter => 1 }; our @Info; @@ -1189,6 +1419,114 @@ sub output_graph_ping return (@ret); } +sub output_graph_email_count +{ + my @inst = @_; + my @ret = (); + + my $c = scalar @inst; + + my @colors = get_n_colors ($c); + + for (my $i = 0; $i < $c; ++$i) { + push @ret, + "DEF:min_$i=$AbsDir/email-$inst[$i].rrd:count:MIN", + "DEF:avg_$i=$AbsDir/email-$inst[$i].rrd:count:AVERAGE", + "DEF:max_$i=$AbsDir/email-$inst[$i].rrd:count:MAX"; + } + + for (my $i = 0; $i < $c; ++$i) { + my $s = ""; + + if (length $inst[$i] > 10) { + $s = substr ($inst[$i], 0, 7) . '...'; + } + else { + $s = sprintf '%-10s', $inst[$i]; + } + + push @ret, + "LINE1:avg_$i#$colors[$i]:$s", + "GPRINT:min_$i:MIN:%4.1lf Min,", + "GPRINT:avg_$i:AVERAGE:%4.1lf Avg,", + "GPRINT:max_$i:MAX:%4.1lf Max,", + "GPRINT:avg_$i:LAST:%4.1lf Last\\l"; + } + return @ret; +} + +sub output_graph_email_size +{ + my @inst = @_; + my @ret = (); + + my $c = scalar @inst; + + my @colors = get_n_colors ($c); + + for (my $i = 0; $i < $c; ++$i) { + push @ret, + "DEF:min_$i=$AbsDir/email_size-$inst[$i].rrd:size:MIN", + "DEF:avg_$i=$AbsDir/email_size-$inst[$i].rrd:size:AVERAGE", + "DEF:max_$i=$AbsDir/email_size-$inst[$i].rrd:size:MAX"; + } + + for (my $i = 0; $i < $c; ++$i) { + my $s = ""; + + if (length $inst[$i] > 10) { + $s = substr ($inst[$i], 0, 7) . '...'; + } + else { + $s = sprintf '%-10s', $inst[$i]; + } + + push @ret, + "LINE1:avg_$i#$colors[$i]:$s", + "GPRINT:min_$i:MIN:%4.1lf Min,", + "GPRINT:avg_$i:AVERAGE:%4.1lf Avg,", + "GPRINT:max_$i:MAX:%4.1lf Max,", + "GPRINT:avg_$i:LAST:%4.1lf Last\\l"; + } + return @ret; +} + +sub output_graph_spam_check +{ + my @inst = sort @_; + my @ret = (); + + my $c = scalar @inst; + + my @colors = get_n_colors ($c); + + for (my $i = 0; $i < $c; ++$i) { + push @ret, + "DEF:min_$i=$AbsDir/spam_check-$inst[$i].rrd:hits:MIN", + "DEF:avg_$i=$AbsDir/spam_check-$inst[$i].rrd:hits:AVERAGE", + "DEF:max_$i=$AbsDir/spam_check-$inst[$i].rrd:hits:MAX"; + } + + for (my $i = 0; $i < $c; ++$i) { + my $s = ""; + + if (length $inst[$i] > 25) { + $s = substr ($inst[$i], 0, 22) . '...'; + } + else { + $s = sprintf '%-25s', $inst[$i]; + } + + push @ret, + "LINE1:avg_$i#$colors[$i]:$s", + "GPRINT:min_$i:MIN:%4.1lf Min,", + "GPRINT:avg_$i:AVERAGE:%4.1lf Avg,", + "GPRINT:max_$i:MAX:%4.1lf Max,", + "GPRINT:avg_$i:LAST:%4.1lf Last\\l"; + } + return @ret; +} + sub output_graph_mysql_commands { my @inst = @_; @@ -1275,6 +1613,93 @@ sub output_graph_mysql_handler return (@ret); } +sub output_graph_named_qtype +{ + my @inst = @_; + my @ret = (); + + die if (@inst < 2); + + my @colors = get_n_colors (scalar (@inst)); + + for (my $i = 0; $i < scalar (@inst); $i++) + { + my $inst = $inst[$i]; + push (@ret, + "DEF:avg_$i=$AbsDir/qtype-$inst.rrd:value:AVERAGE", + "DEF:min_$i=$AbsDir/qtype-$inst.rrd:value:MIN", + "DEF:max_$i=$AbsDir/qtype-$inst.rrd:value:MAX"); + } + + for (my $i = 0; $i < scalar (@inst); $i++) + { + my $inst = $inst[$i]; + my $color = $colors[$i]; + my $type = ($i == 0) ? 'AREA' : 'STACK'; + + if (length ($inst) > 5) + { + $inst = substr ($inst, 0, 5); + } + else + { + $inst = sprintf ('%-5s', $inst); + } + + push (@ret, + "$type:avg_$i#$color:$inst", + "GPRINT:min_$i:MIN:%9.3lf Min,", + "GPRINT:avg_$i:AVERAGE:%9.3lf Avg,", + "GPRINT:max_$i:MAX:%9.3lf Max,", + "GPRINT:avg_$i:LAST:%9.3lf Last\\l"); + } + + return (@ret); +} + +sub output_graph_named_rcode +{ + my @inst = @_; + my @ret = (); + + die if (@inst < 2); + + my @colors = get_n_colors (scalar (@inst)); + + for (my $i = 0; $i < scalar (@inst); $i++) + { + my $inst = $inst[$i]; + push (@ret, + "DEF:avg_$i=$AbsDir/rcode-$inst.rrd:value:AVERAGE", + "DEF:min_$i=$AbsDir/rcode-$inst.rrd:value:MIN", + "DEF:max_$i=$AbsDir/rcode-$inst.rrd:value:MAX"); + } + + for (my $i = 0; $i < scalar (@inst); $i++) + { + my $inst = $inst[$i]; + my $color = $colors[$i]; + my $type = ($i == 0) ? 'AREA' : 'STACK'; + + if (length ($inst) > 8) + { + $inst = substr ($inst, 0, 6) . '..'; + } + else + { + $inst = sprintf ('%-8s', $inst); + } + + push (@ret, + "$type:avg_$i#$color:$inst", + "GPRINT:min_$i:MIN:%9.3lf Min,", + "GPRINT:avg_$i:AVERAGE:%9.3lf Avg,", + "GPRINT:max_$i:MAX:%9.3lf Max,", + "GPRINT:avg_$i:LAST:%9.3lf Last\\l"); + } + + return (@ret); +} sub output_graph { die unless (defined ($GraphDefs->{$Type})); diff --git a/contrib/init.d-rh7 b/contrib/init.d-rh7 index 01b7213b..c7fe270d 100755 --- a/contrib/init.d-rh7 +++ b/contrib/init.d-rh7 @@ -42,6 +42,7 @@ case "$1" in ;; restart|reload) stop + sleep 1 start ;; condrestart) diff --git a/debian/control b/debian/control index 87d1f75b..f9f8f3c2 100644 --- a/debian/control +++ b/debian/control @@ -133,4 +133,3 @@ Description: statistics collection daemon (development files) . This package contains the development files needed to create your own plugins. - diff --git a/src/Makefile.am b/src/Makefile.am index 46d1a69c..6e7e3395 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,6 +12,7 @@ sbin_PROGRAMS = collectd collectd_SOURCES = collectd.c collectd.h \ utils_debug.c utils_debug.h \ utils_mount.c utils_mount.h \ + utils_ignorelist.c utils_ignorelist.h \ common.c common.h \ network.c network.h \ plugin.c plugin.h \ @@ -155,6 +156,31 @@ collectd_LDADD += "-dlopen" disk.la collectd_DEPENDENCIES += disk.la endif +if BUILD_MODULE_DNS +pkglib_LTLIBRARIES += dns.la +dns_la_SOURCES = dns.c utils_dns.c utils_dns.h +dns_la_LDFLAGS = -module -avoid-version +if BUILD_WITH_LIBPCAP +dns_la_LDFLAGS += -lpcap +endif +if BUILD_WITH_LIBPTHREAD +dns_la_LDFLAGS += -lpthread +endif +collectd_LDADD += "-dlopen" dns.la +collectd_DEPENDENCIES += dns.la +endif + +if BUILD_MODULE_EMAIL +pkglib_LTLIBRARIES += email.la +email_la_SOURCES = email.c +email_la_LDFLAGS = -module -avoid-version +if BUILD_WITH_LIBPTHREAD +email_la_LDFLAGS += -lpthread +endif +collectd_LDADD += "-dlopen" email.la +collectd_DEPENDENCIES += email.la +endif + #if BUILD_MODULE_QUOTA #pkglib_LTLIBRARIES += quota.la #quota_la_SOURCES = quota_plugin.c quota_plugin.h @@ -217,6 +243,14 @@ memory_la_LDFLAGS += -lstatgrab endif endif +if BUILD_MODULE_MULTIMETER +pkglib_LTLIBRARIES += multimeter.la +multimeter_la_SOURCES = multimeter.c +multimeter_la_LDFLAGS = -module -avoid-version +collectd_LDADD += "-dlopen" multimeter.la +collectd_DEPENDENCIES += multimeter.la +endif + if BUILD_MODULE_MYSQL pkglib_LTLIBRARIES += mysql.la mysql_la_SOURCES = mysql.c @@ -389,7 +423,7 @@ install-exec-hook: $(mkinstalldirs) $(DESTDIR)$(sysconfdir) if test -e $(DESTDIR)$(sysconfdir)/collectd.conf; \ then \ - $(INSTALL_DATA) collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf.pkg-orig; \ + $(INSTALL) -m 0640 collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf.pkg-orig; \ else \ - $(INSTALL_DATA) collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf; \ + $(INSTALL) -m 0640 collectd.conf $(DESTDIR)$(sysconfdir)/collectd.conf; \ fi diff --git a/src/apache.c b/src/apache.c index 156ad5dc..530481be 100644 --- a/src/apache.c +++ b/src/apache.c @@ -42,7 +42,8 @@ static char *cacert = NULL; #if HAVE_LIBCURL static CURL *curl = NULL; -static char apache_buffer[4096]; +#define ABUFFER_SIZE 16384 +static char apache_buffer[ABUFFER_SIZE]; static int apache_buffer_len = 0; static char apache_curl_error[CURL_ERROR_SIZE]; #endif /* HAVE_LIBCURL */ @@ -89,9 +90,9 @@ static size_t apache_curl_callback (void *buf, size_t size, size_t nmemb, void * { size_t len = size * nmemb; - if ((apache_buffer_len + len) >= 4096) + if ((apache_buffer_len + len) >= ABUFFER_SIZE) { - len = 4095 - apache_buffer_len; + len = (ABUFFER_SIZE - 1) - apache_buffer_len; } if (len <= 0) diff --git a/src/apcups.c b/src/apcups.c index f7486f5f..cd64426a 100644 --- a/src/apcups.c +++ b/src/apcups.c @@ -301,7 +301,7 @@ static int apc_query_server (char *host, int port, double value; static int sockfd = -1; - static unsigned int complain = 0; + static complain_t compl; #if APCMAIN # define PRINT_VALUE(name, val) printf(" Found property: name = %s; value = %f;\n", name, val) @@ -313,19 +313,14 @@ static int apc_query_server (char *host, int port, { if ((sockfd = net_open (host, NULL, port)) < 0) { - /* Complain once every six hours. */ - int complain_step = 21600 / atoi (COLLECTD_STEP); - - if ((complain % complain_step) == 0) - syslog (LOG_ERR, "apcups plugin: Connecting to the apcupsd failed."); - complain++; - + plugin_complain (LOG_ERR, &compl, "apcups plugin: " + "Connecting to the apcupsd failed."); return (-1); } - else if (complain > 1) + else { - syslog (LOG_NOTICE, "apcups plugin: Connection re-established to the apcupsd."); - complain = 0; + plugin_relief (LOG_NOTICE, &compl, "apcups plugin: " + "Connection re-established to the apcupsd."); } } diff --git a/src/collectd.c b/src/collectd.c index c9385d95..a93cc286 100644 --- a/src/collectd.c +++ b/src/collectd.c @@ -123,6 +123,8 @@ static void exit_usage (char *name) " General:\n" " -C Configuration file.\n" " Default: "CONFIGFILE"\n" + " -P PID-file.\n" + " Default: "PIDFILE"\n" #if COLLECT_DAEMON " -f Don't fork to the background.\n" #endif @@ -191,7 +193,7 @@ static int start_client (void) curtime = time (NULL); /* Issue all plugins */ - plugin_read_all (); + plugin_read_all (&loop); if (gettimeofday (&tv_now, NULL) < 0) { @@ -205,7 +207,7 @@ static int start_client (void) continue; } - while (nanosleep (&ts_wait, &ts_wait) == -1) + while ((loop == 0) && (nanosleep (&ts_wait, &ts_wait) == -1)) { if (errno != EINTR) { @@ -292,7 +294,7 @@ int main (int argc, char **argv) char *configfile = CONFIGFILE; #if COLLECT_DAEMON struct sigaction sigChldAction; - char *pidfile = PIDFILE; + char *pidfile = NULL; pid_t pid; int daemonize = 1; #endif @@ -316,7 +318,7 @@ int main (int argc, char **argv) c = getopt (argc, argv, "hC:" #if COLLECT_DAEMON - "f" + "fP:" #endif ); @@ -329,6 +331,9 @@ int main (int argc, char **argv) configfile = optarg; break; #if COLLECT_DAEMON + case 'P': + pidfile = optarg; + break; case 'f': daemonize = 0; break; @@ -379,7 +384,8 @@ int main (int argc, char **argv) sigChldAction.sa_handler = SIG_IGN; sigaction (SIGCHLD, &sigChldAction, NULL); - if ((pidfile = cf_get_option ("PIDFile", PIDFILE)) == NULL) + if ((pidfile == NULL) + && ((pidfile = cf_get_option ("PIDFile", PIDFILE)) == NULL)) { fprintf (stderr, "Cannot obtain pidfile. This shoud not happen. Ever."); return (1); @@ -449,6 +455,8 @@ int main (int argc, char **argv) #endif start_client (); + plugin_shutdown_all (); + #if COLLECT_DEBUG if (logfile != NULL) DBG_STOPFILE("debug file closed."); diff --git a/src/collectd.conf.in b/src/collectd.conf.in index d733a6fa..b73278fc 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -28,9 +28,11 @@ @BUILD_MODULE_CPUFREQ_TRUE@LoadPlugin cpufreq @BUILD_MODULE_DF_TRUE@LoadPlugin df @BUILD_MODULE_DISK_TRUE@LoadPlugin disk +@BUILD_MODULE_DNS_TRUE@LoadPlugin dns @BUILD_MODULE_HDDTEMP_TRUE@LoadPlugin hddtemp @BUILD_MODULE_LOAD_TRUE@LoadPlugin load @BUILD_MODULE_MEMORY_TRUE@LoadPlugin memory +@BUILD_MODULE_MULTIMETER_TRUE@LoadPlugin multimeter @BUILD_MODULE_MYSQL_TRUE@LoadPlugin mysql @BUILD_MODULE_NFS_TRUE@LoadPlugin nfs @BUILD_MODULE_NTPD_TRUE@LoadPlugin ntpd @@ -57,6 +59,11 @@ # Port 3551 # +# +# Interface eth0 +# IgnoreSource 192.168.0.1 +# + # # Host 127.0.0.1 # Port 7634 @@ -78,6 +85,27 @@ # Host host.foo.bar # +# +# Process name +# + +## There are two different naming schemes for the sensors plugin, to preserver +## backwards compatibility. The `extended naming' is prefered, but not the +## default for this reason. See `collectd.conf(5)' for more information. +# +# ExtendedSensorNaming false +# Sensor it8712-temp1 +# Sensor it8712-fan3 +# Sensor it8712-in8 +## -- OR -- +# ExtendedSensorNaming true +# Sensor it8712-isa-0290/temperature-temp1 +# Sensor it8712-isa-0290/fanspeed-fan3 +# Sensor it8712-isa-0290/voltage-in8 +# +# IgnoreSelected false +# + # # Interface eth0 # IgnoreSelected false diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index f3e25856..aa4799a4 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -49,7 +49,8 @@ will be mostly useless. The names of the plugins are listed in L. =item B I Sets where to write the PID file to. This file is overwritten when it exists -and deleted when the program ist stopped. Available in B. +and deleted when the program ist stopped. Some init-scripts might override this +setting using the B<-P> commandline option. Available in B. =item B I @@ -159,6 +160,72 @@ TCP-Port to connect to. Defaults to B<3551>. =back +=head2 Plugin C + +=over 4 + +=item B I + +Select partitions based on the devicename. + +=item B I + +Select partitions based on the mountpoint. + +=item B I + +Select partitions based on the filesystem type. + +=item B I|I + +Invert the selection: If set to true, all partitions B the ones that +match any one of the criteria are collected. By default only selected +partitions are collected if a selection is made. If no selection is conifured +at all, B partitions are selected. + +=back + +=head2 Plugin C + +=over 4 + +=item B I + +The dns plugin uses B to capture dns traffic and analyses it. This +option sets the interface that should be used. If this option is not set, or +set to "any", the plugin will try to get packets from B interfaces. This +may not work on certain platforms, such as MacEOSEX. + +=item B I + +Ignore packets that originate from this address. + +=back + +=head2 Plugin C + +=over 4 + +=item B I + +If running as root change the group of the UNIX-socket after it has been +created. Defaults to B. + +=item B I + +Change the file permissions of the UNIX-socket after it has been created. The +permissions must be given as a numeric, octal value as you would pass to +L. Defaults to B<0770>. + +=item B I + +Sets the maximum number of connections that can be handled in parallel. Since +this many threads will be started immediately setting this to a very high +value will waste valuable resources. Defaults to B<5> and will be forced to be +at most B<16384> to prevent typos and dumb mistakes. + +=back + =head2 Plugin C =over 4 @@ -225,6 +292,68 @@ Sets the Time-To-Live of generated ICMP packets. =back +=head2 Plugin C + +=over 4 + +=item B I|I + +If set to I this option switches on the extended sensors and RRD-files +naming. This option exists to preserve backwards compatibility. It is +recommended that you set this option to I. The default is I to +maintain compatibility only. + +Sensors get names like I (e.g. +I) and RRD files are therefore stored in a +standalone directory inside the B directory and get names like +I (e.g. +I). + +The B option breaks the compatibility with previous +sensors and RRD files naming and the place where RRDs are stored. If you turn +it on, the plugin will create new RRD files in a standalone directory inside +the B directory and without previous history. You can rename ``old'' +RRD-files to preserve already collected statistics, because the file layout +hasn't changed. If you have two chips of the same type, you need to use +B in order to collect information from both chips. + +If not set or set to I, the extended naming is not active. Sensors get +names like I (e.g. I) and RRD files are stored in the +main B directory and get names like I (e.g. +I). You simply continue using the plugin the old way +and additionally also getting data for newly added sensors in this mode. + +=item B I or B I + +Both option modes select the name of the sensor which you want to collect. +The naming scheme is dependent on the state of the B +option (see previous option). Both option modes can also deselect the +sensor according to the B option (see below). + +For example the option "B I" will cause the collectd +to gather data for the voltage sensor I of the I chip in case +of the B option is set to I. + +And likewise the option "B I" will +cause the collectd to gather data for the voltage sensor I of the I +on the isa bus at the address 0290 in case of the B +option set to I. + +=item B I|I + +If no configuration if given, the B-plugin will collect data from +all sensors. This may not be practical, especially for uninteresting sensors. +Thus, you can use the B-option to pick the sensors you're +interested in. Sometimes, however, it's easier/prefered to collect all +sensors I a few ones. This option enables you to +do that: By setting B to I the effect of +B is inversed: All selected sensors are ignored and all +other sensors are collected. + +back + +=back + =head2 Plugin C =over 4 diff --git a/src/collectd.h b/src/collectd.h index 4d75db48..2ce0a155 100644 --- a/src/collectd.h +++ b/src/collectd.h @@ -200,6 +200,10 @@ #define MODE_LOCAL 0x04 #define MODE_LOG 0x08 +#ifndef COLLECTD_GRP_NAME +# define COLLECTD_GRP_NAME "collectd" +#endif + #ifndef COLLECTD_STEP # define COLLECTD_STEP "10" #endif diff --git a/src/collectd.pod b/src/collectd.pod index 26185eea..f5ed782c 100644 --- a/src/collectd.pod +++ b/src/collectd.pod @@ -40,6 +40,14 @@ Disk and partition usage/throughput (I) =item +DNS traffic (I) + +=item + +Email usage (I) + +=item + Harddisk temperatures (I) =item @@ -110,6 +118,13 @@ Specify an alternative config file. This is the place to go when you wish to change B's behavior. The path may be relative to the current working directory. +=item B<-P> Ipid-fileE> + +Specify an alternative pid file. This overwrites any settings in the config +file. This is thought for init-scripts that require the PID-file in a certain +directory to work correctly. For everyday-usage use the B +config-option. + =item B<-f> Don't fork to the background. I will also B close standard file @@ -173,6 +188,27 @@ not exist make sure B (L) or a similar tool is installed and an "cpu governor" (that's kernel module) is loaded. +=head2 email + +This plugin collects data indirectly by providing a UNIX socket that external +programs can connect to. A simple line based protocol is used to communicate +with the plugin: + +E-Mail type (e.g. "ham", "spam", "virus", ...) and size (bytes): + + e:: + +Spam score: + + s: + +Successful spam checks (e.g. "BAYES_99", "SUBJECT_DRUG_GAP_C", ...): + + c:[,,...] + +Each line is limited to 256 characters (including the newline character). +Longer lines will be ignored. + =head2 mysql Requires B to be installed. It connects to the database when @@ -319,6 +355,14 @@ The DS'es depend on the module creating the RRD files: DS:wcount:COUNTER:HEARTBEAT:0:U DS:wbytes:COUNTER:HEARTBEAT:0:U +=item E-Mail count (FtypeE>.rrd>) + + DS:count:GAUGE:HEARTBEAT:0:U + +=item E-Mail size (FtypeE>.rrd>) + + DS:size:GAUGE:HEARTBEAT:0:U + =item HDD Temperature (FmajorE>-IminorE>.rrd>) DS:value:GAUGE:HEARTBEAT:U:U @@ -414,15 +458,31 @@ The DS'es depend on the module creating the RRD files: DS:paging:GAUGE:HEARTBEAT:0:65535 DS:blocked:GAUGE:HEARTBEAT:0:65535 -=item lm_sensors (FchipE>-IfeatureE>.rrd>) +=item lm_sensors fanspeed sensor (FinstE>.rrd> or FchipE>/fanspeed-IinstE>.rrd>) + + DS:value:GAUGE:HEARTBEAT:U:U + +=item lm_sensors temperature sensor (FinstE>.rrd> or FchipE>/temperature-IinstE>.rrd>) DS:value:GAUGE:HEARTBEAT:U:U +=item lm_sensors voltage sensor (FinstE>.rrd> or FchipE>/voltage-IinstE>.rrd>) + + DS:voltage:GAUGE:HEARTBEAT:U:U + =item Serial port traffic (FnumE>.rrd>) DS:incoming:COUNTER:HEARTBEAT:0:U DS:outgoing:COUNTER:HEARTBEAT:0:U +=item Spam score (F) + + DS:score:GAUGE:HEARTBEAT:0:U + +=item Spam checks (FtypeE>.rrd>) + + DS:hits:GAUGE:HEARTBEAT:0:U + =item Swap usage (F) DS:used:GAUGE:HEARTBEAT:0:1099511627776 diff --git a/src/cpu.c b/src/cpu.c index ca782941..b087d665 100644 --- a/src/cpu.c +++ b/src/cpu.c @@ -312,12 +312,19 @@ static void cpu_read (void) char *fields[9]; int numfields; + static complain_t complain_obj; + if ((fh = fopen ("/proc/stat", "r")) == NULL) { - syslog (LOG_WARNING, "cpu: fopen: %s", strerror (errno)); + plugin_complain (LOG_ERR, &complain_obj, "cpu plugin: " + "fopen (/proc/stat) failed: %s", + strerror (errno)); return; } + plugin_relief (LOG_NOTICE, &complain_obj, "cpu plugin: " + "fopen (/proc/stat) succeeded."); + while (fgets (buf, BUFSIZE, fh) != NULL) { if (strncmp (buf, "cpu", 3)) @@ -384,14 +391,21 @@ static void cpu_read (void) long cpuinfo[CPUSTATES]; size_t cpuinfo_size; + static complain_t complain_obj; + cpuinfo_size = sizeof (cpuinfo); if (sysctlbyname("kern.cp_time", &cpuinfo, &cpuinfo_size, NULL, 0) < 0) { - syslog (LOG_WARNING, "cpu: sysctlbyname: %s", strerror (errno)); + plugin_complain (LOG_ERR, &complain_obj, "cpu plugin: " + "sysctlbyname failed: %s.", + strerror (errno)); return; } + plugin_relief (LOG_NOTICE, &complain_obj, "cpu plugin: " + "sysctlbyname succeeded."); + cpuinfo[CP_SYS] += cpuinfo[CP_INTR]; /* FIXME: Instance is always `0' */ diff --git a/src/df.c b/src/df.c index b88ed7da..d327164a 100644 --- a/src/df.c +++ b/src/df.c @@ -23,7 +23,9 @@ #include "collectd.h" #include "common.h" #include "plugin.h" +#include "configfile.h" #include "utils_mount.h" +#include "utils_ignorelist.h" #define MODULE_NAME "df" @@ -57,13 +59,78 @@ static char *ds_def[] = }; static int ds_num = 2; +static char *config_keys[] = +{ + "Device", + "MountPoint", + "FSType", + "IgnoreSelected", + NULL +}; +static int config_keys_num = 4; + +static ignorelist_t *il_device = NULL; +static ignorelist_t *il_mountpoint = NULL; +static ignorelist_t *il_fstype = NULL; + #define BUFSIZE 512 static void df_init (void) { + if (il_device == NULL) + il_device = ignorelist_create (1); + if (il_mountpoint == NULL) + il_mountpoint = ignorelist_create (1); + if (il_fstype == NULL) + il_fstype = ignorelist_create (1); + return; } +static int df_config (char *key, char *value) +{ + df_init (); + + if (strcasecmp (key, "Device") == 0) + { + if (ignorelist_add (il_device, value)) + return (1); + return (0); + } + else if (strcasecmp (key, "MountPoint") == 0) + { + if (ignorelist_add (il_mountpoint, value)) + return (1); + return (0); + } + else if (strcasecmp (key, "FSType") == 0) + { + if (ignorelist_add (il_fstype, value)) + return (1); + return (0); + } + else if (strcasecmp (key, "IgnoreSelected") == 0) + { + if ((strcasecmp (value, "True") == 0) + || (strcasecmp (value, "Yes") == 0) + || (strcasecmp (value, "On") == 0)) + { + ignorelist_set_invert (il_device, 0); + ignorelist_set_invert (il_mountpoint, 0); + ignorelist_set_invert (il_fstype, 0); + } + else + { + ignorelist_set_invert (il_device, 1); + ignorelist_set_invert (il_mountpoint, 1); + ignorelist_set_invert (il_fstype, 1); + } + return (0); + } + + return (-1); +} + static void df_write (char *host, char *inst, char *val) { char file[BUFSIZE]; @@ -143,6 +210,16 @@ static void df_read (void) mnt_name[i] = '-'; } + if (ignorelist_match (il_device, + (mnt_ptr->spec_device != NULL) + ? mnt_ptr->spec_device + : mnt_ptr->device)) + continue; + if (ignorelist_match (il_mountpoint, mnt_ptr->dir)) + continue; + if (ignorelist_match (il_fstype, mnt_ptr->type)) + continue; + df_submit (mnt_name, df_used, df_free); } @@ -155,6 +232,7 @@ static void df_read (void) void module_register (void) { plugin_register (MODULE_NAME, df_init, df_read, df_write); + cf_register (MODULE_NAME, df_config, config_keys, config_keys_num); } #undef BUFSIZE diff --git a/src/disk.c b/src/disk.c index ca3531eb..c809fdb6 100644 --- a/src/disk.c +++ b/src/disk.c @@ -315,14 +315,22 @@ static void disk_read (void) int disk_minor; char disk_name[64]; + static complain_t complain_obj; + /* Get the list of all disk objects. */ if (IOServiceGetMatchingServices (io_master_port, IOServiceMatching (kIOBlockStorageDriverClass), &disk_list) != kIOReturnSuccess) { - syslog (LOG_ERR, "disk-plugin: IOServiceGetMatchingServices failed."); + plugin_complain (LOG_ERR, &complain_obj, "disk plugin: " + "IOServiceGetMatchingServices failed."); return; } + else if (complain_obj.interval != 0) + { + plugin_relief (LOG_NOTICE, &complain_obj, "disk plugin: " + "IOServiceGetMatchingServices succeeded."); + } while ((disk = IOIteratorNext (disk_list)) != 0) { @@ -460,15 +468,22 @@ static void disk_read (void) diskstats_t *ds, *pre_ds; + static complain_t complain_obj; + if ((fh = fopen ("/proc/diskstats", "r")) == NULL) { if ((fh = fopen ("/proc/partitions", "r")) == NULL) + { + plugin_complain (LOG_ERR, &complain_obj, "disk plugin: Failed to open /proc/{diskstats,partitions}."); return; + } /* Kernel is 2.4.* */ fieldshift = 1; } + plugin_relief (LOG_NOTICE, &complain_obj, "disk plugin: Succeeded to open /proc/{diskstats,partitions}."); + while (fgets (buffer, 1024, fh) != NULL) { numfields = strsplit (buffer, fields, 32); diff --git a/src/dns.c b/src/dns.c new file mode 100644 index 00000000..cd068fb3 --- /dev/null +++ b/src/dns.c @@ -0,0 +1,530 @@ +/** + * collectd - src/dns.c + * Copyright (C) 2006 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 "configfile.h" +#include "utils_debug.h" +#include "utils_dns.h" + +#define MODULE_NAME "dns" + +#if HAVE_LIBPCAP && HAVE_LIBPTHREAD +# include +# include +# include +# define DNS_HAVE_READ 1 +#else +# define DNS_HAVE_READ 0 +#endif + +/* + * Private data types + */ +#if DNS_HAVE_READ +struct counter_list_s +{ + unsigned int key; + unsigned int value; + struct counter_list_s *next; +}; +typedef struct counter_list_s counter_list_t; +#endif + +/* + * Private variables + */ +static char *traffic_file = "dns/dns_traffic.rrd"; +static char *qtype_file = "dns/qtype-%s.rrd"; +static char *opcode_file = "dns/opcode-%s.rrd"; +static char *rcode_file = "dns/rcode-%s.rrd"; + +static char *traffic_ds_def[] = +{ + /* Limit to 1GBit/s */ + "DS:queries:COUNTER:"COLLECTD_HEARTBEAT":0:125000000", + "DS:responses:COUNTER:"COLLECTD_HEARTBEAT":0:125000000", + NULL +}; +static int traffic_ds_num = 2; + +static char *qtype_ds_def[] = +{ + "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535", + NULL +}; +static int qtype_ds_num = 1; + +static char *opcode_ds_def[] = +{ + "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535", + NULL +}; +static int opcode_ds_num = 1; + +static char *rcode_ds_def[] = +{ + "DS:value:COUNTER:"COLLECTD_HEARTBEAT":0:65535", + NULL +}; +static int rcode_ds_num = 1; + +#if DNS_HAVE_READ +static char *config_keys[] = +{ + "Interface", + "IgnoreSource", + NULL +}; +static int config_keys_num = 2; + +#define PCAP_SNAPLEN 1460 +static char *pcap_device = NULL; + +static unsigned int tr_queries; +static unsigned int tr_responses; +static counter_list_t *qtype_list; +static counter_list_t *opcode_list; +static counter_list_t *rcode_list; + +static pthread_t listen_thread; +static int listen_thread_init = 0; +/* The `traffic' mutex if for `tr_queries' and `tr_responses' */ +static pthread_mutex_t traffic_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t qtype_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t opcode_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t rcode_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif /* DNS_HAVE_READ */ + +/* + * Private functions + */ +#if DNS_HAVE_READ +static counter_list_t *counter_list_search (counter_list_t **list, unsigned int key) +{ + counter_list_t *entry; + + DBG ("counter_list_search (list = %p, key = %u)", + (void *) *list, key); + + for (entry = *list; entry != NULL; entry = entry->next) + if (entry->key == key) + break; + + DBG ("return (%p)", (void *) entry); + return (entry); +} + +static counter_list_t *counter_list_create (counter_list_t **list, + unsigned int key, unsigned int value) +{ + counter_list_t *entry; + + DBG ("counter_list_create (list = %p, key = %u, value = %u)", + (void *) *list, key, value); + + entry = (counter_list_t *) malloc (sizeof (counter_list_t)); + if (entry == NULL) + return (NULL); + + memset (entry, 0, sizeof (counter_list_t)); + entry->key = key; + entry->value = value; + + if (*list == NULL) + { + *list = entry; + } + else + { + counter_list_t *last; + + last = *list; + while (last->next != NULL) + last = last->next; + + last->next = entry; + } + + DBG ("return (%p)", (void *) entry); + return (entry); +} + +static void counter_list_add (counter_list_t **list, + unsigned int key, unsigned int increment) +{ + counter_list_t *entry; + + DBG ("counter_list_add (list = %p, key = %u, increment = %u)", + (void *) *list, key, increment); + + entry = counter_list_search (list, key); + + if (entry != NULL) + { + entry->value += increment; + } + else + { + counter_list_create (list, key, increment); + } + DBG ("return ()"); +} + +static int dns_config (char *key, char *value) +{ + if (strcasecmp (key, "Interface") == 0) + { + if (pcap_device != NULL) + free (pcap_device); + if ((pcap_device = strdup (value)) == NULL) + return (1); + } + else if (strcasecmp (key, "IgnoreSource") == 0) + { + if (value != NULL) + ignore_list_add_name (value); + } + else + { + return (-1); + } + + return (0); +} + +static void dns_child_callback (const rfc1035_header_t *dns) +{ + if (dns->qr == 0) + { + /* This is a query */ + pthread_mutex_lock (&traffic_mutex); + tr_queries += dns->length; + pthread_mutex_unlock (&traffic_mutex); + + pthread_mutex_lock (&qtype_mutex); + counter_list_add (&qtype_list, dns->qtype, 1); + pthread_mutex_unlock (&qtype_mutex); + } + else + { + /* This is a reply */ + pthread_mutex_lock (&traffic_mutex); + tr_responses += dns->length; + pthread_mutex_unlock (&traffic_mutex); + + pthread_mutex_lock (&rcode_mutex); + counter_list_add (&rcode_list, dns->rcode, 1); + pthread_mutex_unlock (&rcode_mutex); + } + + /* FIXME: Are queries, replies or both interesting? */ + pthread_mutex_lock (&opcode_mutex); + counter_list_add (&opcode_list, dns->opcode, 1); + pthread_mutex_unlock (&opcode_mutex); +} + +static void *dns_child_loop (void *dummy) +{ + pcap_t *pcap_obj; + char pcap_error[PCAP_ERRBUF_SIZE]; + struct bpf_program fp; + + int status; + + /* Don't block any signals */ + { + sigset_t sigmask; + sigemptyset (&sigmask); + pthread_sigmask (SIG_SETMASK, &sigmask, NULL); + } + + /* Passing `pcap_device == NULL' is okay and the same as passign "any" */ + DBG ("Creating PCAP object.."); + pcap_obj = pcap_open_live (pcap_device, + PCAP_SNAPLEN, + 0 /* Not promiscuous */, + atoi (COLLECTD_STEP), + pcap_error); + if (pcap_obj == NULL) + { + syslog (LOG_ERR, "dns plugin: Opening interface `%s' " + "failed: %s", + (pcap_device != NULL) ? pcap_device : "any", + pcap_error); + return (NULL); + } + + memset (&fp, 0, sizeof (fp)); + if (pcap_compile (pcap_obj, &fp, "udp port 53", 1, 0) < 0) + { + DBG ("pcap_compile failed"); + syslog (LOG_ERR, "dns plugin: pcap_compile failed"); + return (NULL); + } + if (pcap_setfilter (pcap_obj, &fp) < 0) + { + DBG ("pcap_setfilter failed"); + syslog (LOG_ERR, "dns plugin: pcap_setfilter failed"); + return (NULL); + } + + DBG ("PCAP object created."); + + dnstop_set_pcap_obj (pcap_obj); + dnstop_set_callback (dns_child_callback); + + status = pcap_loop (pcap_obj, + -1 /* loop forever */, + handle_pcap /* callback */, + NULL /* Whatever this means.. */); + if (status < 0) + syslog (LOG_ERR, "dns plugin: Listener thread is exiting " + "abnormally: %s", pcap_geterr (pcap_obj)); + + DBG ("child is exiting"); + + pcap_close (pcap_obj); + listen_thread_init = 0; + pthread_exit (NULL); + + return (NULL); +} /* static void dns_child_loop (void) */ +#endif /* DNS_HAVE_READ */ + +static void dns_init (void) +{ +#if DNS_HAVE_READ + /* clean up an old thread */ + int status; + + pthread_mutex_lock (&traffic_mutex); + tr_queries = 0; + tr_responses = 0; + pthread_mutex_unlock (&traffic_mutex); + + if (listen_thread_init != 0) + return; + + status = pthread_create (&listen_thread, NULL, dns_child_loop, + (void *) 0); + if (status != 0) + { + syslog (LOG_ERR, "dns plugin: pthread_create failed: %s", + strerror (status)); + return; + } + + listen_thread_init = 1; +#endif /* DNS_HAVE_READ */ +} + +static void traffic_write (char *host, char *inst, char *val) +{ + rrd_update_file (host, traffic_file, val, + traffic_ds_def, traffic_ds_num); +} + +static void qtype_write (char *host, char *inst, char *val) +{ + char file[512]; + int status; + + status = snprintf (file, 512, qtype_file, inst); + if (status < 1) + return; + else if (status >= 512) + return; + + rrd_update_file (host, file, val, qtype_ds_def, qtype_ds_num); +} + +static void rcode_write (char *host, char *inst, char *val) +{ + char file[512]; + int status; + + status = snprintf (file, 512, rcode_file, inst); + if (status < 1) + return; + else if (status >= 512) + return; + + rrd_update_file (host, file, val, rcode_ds_def, rcode_ds_num); +} + +static void opcode_write (char *host, char *inst, char *val) +{ + char file[512]; + int status; + + status = snprintf (file, 512, opcode_file, inst); + if (status < 1) + return; + else if (status >= 512) + return; + + rrd_update_file (host, file, val, opcode_ds_def, opcode_ds_num); +} + +#if DNS_HAVE_READ +static void traffic_submit (unsigned int queries, unsigned int replies) +{ + char buffer[64]; + int status; + + status = snprintf (buffer, 64, "N:%u:%u", queries, replies); + if ((status < 1) || (status >= 64)) + return; + + plugin_submit ("dns_traffic", "-", buffer); +} + +static void qtype_submit (int qtype, unsigned int counter) +{ + char inst[32]; + char buffer[32]; + int status; + + strncpy (inst, qtype_str (qtype), 32); + inst[31] = '\0'; + + status = snprintf (buffer, 32, "N:%u", counter); + if ((status < 1) || (status >= 32)) + return; + + plugin_submit ("dns_qtype", inst, buffer); +} + +static void rcode_submit (int rcode, unsigned int counter) +{ + char inst[32]; + char buffer[32]; + int status; + + strncpy (inst, rcode_str (rcode), 32); + inst[31] = '\0'; + + status = snprintf (buffer, 32, "N:%u", counter); + if ((status < 1) || (status >= 32)) + return; + + plugin_submit ("dns_rcode", inst, buffer); +} + +static void opcode_submit (int opcode, unsigned int counter) +{ + char inst[32]; + char buffer[32]; + int status; + + strncpy (inst, opcode_str (opcode), 32); + inst[31] = '\0'; + + status = snprintf (buffer, 32, "N:%u", counter); + if ((status < 1) || (status >= 32)) + return; + + plugin_submit ("dns_opcode", inst, buffer); +} + +static void dns_read (void) +{ + unsigned int keys[T_MAX]; + unsigned int values[T_MAX]; + int len; + int i; + + counter_list_t *ptr; + + pthread_mutex_lock (&traffic_mutex); + values[0] = tr_queries; + values[1] = tr_responses; + pthread_mutex_unlock (&traffic_mutex); + traffic_submit (values[0], values[1]); + + pthread_mutex_lock (&qtype_mutex); + for (ptr = qtype_list, len = 0; + (ptr != NULL) && (len < T_MAX); + ptr = ptr->next, len++) + { + keys[len] = ptr->key; + values[len] = ptr->value; + } + pthread_mutex_unlock (&qtype_mutex); + + for (i = 0; i < len; i++) + { + DBG ("qtype = %u; counter = %u;", keys[i], values[i]); + qtype_submit (keys[i], values[i]); + } + + pthread_mutex_lock (&opcode_mutex); + for (ptr = opcode_list, len = 0; + (ptr != NULL) && (len < T_MAX); + ptr = ptr->next, len++) + { + keys[len] = ptr->key; + values[len] = ptr->value; + } + pthread_mutex_unlock (&opcode_mutex); + + for (i = 0; i < len; i++) + { + DBG ("opcode = %u; counter = %u;", keys[i], values[i]); + opcode_submit (keys[i], values[i]); + } + + pthread_mutex_lock (&rcode_mutex); + for (ptr = rcode_list, len = 0; + (ptr != NULL) && (len < T_MAX); + ptr = ptr->next, len++) + { + keys[len] = ptr->key; + values[len] = ptr->value; + } + pthread_mutex_unlock (&rcode_mutex); + + for (i = 0; i < len; i++) + { + DBG ("rcode = %u; counter = %u;", keys[i], values[i]); + rcode_submit (keys[i], values[i]); + } +} +#else /* if !DNS_HAVE_READ */ +# define dns_read NULL +#endif + +void module_register (void) +{ + plugin_register (MODULE_NAME, dns_init, dns_read, NULL); + plugin_register ("dns_traffic", NULL, NULL, traffic_write); + plugin_register ("dns_qtype", NULL, NULL, qtype_write); + plugin_register ("dns_rcode", NULL, NULL, rcode_write); + plugin_register ("dns_opcode", NULL, NULL, opcode_write); +#if DNS_HAVE_READ + cf_register (MODULE_NAME, dns_config, config_keys, config_keys_num); +#endif +} + +#undef MODULE_NAME diff --git a/src/email.c b/src/email.c new file mode 100644 index 00000000..9bcd5bfb --- /dev/null +++ b/src/email.c @@ -0,0 +1,842 @@ +/** + * collectd - src/email.c + * Copyright (C) 2006 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; 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 + * + * Author: + * Sebastian Harl + **/ + +/* + * This plugin communicates with a spam filter, a virus scanner or similar + * software using a UNIX socket and a very simple protocol: + * + * e-mail type (e.g. ham, spam, virus, ...) and size + * e:: + * + * spam score + * s: + * + * successful spam checks + * c:[,,...] + */ + +#include "collectd.h" +#include "common.h" +#include "plugin.h" + +#include "configfile.h" + +#if HAVE_LIBPTHREAD +# include +# define EMAIL_HAVE_READ 1 +#else +# define EMAIL_HAVE_READ 0 +#endif + +#if HAVE_SYS_SELECT_H +# include +#endif /* HAVE_SYS_SELECT_H */ + +#if HAVE_SYS_SOCKET_H +# include +#endif /* HAVE_SYS_SOCKET_H */ + +/* *sigh* glibc does not define UNIX_PATH_MAX in sys/un.h ... */ +#if HAVE_LINUX_UN_H +# include +#elif HAVE_SYS_UN_H +# include +#endif /* HAVE_LINUX_UN_H | HAVE_SYS_UN_H */ + +/* some systems (e.g. Darwin) seem to not define UNIX_PATH_MAX at all */ +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path) +#endif /* UNIX_PATH_MAX */ + +#if HAVE_GRP_H +# include +#endif /* HAVE_GRP_H */ + +#define MODULE_NAME "email" + +/* 256 bytes ought to be enough for anybody ;-) */ +#define BUFSIZE 256 + +#ifndef COLLECTD_SOCKET_PREFIX +# define COLLECTD_SOCKET_PREFIX "/tmp/.collectd-" +#endif /* COLLECTD_SOCKET_PREFIX */ + +#define SOCK_PATH COLLECTD_SOCKET_PREFIX"email" +#define MAX_CONNS 5 +#define MAX_CONNS_LIMIT 16384 + +#define log_err(...) syslog (LOG_ERR, MODULE_NAME": "__VA_ARGS__) +#define log_warn(...) syslog (LOG_WARNING, MODULE_NAME": "__VA_ARGS__) + +/* + * Private data structures + */ +#if EMAIL_HAVE_READ +/* linked list of email and check types */ +typedef struct type { + char *name; + int value; + struct type *next; +} type_t; + +typedef struct { + type_t *head; + type_t *tail; +} type_list_t; + +/* collector thread control information */ +typedef struct collector { + pthread_t thread; + + /* socket descriptor of the current/last connection */ + int 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' */ + + struct conn *next; +} conn_t; + +typedef struct { + conn_t *head; + conn_t *tail; +} conn_list_t; +#endif /* EMAIL_HAVE_READ */ + +/* + * Private variables + */ +#if EMAIL_HAVE_READ +/* valid configuration file keys */ +static char *config_keys[] = +{ + "SocketGroup", + "SocketPerms", + "MaxConns", + NULL +}; +static int config_keys_num = 3; + +/* socket configuration */ +static char *sock_group = COLLECTD_GRP_NAME; +static int sock_perms = S_IRWXU | S_IRWXG; +static int max_conns = MAX_CONNS; + +/* state of the plugin */ +static int disabled = 0; + +/* thread managing "client" connections */ +static pthread_t connector; +static int connector_socket; + +/* tell the collector threads that a new connection is available */ +static pthread_cond_t conn_available = PTHREAD_COND_INITIALIZER; + +/* connections that are waiting to be processed */ +static pthread_mutex_t conns_mutex = PTHREAD_MUTEX_INITIALIZER; +static conn_list_t conns; + +/* tell the connector thread that a collector is available */ +static pthread_cond_t collector_available = PTHREAD_COND_INITIALIZER; + +/* collector threads */ +static collector_t **collectors; + +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 pthread_mutex_t size_mutex = PTHREAD_MUTEX_INITIALIZER; +static type_list_t size; + +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; +#endif /* EMAIL_HAVE_READ */ + +#define COUNT_FILE "email/email-%s.rrd" +static char *count_ds_def[] = +{ + "DS:count:GAUGE:"COLLECTD_HEARTBEAT":0:U", + NULL +}; +static int count_ds_num = 1; + +#define SIZE_FILE "email/email_size-%s.rrd" +static char *size_ds_def[] = +{ + "DS:size:GAUGE:"COLLECTD_HEARTBEAT":0:U", + NULL +}; +static int size_ds_num = 1; + +#define SCORE_FILE "email/spam_score.rrd" +static char *score_ds_def[] = +{ + "DS:score:GAUGE:"COLLECTD_HEARTBEAT":U:U", + NULL +}; +static int score_ds_num = 1; + +#define CHECK_FILE "email/spam_check-%s.rrd" +static char *check_ds_def[] = +{ + "DS:hits:GAUGE:"COLLECTD_HEARTBEAT":0:U", + NULL +}; +static int check_ds_num = 1; + +#if EMAIL_HAVE_READ +static int email_config (char *key, char *value) +{ + if (0 == strcasecmp (key, "SocketGroup")) { + sock_group = sstrdup (value); + } + else if (0 == strcasecmp (key, "SocketPerms")) { + /* the user is responsible for providing reasonable values */ + sock_perms = (int)strtol (value, NULL, 8); + } + else if (0 == strcasecmp (key, "MaxConns")) { + long int tmp = strtol (value, NULL, 0); + + if (tmp < 1) { + fprintf (stderr, "email plugin: `MaxConns' was set to invalid " + "value %li, will use default %i.\n", + tmp, MAX_CONNS); + max_conns = MAX_CONNS; + } + else if (tmp > MAX_CONNS_LIMIT) { + fprintf (stderr, "email plugin: `MaxConns' was set to invalid " + "value %li, will use hardcoded limit %i.\n", + tmp, MAX_CONNS_LIMIT); + max_conns = MAX_CONNS_LIMIT; + } + else { + max_conns = (int)tmp; + } + } + else { + return -1; + } + return 0; +} /* static int email_config (char *, char *) */ + +/* Increment the value of the given name in the given list by incr. */ +static void type_list_incr (type_list_t *list, char *name, int incr) +{ + if (NULL == list->head) { + list->head = (type_t *)smalloc (sizeof (type_t)); + + list->head->name = sstrdup (name); + list->head->value = incr; + list->head->next = NULL; + + list->tail = list->head; + } + else { + type_t *ptr; + + for (ptr = list->head; NULL != ptr; ptr = ptr->next) { + if (0 == strcmp (name, ptr->name)) + break; + } + + if (NULL == ptr) { + list->tail->next = (type_t *)smalloc (sizeof (type_t)); + list->tail = list->tail->next; + + list->tail->name = sstrdup (name); + list->tail->value = incr; + list->tail->next = NULL; + } + else { + ptr->value += 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)) { + log_err ("select() failed: %s", strerror (errno)); + 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) { + log_err ("read() failed: %s", strerror (errno)); + 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)) { + log_err ("select() failed: %s", strerror (errno)); + 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) { + log_err ("read() failed: %s", strerror (errno)); + 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); + + while (1) { + int loop = 1; + + conn_t *connection; + + pthread_mutex_lock (&conns_mutex); + + while (NULL == conns.head) { + pthread_cond_wait (&conn_available, &conns_mutex); + } + + connection = conns.head; + conns.head = conns.head->next; + + if (NULL == conns.head) { + conns.tail = NULL; + } + + this->socket = connection->socket; + + pthread_mutex_unlock (&conns_mutex); + + connection->buffer = buffer; + connection->idx = 0; + connection->length = 0; + + { /* put the socket in non-blocking mode */ + int flags = 0; + + errno = 0; + if (-1 == fcntl (connection->socket, F_GETFL, &flags)) { + log_err ("fcntl() failed: %s", strerror (errno)); + loop = 0; + } + + errno = 0; + if (-1 == fcntl (connection->socket, F_SETFL, flags | O_NONBLOCK)) { + log_err ("fcntl() failed: %s", strerror (errno)); + loop = 0; + } + } + + while (loop) { + char *line = read_line (connection); + + if (NULL == line) { + loop = 0; + break; + } + + if (':' != line[1]) { + log_err ("syntax error in line '%s'", line); + continue; + } + + if ('e' == line[0]) { /* e:: */ + char *ptr = NULL; + char *type = strtok_r (line + 2, ":", &ptr); + char *tmp = strtok_r (NULL, ":", &ptr); + int bytes = 0; + + if (NULL == tmp) { + log_err ("syntax error in line '%s'", line); + continue; + } + + bytes = atoi (tmp); + + pthread_mutex_lock (&count_mutex); + type_list_incr (&count, type, 1); + pthread_mutex_unlock (&count_mutex); + + pthread_mutex_lock (&size_mutex); + type_list_incr (&size, type, bytes); + pthread_mutex_unlock (&size_mutex); + } + else if ('s' == line[0]) { /* s: */ + pthread_mutex_lock (&score_mutex); + score = (score * (double)score_count + atof (line + 2)) + / (double)(score_count + 1); + ++score_count; + pthread_mutex_unlock (&score_mutex); + } + else if ('c' == line[0]) { /* c:[,,...] */ + char *ptr = NULL; + char *type = strtok_r (line + 2, ",", &ptr); + + do { + pthread_mutex_lock (&check_mutex); + type_list_incr (&check, type, 1); + pthread_mutex_unlock (&check_mutex); + } while (NULL != (type = strtok_r (NULL, ",", &ptr))); + } + else { + log_err ("unknown type '%c'", line[0]); + } + } /* while (loop) */ + + close (connection->socket); + + free (connection); + + pthread_mutex_lock (&available_mutex); + ++available_collectors; + pthread_mutex_unlock (&available_mutex); + + pthread_cond_signal (&collector_available); + } /* while (1) */ + + free (buffer); + pthread_exit ((void *)0); +} /* static void *collect (void *) */ + +static void *open_connection (void *arg) +{ + struct sockaddr_un addr; + + /* create UNIX socket */ + errno = 0; + if (-1 == (connector_socket = socket (PF_UNIX, SOCK_STREAM, 0))) { + disabled = 1; + log_err ("socket() failed: %s", strerror (errno)); + pthread_exit ((void *)1); + } + + addr.sun_family = AF_UNIX; + + strncpy (addr.sun_path, SOCK_PATH, (size_t)(UNIX_PATH_MAX - 1)); + addr.sun_path[UNIX_PATH_MAX - 1] = '\0'; + unlink (addr.sun_path); + + errno = 0; + if (-1 == bind (connector_socket, (struct sockaddr *)&addr, + offsetof (struct sockaddr_un, sun_path) + + strlen(addr.sun_path))) { + disabled = 1; + log_err ("bind() failed: %s", strerror (errno)); + pthread_exit ((void *)1); + } + + errno = 0; + if (-1 == listen (connector_socket, 5)) { + disabled = 1; + log_err ("listen() failed: %s", strerror (errno)); + pthread_exit ((void *)1); + } + + if ((uid_t)0 == geteuid ()) { + struct group *grp; + + errno = 0; + if (NULL != (grp = getgrnam (sock_group))) { + errno = 0; + if (0 != chown (SOCK_PATH, (uid_t)-1, grp->gr_gid)) { + log_warn ("chown() failed: %s", strerror (errno)); + } + } + else { + log_warn ("getgrnam() failed: %s", strerror (errno)); + } + } + else { + log_warn ("not running as root"); + } + + errno = 0; + if (0 != chmod (SOCK_PATH, sock_perms)) { + log_warn ("chmod() failed: %s", strerror (errno)); + } + + { /* initialize collector threads */ + int i = 0; + int err = 0; + + pthread_attr_t ptattr; + + conns.head = NULL; + conns.tail = NULL; + + pthread_attr_init (&ptattr); + pthread_attr_setdetachstate (&ptattr, PTHREAD_CREATE_DETACHED); + + available_collectors = max_conns; + + collectors = + (collector_t **)smalloc (max_conns * sizeof (collector_t *)); + + for (i = 0; i < max_conns; ++i) { + collectors[i] = (collector_t *)smalloc (sizeof (collector_t)); + collectors[i]->socket = 0; + + if (0 != (err = pthread_create (&collectors[i]->thread, &ptattr, + collect, collectors[i]))) { + log_err ("pthread_create() failed: %s", strerror (err)); + } + } + + pthread_attr_destroy (&ptattr); + } + + while (1) { + int remote = 0; + + conn_t *connection; + + pthread_mutex_lock (&available_mutex); + + while (0 == available_collectors) { + pthread_cond_wait (&collector_available, &available_mutex); + } + + --available_collectors; + + pthread_mutex_unlock (&available_mutex); + + do { + errno = 0; + if (-1 == (remote = accept (connector_socket, NULL, NULL))) { + if (EINTR != errno) { + disabled = 1; + log_err ("accept() failed: %s", strerror (errno)); + pthread_exit ((void *)1); + } + } + } while (EINTR == errno); + + connection = (conn_t *)smalloc (sizeof (conn_t)); + + connection->socket = remote; + connection->next = NULL; + + pthread_mutex_lock (&conns_mutex); + + if (NULL == conns.head) { + conns.head = connection; + conns.tail = connection; + } + else { + conns.tail->next = connection; + conns.tail = conns.tail->next; + } + + pthread_mutex_unlock (&conns_mutex); + + pthread_cond_signal (&conn_available); + } + pthread_exit ((void *)0); +} /* static void *open_connection (void *) */ +#endif /* EMAIL_HAVE_READ */ + +static void email_init (void) +{ +#if EMAIL_HAVE_READ + int err = 0; + + if (0 != (err = pthread_create (&connector, NULL, + open_connection, NULL))) { + disabled = 1; + log_err ("pthread_create() failed: %s", strerror (err)); + return; + } +#endif /* EMAIL_HAVE_READ */ + return; +} /* static void email_init (void) */ + +#if EMAIL_HAVE_READ +static void email_shutdown (void) +{ + int i = 0; + + if (disabled) + return; + + pthread_kill (connector, SIGTERM); + close (connector_socket); + + /* don't allow any more connections to be processed */ + pthread_mutex_lock (&conns_mutex); + + for (i = 0; i < max_conns; ++i) { + pthread_kill (collectors[i]->thread, SIGTERM); + close (collectors[i]->socket); + } + + pthread_mutex_unlock (&conns_mutex); + + unlink (SOCK_PATH); + return; +} /* static void email_shutdown (void) */ +#endif /* EMAIL_HAVE_READ */ + +static void count_write (char *host, char *inst, char *val) +{ + char file[BUFSIZE] = ""; + int len = 0; + + len = snprintf (file, BUFSIZE, COUNT_FILE, inst); + if ((len < 0) || (len >= BUFSIZE)) + return; + + rrd_update_file (host, file, val, count_ds_def, count_ds_num); + return; +} /* static void email_write (char *host, char *inst, char *val) */ + +static void size_write (char *host, char *inst, char *val) +{ + char file[BUFSIZE] = ""; + int len = 0; + + len = snprintf (file, BUFSIZE, SIZE_FILE, inst); + if ((len < 0) || (len >= BUFSIZE)) + return; + + rrd_update_file (host, file, val, size_ds_def, size_ds_num); + return; +} /* static void size_write (char *host, char *inst, char *val) */ + +static void score_write (char *host, char *inst, char *val) +{ + rrd_update_file (host, SCORE_FILE, val, score_ds_def, score_ds_num); + return; +} /* static void score_write (char *host, char *inst, char *val) */ + +static void check_write (char *host, char *inst, char *val) +{ + char file[BUFSIZE] = ""; + int len = 0; + + len = snprintf (file, BUFSIZE, CHECK_FILE, inst); + if ((len < 0) || (len >= BUFSIZE)) + return; + + rrd_update_file (host, file, val, check_ds_def, check_ds_num); + return; +} /* static void check_write (char *host, char *inst, char *val) */ + +#if EMAIL_HAVE_READ +static void type_submit (char *plugin, char *inst, int value) +{ + char buf[BUFSIZE] = ""; + int len = 0; + + len = snprintf (buf, BUFSIZE, "%u:%i", (unsigned int)curtime, value); + if ((len < 0) || (len >= BUFSIZE)) + return; + + plugin_submit (plugin, inst, buf); + return; +} /* static void type_submit (char *, char *, int) */ + +static void score_submit (double value) +{ + char buf[BUFSIZE] = ""; + int len = 0; + + len = snprintf (buf, BUFSIZE, "%u:%.2f", (unsigned int)curtime, value); + if ((len < 0) || (len >= BUFSIZE)) + return; + + plugin_submit ("email_spam_score", NULL, buf); + return; +} /* static void score_submit (double) */ + +static void email_read (void) +{ + type_t *ptr; + + if (disabled) + return; + + pthread_mutex_lock (&count_mutex); + + for (ptr = count.head; NULL != ptr; ptr = ptr->next) { + type_submit ("email_count", ptr->name, ptr->value); + ptr->value = 0; + } + + pthread_mutex_unlock (&count_mutex); + + pthread_mutex_lock (&size_mutex); + + for (ptr = size.head; NULL != ptr; ptr = ptr->next) { + type_submit ("email_size", ptr->name, ptr->value); + ptr->value = 0; + } + + pthread_mutex_unlock (&size_mutex); + + pthread_mutex_lock (&score_mutex); + + score_submit (score); + score = 0.0; + score_count = 0; + + pthread_mutex_unlock (&score_mutex); + + pthread_mutex_lock (&check_mutex); + + for (ptr = check.head; NULL != ptr; ptr = ptr->next) { + type_submit ("email_spam_check", ptr->name, ptr->value); + ptr->value = 0; + } + + pthread_mutex_unlock (&check_mutex); + return; +} /* static void read (void) */ +#else /* if !EMAIL_HAVE_READ */ +# define email_read NULL +#endif + +void module_register (void) +{ + plugin_register (MODULE_NAME, email_init, email_read, NULL); + plugin_register ("email_count", NULL, NULL, count_write); + plugin_register ("email_size", NULL, NULL, size_write); + plugin_register ("email_spam_score", NULL, NULL, score_write); + plugin_register ("email_spam_check", NULL, NULL, check_write); +#if EMAIL_HAVE_READ + plugin_register_shutdown (MODULE_NAME, email_shutdown); + cf_register (MODULE_NAME, email_config, config_keys, config_keys_num); +#endif /* EMAIL_HAVE_READ */ + return; +} /* void module_register (void) */ + +/* vim: set sw=4 ts=4 tw=78 noexpandtab : */ + diff --git a/src/liboping/liboping.c b/src/liboping/liboping.c index debd432f..13bcc920 100644 --- a/src/liboping/liboping.c +++ b/src/liboping/liboping.c @@ -537,7 +537,17 @@ static ssize_t ping_sendto (pingobj_t *obj, pinghost_t *ph, (struct sockaddr *) ph->addr, ph->addrlen); if (ret < 0) + { +#if defined(EHOSTUNREACH) + if (errno == EHOSTUNREACH) + return (0); +#endif +#if defined(ENETUNREACH) + if (errno == ENETUNREACH) + return (0); +#endif ping_set_error (obj, "sendto", strerror (errno)); + } return (ret); } diff --git a/src/multimeter.c b/src/multimeter.c new file mode 100644 index 00000000..317745b2 --- /dev/null +++ b/src/multimeter.c @@ -0,0 +1,234 @@ +/** + * collectd - src/multimeter.c + * Copyright (C) 2005,2006 Peter Holik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * 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: + * Peter Holik + * + * Used multimeter: Metex M-4650CR + * + **/ + +#include +#include +#include +#include "collectd.h" +#include "common.h" +#include "plugin.h" + +#define MODULE_NAME "multimeter" + +static char *multimeter_file = "multimeter.rrd"; + +static char *ds_def[] = +{ + "DS:value:GAUGE:"COLLECTD_HEARTBEAT":U:U", + NULL +}; +static int ds_num = 1; + +static int fd = -1; + +static int multimeter_timeval_sub (struct timeval *tv1, struct timeval *tv2, + struct timeval *res) +{ + if ((tv1->tv_sec < tv2->tv_sec) || + ((tv1->tv_sec == tv2->tv_sec) && (tv1->tv_usec < tv2->tv_usec))) + return (-1); + + res->tv_sec = tv1->tv_sec - tv2->tv_sec; + res->tv_usec = tv1->tv_usec - tv2->tv_usec; + + assert ((res->tv_sec > 0) || ((res->tv_sec == 0) && (res->tv_usec > 0))); + + while (res->tv_usec < 0) + { + res->tv_usec += 1000000; + res->tv_sec--; + } + return (0); +} +#define LINE_LENGTH 14 +static int multimeter_read_value(double *value) +{ + int retry = 3; /* sometimes we receive garbadge */ + + do + { + struct timeval time_end; + + tcflush(fd, TCIFLUSH); + + if (gettimeofday (&time_end, NULL) < 0) + { + syslog (LOG_ERR, MODULE_NAME": gettimeofday failed: %s", + strerror (errno)); + return (-1); + } + time_end.tv_sec++; + + while (1) + { + char buf[LINE_LENGTH]; + char *range; + int status; + fd_set rfds; + struct timeval timeout; + struct timeval time_now; + + write(fd, "D", 1); + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + if (gettimeofday (&time_now, NULL) < 0) + { + syslog (LOG_ERR, MODULE_NAME": gettimeofday failed: %s", + strerror (errno)); + return (-1); + } + if (multimeter_timeval_sub (&time_end, &time_now, &timeout) == -1) + break; + + status = select(fd+1, &rfds, NULL, NULL, &timeout); + + if (status > 0) /* usually we succeed */ + { + status = read(fd, buf, LINE_LENGTH); + + if ((status < 0) && ((errno == EAGAIN) || (errno == EINTR))) + continue; + + /* Format: "DC 00.000mV \r" */ + if (status > 0 && status == LINE_LENGTH) + { + *value = strtod(buf + 2, &range); + + if ( range > (buf + 6) ) + { + range = buf + 9; + + switch ( *range ) + { + case 'p': *value *= 1.0E-12; break; + case 'n': *value *= 1.0E-9; break; + case 'u': *value *= 1.0E-6; break; + case 'm': *value *= 1.0E-3; break; + case 'k': *value *= 1.0E3; break; + case 'M': *value *= 1.0E6; break; + case 'G': *value *= 1.0E9; break; + } + } + else + return (-1); /* Overflow */ + + return (0); /* value received */ + } + else break; + } + else if (!status) /* Timeout */ + { + break; + } + else if ((status == -1) && ((errno == EAGAIN) || (errno == EINTR))) + { + continue; + } + else /* status == -1 */ + { + syslog (LOG_ERR, MODULE_NAME": select failed: %s", + strerror (errno)); + break; + } + } + } while (--retry); + + return (-2); /* no value received */ +} + +static void multimeter_init (void) +{ + int i; + char device[] = "/dev/ttyS "; + + for (i = 0; i < 10; i++) + { + device[strlen(device)-1] = i + '0'; + + if ((fd = open(device, O_RDWR | O_NOCTTY)) > 0) + { + struct termios tios; + int rts = TIOCM_RTS; + double value; + + tios.c_cflag = B1200 | CS7 | CSTOPB | CREAD | CLOCAL; + tios.c_iflag = IGNBRK | IGNPAR; + tios.c_oflag = 0; + tios.c_lflag = 0; + tios.c_cc[VTIME] = 3; + tios.c_cc[VMIN] = LINE_LENGTH; + + tcflush(fd, TCIFLUSH); + tcsetattr(fd, TCSANOW, &tios); + ioctl(fd, TIOCMBIC, &rts); + + if (multimeter_read_value(&value) < -1) + { + close(fd); + fd = -1; + } + else + { + syslog (LOG_INFO, MODULE_NAME" found (%s)", device); + return; + } + } + } + syslog (LOG_ERR, MODULE_NAME" not found"); +} +#undef LINE_LENGTH + +static void multimeter_write (char *host, char *inst, char *val) +{ + rrd_update_file (host, multimeter_file, val, ds_def, ds_num); +} +#define BUFSIZE 128 +static void multimeter_submit (double *value) +{ + char buf[BUFSIZE]; + + if (snprintf (buf, BUFSIZE, "%u:%f", (unsigned int) curtime, *value) >= BUFSIZE) + return; + + plugin_submit (MODULE_NAME, NULL, buf); +} +#undef BUFSIZE + +static void multimeter_read (void) +{ + double value; + + if (fd > -1 && !(multimeter_read_value(&value))) + multimeter_submit (&value); +} + +void module_register (void) +{ + plugin_register (MODULE_NAME, multimeter_init, multimeter_read, multimeter_write); +} + +#undef MODULE_NAME diff --git a/src/plugin.c b/src/plugin.c index 2f521570..46a1c617 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -34,6 +34,7 @@ typedef struct plugin void (*init) (void); void (*read) (void); void (*write) (char *host, char *inst, char *val); + void (*shutdown) (void); struct plugin *next; } plugin_t; @@ -276,16 +277,29 @@ void plugin_init_all (void) /* * Call `read' on all plugins (if given) */ -void plugin_read_all (void) +void plugin_read_all (const int *loop) { plugin_t *p; - for (p = first_plugin; p != NULL; p = p->next) + for (p = first_plugin; (*loop == 0) && (p != NULL); p = p->next) if (p->read != NULL) (*p->read) (); } /* + * Call `shutdown' on all plugins (if given) + */ +void plugin_shutdown_all (void) +{ + plugin_t *p; + + for (p = first_plugin; NULL != p; p = p->next) + if (NULL != p->shutdown) + (*p->shutdown) (); + return; +} + +/* * Add plugin to the linked list of registered plugins. */ void plugin_register (char *type, @@ -317,11 +331,27 @@ void plugin_register (char *type, p->read = read; p->write = write; + p->shutdown = NULL; + p->next = first_plugin; first_plugin = p; } /* + * Register the shutdown function (optional). + */ +int plugin_register_shutdown (char *type, void (*shutdown) (void)) +{ + plugin_t *p = plugin_search (type); + + if (NULL == p) + return -1; + + p->shutdown = shutdown; + return 0; +} + +/* * Send received data back to the plugin/module which will append DS * definitions and pass it on to ``rrd_update_file''. */ @@ -350,3 +380,54 @@ void plugin_submit (char *type, char *inst, char *val) else plugin_write (NULL, type, inst, val); } + +void plugin_complain (int level, complain_t *c, const char *format, ...) +{ + char message[512]; + va_list ap; + int step; + + if (c->delay > 0) + { + c->delay--; + return; + } + + step = atoi (COLLECTD_STEP); + assert (step > 0); + + if (c->interval < step) + c->interval = step; + else + c->interval *= 2; + + if (c->interval > 86400) + c->interval = 86400; + + c->delay = c->interval / step; + + va_start (ap, format); + vsnprintf (message, 512, format, ap); + message[511] = '\0'; + va_end (ap); + + syslog (level, message); +} + +void plugin_relief (int level, complain_t *c, const char *format, ...) +{ + char message[512]; + va_list ap; + + if (c->interval == 0) + return; + + c->interval = 0; + + va_start (ap, format); + vsnprintf (message, 512, format, ap); + message[511] = '\0'; + va_end (ap); + + syslog (level, message); +} diff --git a/src/plugin.h b/src/plugin.h index e1303c76..45e9b494 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -1,3 +1,6 @@ +#ifndef PLUGIN_H +#define PLUGIN_H + /** * collectd - src/plugin.h * Copyright (C) 2005,2006 Florian octo Forster @@ -20,8 +23,14 @@ * Florian octo Forster **/ -#ifndef PLUGIN_H -#define PLUGIN_H +/* + * + */ +typedef struct complain_s +{ + unsigned int interval; /* how long we wait for reporting this error again */ + unsigned int delay; /* how many more iterations we still need to wait */ +} complain_t; /* * NAME @@ -90,13 +99,17 @@ int plugin_load (const char *type); int plugin_load_all (char *dir); void plugin_init_all (void); -void plugin_read_all (void); +void plugin_read_all (const int *loop); + +void plugin_shutdown_all (void); void plugin_register (char *type, void (*init) (void), void (*read) (void), void (*write) (char *, char *, char *)); +int plugin_register_shutdown (char *, void (*) (void)); + /* * NAME * plugin_write @@ -117,4 +130,8 @@ void plugin_write (char *host, char *type, char *inst, char *val); void plugin_submit (char *type, char *inst, char *val); + +void plugin_complain (int level, complain_t *c, const char *format, ...); +void plugin_relief (int level, complain_t *c, const char *format, ...); + #endif /* PLUGIN_H */ diff --git a/src/processes.c b/src/processes.c index dba29eff..c8f30168 100644 --- a/src/processes.c +++ b/src/processes.c @@ -26,47 +26,62 @@ #include "common.h" #include "plugin.h" #include "utils_debug.h" +#include "configfile.h" /* Include header files for the mach system, if they exist.. */ -#if HAVE_MACH_MACH_INIT_H -# include -#endif -#if HAVE_MACH_HOST_PRIV_H -# include -#endif -#if HAVE_MACH_MACH_ERROR_H -# include -#endif -#if HAVE_MACH_MACH_HOST_H -# include -#endif -#if HAVE_MACH_MACH_PORT_H -# include -#endif -#if HAVE_MACH_MACH_TYPES_H -# include -#endif -#if HAVE_MACH_MESSAGE_H -# include -#endif -#if HAVE_MACH_PROCESSOR_SET_H -# include -#endif -#if HAVE_MACH_TASK_H -# include -#endif -#if HAVE_MACH_THREAD_ACT_H -# include -#endif -#if HAVE_MACH_VM_REGION_H -# include -#endif -#if HAVE_MACH_VM_MAP_H -# include -#endif -#if HAVE_MACH_VM_PROT_H -# include -#endif +#if HAVE_THREAD_INFO +# if HAVE_MACH_MACH_INIT_H +# include +# endif +# if HAVE_MACH_HOST_PRIV_H +# include +# endif +# if HAVE_MACH_MACH_ERROR_H +# include +# endif +# if HAVE_MACH_MACH_HOST_H +# include +# endif +# if HAVE_MACH_MACH_PORT_H +# include +# endif +# if HAVE_MACH_MACH_TYPES_H +# include +# endif +# if HAVE_MACH_MESSAGE_H +# include +# endif +# if HAVE_MACH_PROCESSOR_SET_H +# include +# endif +# if HAVE_MACH_TASK_H +# include +# endif +# if HAVE_MACH_THREAD_ACT_H +# include +# endif +# if HAVE_MACH_VM_REGION_H +# include +# endif +# if HAVE_MACH_VM_MAP_H +# include +# endif +# if HAVE_MACH_VM_PROT_H +# include +# endif +# if HAVE_SYS_SYSCTL_H +# include +# endif +/* #endif HAVE_THREAD_INFO */ + +#elif KERNEL_LINUX +# if HAVE_LINUX_CONFIG_H +# include +# endif +# ifndef CONFIG_HZ +# define CONFIG_HZ 100 +# endif +#endif /* KERNEL_LINUX */ #define MODULE_NAME "processes" @@ -78,9 +93,8 @@ #define BUFSIZE 256 -static char *ps_file = "processes.rrd"; - -static char *ds_def[] = +static char *processes_file = "processes.rrd"; +static char *processes_ds_def[] = { "DS:running:GAUGE:"COLLECTD_HEARTBEAT":0:65535", "DS:sleeping:GAUGE:"COLLECTD_HEARTBEAT":0:65535", @@ -90,7 +104,95 @@ static char *ds_def[] = "DS:blocked:GAUGE:"COLLECTD_HEARTBEAT":0:65535", NULL }; -static int ds_num = 6; +static int processes_ds_num = 6; + +static char *ps_rss_file = "processes/ps_rss-%s.rrd"; +static char *ps_rss_ds_def[] = +{ + /* max = 2^63 - 1 */ + "DS:byte:GAUGE:"COLLECTD_HEARTBEAT":0:9223372036854775807", + NULL +}; +static int ps_rss_ds_num = 1; + +static char *ps_cputime_file = "processes/ps_cputime-%s.rrd"; +static char *ps_cputime_ds_def[] = +{ + /* 1 second in user-mode per second ought to be enough.. */ + "DS:user:COUNTER:"COLLECTD_HEARTBEAT":0:1000000", + "DS:syst:COUNTER:"COLLECTD_HEARTBEAT":0:1000000", + NULL +}; +static int ps_cputime_ds_num = 2; + +static char *ps_count_file = "processes/ps_count-%s.rrd"; +static char *ps_count_ds_def[] = +{ + "DS:processes:GAUGE:"COLLECTD_HEARTBEAT":0:65535", + "DS:threads:GAUGE:"COLLECTD_HEARTBEAT":0:65535", + NULL +}; +static int ps_count_ds_num = 2; + +static char *ps_pagefaults_file = "processes/ps_pagefaults-%s.rrd"; +static char *ps_pagefaults_ds_def[] = +{ + /* max = 2^63 - 1 */ + "DS:minflt:COUNTER:"COLLECTD_HEARTBEAT":0:9223372036854775807", + "DS:majflt:COUNTER:"COLLECTD_HEARTBEAT":0:9223372036854775807", + NULL +}; +static int ps_pagefaults_ds_num = 2; + +static char *config_keys[] = +{ + "Process", + NULL +}; +static int config_keys_num = 1; + +typedef struct procstat_entry_s +{ + unsigned long id; + unsigned long age; + + unsigned long num_proc; + unsigned long num_lwp; + unsigned long vmem_rss; + + unsigned long vmem_minflt; + unsigned long vmem_majflt; + unsigned long vmem_minflt_counter; + unsigned long vmem_majflt_counter; + + unsigned long cpu_user; + unsigned long cpu_system; + unsigned long cpu_user_counter; + unsigned long cpu_system_counter; + + struct procstat_entry_s *next; +} procstat_entry_t; + +#define PROCSTAT_NAME_LEN 256 +typedef struct procstat +{ + char name[PROCSTAT_NAME_LEN]; + + unsigned long num_proc; + unsigned long num_lwp; + unsigned long vmem_rss; + + unsigned long vmem_minflt_counter; + unsigned long vmem_majflt_counter; + + unsigned long cpu_user_counter; + unsigned long cpu_system_counter; + + struct procstat *next; + struct procstat_entry_s *instances; +} procstat_t; + +static procstat_t *list_head_g = NULL; #if HAVE_THREAD_INFO static mach_port_t port_host_self; @@ -101,9 +203,222 @@ static mach_msg_type_number_t pset_list_len; /* #endif HAVE_THREAD_INFO */ #elif KERNEL_LINUX -/* No global variables */ +static long pagesize_g; #endif /* KERNEL_LINUX */ +#if HAVE_THREAD_INFO | KERNEL_LINUX +static void ps_list_register (const char *name) +{ + procstat_t *new; + procstat_t *ptr; + + if ((new = (procstat_t *) malloc (sizeof (procstat_t))) == NULL) + return; + memset (new, 0, sizeof (procstat_t)); + strncpy (new->name, name, PROCSTAT_NAME_LEN); + + for (ptr = list_head_g; ptr != NULL; ptr = ptr->next) + { + if (strcmp (ptr->name, name) == 0) + return; + if (ptr->next == NULL) + break; + } + + if (ptr == NULL) + list_head_g = new; + else + ptr->next = new; +} + +static procstat_t *ps_list_search (const char *name) +{ + procstat_t *ptr; + + for (ptr = list_head_g; ptr != NULL; ptr = ptr->next) + if (strcmp (ptr->name, name) == 0) + break; + + return (ptr); +} + +static void ps_list_add (const char *name, procstat_entry_t *entry) +{ + procstat_t *ps; + procstat_entry_t *pse; + + if (entry->id == 0) + return; + + if ((ps = ps_list_search (name)) == NULL) + return; + + for (pse = ps->instances; pse != NULL; pse = pse->next) + if ((pse->id == entry->id) || (pse->next == NULL)) + break; + + if ((pse == NULL) || (pse->id != entry->id)) + { + procstat_entry_t *new; + + new = (procstat_entry_t *) malloc (sizeof (procstat_entry_t)); + if (new == NULL) + return; + memset (new, 0, sizeof (procstat_entry_t)); + new->id = entry->id; + + if (pse == NULL) + ps->instances = new; + else + pse->next = new; + + pse = new; + } + + pse->age = 0; + pse->num_proc = entry->num_proc; + pse->num_lwp = entry->num_lwp; + pse->vmem_rss = entry->vmem_rss; + + ps->num_proc += pse->num_proc; + ps->num_lwp += pse->num_lwp; + ps->vmem_rss += pse->vmem_rss; + + if ((entry->vmem_minflt_counter == 0) + && (entry->vmem_majflt_counter == 0)) + { + pse->vmem_minflt_counter += entry->vmem_minflt; + pse->vmem_minflt = entry->vmem_minflt; + + pse->vmem_majflt_counter += entry->vmem_majflt; + pse->vmem_majflt = entry->vmem_majflt; + } + else + { + if (entry->vmem_minflt_counter < pse->vmem_minflt_counter) + { + pse->vmem_minflt = entry->vmem_minflt_counter + + (ULONG_MAX - pse->vmem_minflt_counter); + } + else + { + pse->vmem_minflt = entry->vmem_minflt_counter - pse->vmem_minflt_counter; + } + pse->vmem_minflt_counter = entry->vmem_minflt_counter; + + if (entry->vmem_majflt_counter < pse->vmem_majflt_counter) + { + pse->vmem_majflt = entry->vmem_majflt_counter + + (ULONG_MAX - pse->vmem_majflt_counter); + } + else + { + pse->vmem_majflt = entry->vmem_majflt_counter - pse->vmem_majflt_counter; + } + pse->vmem_majflt_counter = entry->vmem_majflt_counter; + } + + ps->vmem_minflt_counter += pse->vmem_minflt; + ps->vmem_majflt_counter += pse->vmem_majflt; + + if ((entry->cpu_user_counter == 0) + && (entry->cpu_system_counter == 0)) + { + pse->cpu_user_counter += entry->cpu_user; + pse->cpu_user = entry->cpu_user; + + pse->cpu_system_counter += entry->cpu_system; + pse->cpu_system = entry->cpu_system; + } + else + { + if (entry->cpu_user_counter < pse->cpu_user_counter) + { + pse->cpu_user = entry->cpu_user_counter + + (ULONG_MAX - pse->cpu_user_counter); + } + else + { + pse->cpu_user = entry->cpu_user_counter - pse->cpu_user_counter; + } + pse->cpu_user_counter = entry->cpu_user_counter; + + if (entry->cpu_system_counter < pse->cpu_system_counter) + { + pse->cpu_system = entry->cpu_system_counter + + (ULONG_MAX - pse->cpu_system_counter); + } + else + { + pse->cpu_system = entry->cpu_system_counter - pse->cpu_system_counter; + } + pse->cpu_system_counter = entry->cpu_system_counter; + } + + ps->cpu_user_counter += pse->cpu_user; + ps->cpu_system_counter += pse->cpu_system; +} + +static void ps_list_reset (void) +{ + procstat_t *ps; + procstat_entry_t *pse; + procstat_entry_t *pse_prev; + + for (ps = list_head_g; ps != NULL; ps = ps->next) + { + ps->num_proc = 0; + ps->num_lwp = 0; + ps->vmem_rss = 0; + + pse_prev = NULL; + pse = ps->instances; + while (pse != NULL) + { + if (pse->age > 10) + { + DBG ("Removing this procstat entry cause it's too old: " + "id = %lu; name = %s;", + pse->id, ps->name); + + if (pse_prev == NULL) + { + ps->instances = pse->next; + free (pse); + pse = ps->instances; + } + else + { + pse_prev->next = pse->next; + free (pse); + pse = pse_prev->next; + } + } + else + { + pse->age++; + pse_prev = pse; + pse = pse->next; + } + } /* while (pse != NULL) */ + } /* for (ps = list_head_g; ps != NULL; ps = ps->next) */ +} +#endif /* HAVE_THREAD_INFO | KERNEL_LINUX */ + +static int ps_config (char *key, char *value) +{ + if (strcasecmp (key, "Process") == 0) + { + ps_list_register (value); + } + else + { + return (-1); + } + + return (0); +} + static void ps_init (void) { #if HAVE_THREAD_INFO @@ -134,7 +449,9 @@ static void ps_init (void) /* #endif HAVE_THREAD_INFO */ #elif KERNEL_LINUX - /* No init */ + pagesize_g = sysconf(_SC_PAGESIZE); + DBG ("pagesize_g = %li; CONFIG_HZ = %i;", + pagesize_g, CONFIG_HZ); #endif /* KERNEL_LINUX */ return; @@ -142,7 +459,65 @@ static void ps_init (void) static void ps_write (char *host, char *inst, char *val) { - rrd_update_file (host, ps_file, val, ds_def, ds_num); + rrd_update_file (host, processes_file, val, + processes_ds_def, processes_ds_num); +} + +static void ps_rss_write (char *host, char *inst, char *val) +{ + char filename[256]; + int status; + + status = snprintf (filename, 256, ps_rss_file, inst); + if ((status < 1) || (status >= 256)) + return; + + rrd_update_file (host, filename, val, ps_rss_ds_def, ps_rss_ds_num); +} + +static void ps_cputime_write (char *host, char *inst, char *val) +{ + char filename[256]; + int status; + + status = snprintf (filename, 256, ps_cputime_file, inst); + if ((status < 1) || (status >= 256)) + return; + + DBG ("host = %s; filename = %s; val = %s;", + host, filename, val); + rrd_update_file (host, filename, val, + ps_cputime_ds_def, ps_cputime_ds_num); +} + +static void ps_count_write (char *host, char *inst, char *val) +{ + char filename[256]; + int status; + + status = snprintf (filename, 256, ps_count_file, inst); + if ((status < 1) || (status >= 256)) + return; + + DBG ("host = %s; filename = %s; val = %s;", + host, filename, val); + rrd_update_file (host, filename, val, + ps_count_ds_def, ps_count_ds_num); +} + +static void ps_pagefaults_write (char *host, char *inst, char *val) +{ + char filename[256]; + int status; + + status = snprintf (filename, 256, ps_pagefaults_file, inst); + if ((status < 1) || (status >= 256)) + return; + + DBG ("host = %s; filename = %s; val = %s;", + host, filename, val); + rrd_update_file (host, filename, val, + ps_pagefaults_ds_def, ps_pagefaults_ds_num); } #if PROCESSES_HAVE_READ @@ -167,6 +542,254 @@ static void ps_submit (int running, plugin_submit (MODULE_NAME, "-", buf); } +static void ps_submit_proc_list (procstat_t *ps) +{ + char buffer[64]; + + if (ps == NULL) + return; + + snprintf (buffer, 64, "%u:%lu", + (unsigned int) curtime, + ps->vmem_rss); + buffer[63] = '\0'; + plugin_submit ("ps_rss", ps->name, buffer); + + snprintf (buffer, 64, "%u:%u:%u", + (unsigned int) curtime, + /* Make the counter overflow */ + (unsigned int) (ps->cpu_user_counter & 0xFFFFFFFF), + (unsigned int) (ps->cpu_system_counter & 0xFFFFFFFF)); + buffer[63] = '\0'; + plugin_submit ("ps_cputime", ps->name, buffer); + + snprintf (buffer, 64, "%u:%lu:%lu", + (unsigned int) curtime, + ps->num_proc, ps->num_lwp); + buffer[63] = '\0'; + plugin_submit ("ps_count", ps->name, buffer); + + snprintf (buffer, 64, "%u:%lu:%lu", + (unsigned int) curtime, + ps->vmem_minflt_counter, ps->vmem_majflt_counter); + buffer[63] = '\0'; + plugin_submit ("ps_pagefaults", ps->name, buffer); + + DBG ("name = %s; num_proc = %lu; num_lwp = %lu; vmem_rss = %lu; " + "vmem_minflt_counter = %lu; vmem_majflt_counter = %lu; " + "cpu_user_counter = %lu; cpu_system_counter = %lu;", + ps->name, ps->num_proc, ps->num_lwp, ps->vmem_rss, + ps->vmem_minflt_counter, ps->vmem_majflt_counter, ps->cpu_user_counter, + ps->cpu_system_counter); + +} + +#if KERNEL_LINUX +static int *ps_read_tasks (int pid) +{ + int *list = NULL; + int list_size = 1; /* size of allocated space, in elements */ + int list_len = 0; /* number of currently used elements */ + + char dirname[64]; + DIR *dh; + struct dirent *ent; + + snprintf (dirname, 64, "/proc/%i/task", pid); + dirname[63] = '\0'; + + if ((dh = opendir (dirname)) == NULL) + { + DBG ("Failed to open directory `%s'", dirname); + return (NULL); + } + + while ((ent = readdir (dh)) != NULL) + { + if (!isdigit (ent->d_name[0])) + continue; + + if ((list_len + 1) >= list_size) + { + int *new_ptr; + int new_size = 2 * list_size; + /* Comes in sizes: 2, 4, 8, 16, ... */ + + new_ptr = (int *) realloc (list, (size_t) (sizeof (int) * new_size)); + if (new_ptr == NULL) + { + if (list != NULL) + free (list); + syslog (LOG_ERR, "processes plugin: " + "Failed to allocate more memory."); + return (NULL); + } + + list = new_ptr; + list_size = new_size; + + memset (list + list_len, 0, sizeof (int) * (list_size - list_len)); + } + + list[list_len] = atoi (ent->d_name); + if (list[list_len] != 0) + list_len++; + } + + closedir (dh); + + assert (list_len < list_size); + assert (list[list_len] == 0); + + return (list); +} + +int ps_read_process (int pid, procstat_t *ps, char *state) +{ + char filename[64]; + char buffer[1024]; + FILE *fh; + + char *fields[64]; + char fields_len; + + int *tasks; + int i; + + int ppid; + int name_len; + + long long unsigned cpu_user_counter; + long long unsigned cpu_system_counter; + long long unsigned vmem_rss; + + memset (ps, 0, sizeof (procstat_t)); + + snprintf (filename, 64, "/proc/%i/stat", pid); + filename[63] = '\0'; + + if ((fh = fopen (filename, "r")) == NULL) + return (-1); + + if (fgets (buffer, 1024, fh) == NULL) + { + fclose (fh); + return (-1); + } + + fclose (fh); + + fields_len = strsplit (buffer, fields, 64); + if (fields_len < 24) + { + DBG ("`%s' has only %i fields..", + filename, fields_len); + return (-1); + } + else if (fields_len != 41) + { + DBG ("WARNING: (fields_len = %i) != 41", fields_len); + } + + /* copy the name, strip brackets in the process */ + name_len = strlen (fields[1]) - 2; + if ((fields[1][0] != '(') || (fields[1][name_len + 1] != ')')) + { + DBG ("No brackets found in process name: `%s'", fields[1]); + return (-1); + } + fields[1] = fields[1] + 1; + fields[1][name_len] = '\0'; + strncpy (ps->name, fields[1], PROCSTAT_NAME_LEN); + + ppid = atoi (fields[3]); + + if ((tasks = ps_read_tasks (pid)) == NULL) + { + /* This happends for zombied, e.g. */ + DBG ("ps_read_tasks (%i) failed.", pid); + *state = 'Z'; + ps->num_lwp = 0; + ps->num_proc = 0; + } + else + { + *state = '\0'; + ps->num_lwp = 0; + ps->num_proc = 1; + for (i = 0; tasks[i] != 0; i++) + ps->num_lwp++; + + free (tasks); + tasks = NULL; + } + + /* Leave the rest at zero if this is only an LWP */ + if (ps->num_proc == 0) + { + DBG ("This is only an LWP: pid = %i; name = %s;", + pid, ps->name); + return (0); + } + + cpu_user_counter = atoll (fields[13]); + cpu_system_counter = atoll (fields[14]); + vmem_rss = atoll (fields[23]); + ps->vmem_minflt_counter = atol (fields[9]); + ps->vmem_majflt_counter = atol (fields[11]); + + /* Convert jiffies to useconds */ + cpu_user_counter = cpu_user_counter * 1000000 / CONFIG_HZ; + cpu_system_counter = cpu_system_counter * 1000000 / CONFIG_HZ; + vmem_rss = vmem_rss * pagesize_g; + + ps->cpu_user_counter = (unsigned long) cpu_user_counter; + ps->cpu_system_counter = (unsigned long) cpu_system_counter; + ps->vmem_rss = (unsigned long) vmem_rss; + + *state = fields[2][0]; + + /* success */ + return (0); +} /* int ps_read_process (...) */ +#endif /* KERNEL_LINUX */ + +#if HAVE_THREAD_INFO +static int mach_get_task_name (task_t t, int *pid, char *name, size_t name_max_len) +{ + int mib[4]; + + struct kinfo_proc kp; + size_t kp_size; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + + if (pid_for_task (t, pid) != KERN_SUCCESS) + return (-1); + mib[3] = *pid; + + kp_size = sizeof (kp); + if (sysctl (mib, 4, &kp, &kp_size, NULL, 0) != 0) + return (-1); + + if (name_max_len > (MAXCOMLEN + 1)) + name_max_len = MAXCOMLEN + 1; + + strncpy (name, kp.kp_proc.p_comm, name_max_len - 1); + name[name_max_len - 1] = '\0'; + + DBG ("pid = %i; name = %s;", *pid, name); + + /* We don't do the special handling for `p_comm == "LaunchCFMApp"' as + * `top' does it, because it is a lot of work and only used when + * debugging. -octo */ + + return (0); +} +#endif /* HAVE_THREAD_INFO */ + static void ps_read (void) { #if HAVE_THREAD_INFO @@ -179,6 +802,9 @@ static void ps_read (void) task_array_t task_list; mach_msg_type_number_t task_list_len; + int task_pid; + char task_name[MAXCOMLEN + 1]; + int thread; thread_act_array_t thread_list; mach_msg_type_number_t thread_list_len; @@ -191,6 +817,11 @@ static void ps_read (void) int stopped = 0; int blocked = 0; + procstat_t *ps; + procstat_entry_t pse; + + ps_list_reset (); + /* * The Mach-concept is a little different from the traditional UNIX * concept: All the work is done in threads. Threads are contained in @@ -222,6 +853,71 @@ static void ps_read (void) for (task = 0; task < task_list_len; task++) { + ps = NULL; + if (mach_get_task_name (task_list[task], + &task_pid, + task_name, PROCSTAT_NAME_LEN) == 0) + ps = ps_list_search (task_name); + + /* Collect more detailed statistics for this process */ + if (ps != NULL) + { + task_basic_info_data_t task_basic_info; + mach_msg_type_number_t task_basic_info_len; + task_events_info_data_t task_events_info; + mach_msg_type_number_t task_events_info_len; + task_absolutetime_info_data_t task_absolutetime_info; + mach_msg_type_number_t task_absolutetime_info_len; + + memset (&pse, '\0', sizeof (pse)); + pse.id = task_pid; + + task_basic_info_len = TASK_BASIC_INFO_COUNT; + status = task_info (task_list[task], + TASK_BASIC_INFO, + (task_info_t) &task_basic_info, + &task_basic_info_len); + if (status != KERN_SUCCESS) + { + syslog (LOG_ERR, "task_info failed: %s", + mach_error_string (status)); + continue; /* with next thread_list */ + } + + task_events_info_len = TASK_EVENTS_INFO_COUNT; + status = task_info (task_list[task], + TASK_EVENTS_INFO, + (task_info_t) &task_events_info, + &task_events_info_len); + if (status != KERN_SUCCESS) + { + syslog (LOG_ERR, "task_info failed: %s", + mach_error_string (status)); + continue; /* with next thread_list */ + } + + task_absolutetime_info_len = TASK_ABSOLUTETIME_INFO_COUNT; + status = task_info (task_list[task], + TASK_ABSOLUTETIME_INFO, + (task_info_t) &task_absolutetime_info, + &task_absolutetime_info_len); + if (status != KERN_SUCCESS) + { + syslog (LOG_ERR, "task_info failed: %s", + mach_error_string (status)); + continue; /* with next thread_list */ + } + + pse.num_proc++; + pse.vmem_rss = task_basic_info.resident_size; + + pse.vmem_minflt_counter = task_events_info.cow_faults; + pse.vmem_majflt_counter = task_events_info.faults; + + pse.cpu_user_counter = task_absolutetime_info.total_user; + pse.cpu_system_counter = task_absolutetime_info.total_system; + } + status = task_threads (task_list[task], &thread_list, &thread_list_len); if (status != KERN_SUCCESS) @@ -248,7 +944,7 @@ static void ps_read (void) &thread_data_len); if (status != KERN_SUCCESS) { - syslog (LOG_ERR, "thread_info failed: %s\n", + syslog (LOG_ERR, "thread_info failed: %s", mach_error_string (status)); if (task_list[task] != port_task_self) mach_port_deallocate (port_task_self, @@ -256,6 +952,9 @@ static void ps_read (void) continue; /* with next thread_list */ } + if (ps != NULL) + pse.num_lwp++; + switch (thread_data.run_state) { case TH_STATE_RUNNING: @@ -315,6 +1014,9 @@ static void ps_read (void) syslog (LOG_ERR, "mach_port_deallocate failed: %s", mach_error_string (status)); } + + if (ps != NULL) + ps_list_add (task_name, &pse); } /* for (task_list) */ if ((status = vm_deallocate (port_task_self, @@ -336,6 +1038,9 @@ static void ps_read (void) } /* for (pset_list) */ ps_submit (running, sleeping, zombies, stopped, -1, blocked); + + for (ps = list_head_g; ps != NULL; ps = ps->next) + ps_submit_proc_list (ps); /* #endif HAVE_THREAD_INFO */ #elif KERNEL_LINUX @@ -346,15 +1051,19 @@ static void ps_read (void) int paging = 0; int blocked = 0; - char buf[BUFSIZE]; - char filename[20]; /* need 17 bytes */ - char *fields[BUFSIZE]; - struct dirent *ent; - DIR *proc; - FILE *fh; + DIR *proc; + int pid; + + int status; + procstat_t ps; + procstat_entry_t pse; + char state; + + procstat_t *ps_ptr; running = sleeping = zombies = stopped = paging = blocked = 0; + ps_list_reset (); if ((proc = opendir ("/proc")) == NULL) { @@ -367,33 +1076,34 @@ static void ps_read (void) if (!isdigit (ent->d_name[0])) continue; - if (snprintf (filename, 20, "/proc/%s/stat", ent->d_name) >= 20) + if ((pid = atoi (ent->d_name)) < 1) continue; - if ((fh = fopen (filename, "r")) == NULL) + status = ps_read_process (pid, &ps, &state); + if (status != 0) { - syslog (LOG_NOTICE, "Cannot open `%s': %s", filename, - strerror (errno)); + DBG ("ps_read_process failed: %i", status); continue; } - if (fgets (buf, BUFSIZE, fh) == NULL) - { - syslog (LOG_NOTICE, "Unable to read from `%s': %s", - filename, strerror (errno)); - fclose (fh); - continue; - } + pse.id = pid; + pse.age = 0; - fclose (fh); + pse.num_proc = ps.num_proc; + pse.num_lwp = ps.num_lwp; + pse.vmem_rss = ps.vmem_rss; - if (strsplit (buf, fields, BUFSIZE) < 3) - { - DBG ("Line has less than three fields."); - continue; - } + pse.vmem_minflt = 0; + pse.vmem_minflt_counter = ps.vmem_minflt_counter; + pse.vmem_majflt = 0; + pse.vmem_majflt_counter = ps.vmem_majflt_counter; + + pse.cpu_user = 0; + pse.cpu_user_counter = ps.cpu_user_counter; + pse.cpu_system = 0; + pse.cpu_system_counter = ps.cpu_system_counter; - switch (fields[2][0]) + switch (state) { case 'R': running++; break; case 'S': sleeping++; break; @@ -402,11 +1112,16 @@ static void ps_read (void) case 'T': stopped++; break; case 'W': paging++; break; } + + ps_list_add (ps.name, &pse); } closedir (proc); ps_submit (running, sleeping, zombies, stopped, paging, blocked); + + for (ps_ptr = list_head_g; ps_ptr != NULL; ps_ptr = ps_ptr->next) + ps_submit_proc_list (ps_ptr); #endif /* KERNEL_LINUX */ } #else @@ -416,6 +1131,11 @@ static void ps_read (void) void module_register (void) { plugin_register (MODULE_NAME, ps_init, ps_read, ps_write); + plugin_register ("ps_rss", NULL, NULL, ps_rss_write); + plugin_register ("ps_cputime", NULL, NULL, ps_cputime_write); + plugin_register ("ps_count", NULL, NULL, ps_count_write); + plugin_register ("ps_pagefaults", NULL, NULL, ps_pagefaults_write); + cf_register (MODULE_NAME, ps_config, config_keys, config_keys_num); } #undef BUFSIZE diff --git a/src/sensors.c b/src/sensors.c index 940120c6..ff8b580d 100644 --- a/src/sensors.c +++ b/src/sensors.c @@ -18,13 +18,26 @@ * * Authors: * Florian octo Forster + * + * Lubos Stanek Wed Oct 27, 2006 + * - config ExtendedSensorNaming option + * - precise sensor feature selection (chip-bus-address/type-feature) + * with ExtendedSensorNaming + * - more sensor features (finite list) + * - honor sensors.conf's ignored + * - config Sensor option + * - config IgnoreSelected option **/ #include "collectd.h" #include "common.h" #include "plugin.h" +#include "configfile.h" +#include "utils_ignorelist.h" +#include "utils_debug.h" #define MODULE_NAME "sensors" +#define MODULE_NAME_VOLTAGE MODULE_NAME"_voltage" #if defined(HAVE_SENSORS_SENSORS_H) # include @@ -40,8 +53,7 @@ #define BUFSIZE 512 -static char *filename_format = "sensors-%s.rrd"; - +/* temperature and fan sensors */ static char *ds_def[] = { "DS:value:GAUGE:"COLLECTD_HEARTBEAT":U:U", @@ -49,20 +61,176 @@ static char *ds_def[] = }; static int ds_num = 1; -#ifdef HAVE_LIBSENSORS +/* voltage sensors */ +static char *sensor_voltage_ds_def[] = +{ + "DS:voltage:GAUGE:"COLLECTD_HEARTBEAT":U:U", + NULL +}; +static int sensor_voltage_ds_num = 1; + +/* old naming */ +static char *old_filename_format = "sensors-%s.rrd"; +/* end old naming */ + +/* new naming default is to create chip-feature + * 1 => use new naming scheme chip-bus-address/type-feature + */ +static int sensor_extended_naming = 0; + +#if SENSORS_HAVE_READ typedef struct featurelist { const sensors_chip_name *chip; const sensors_feature_data *data; + int type; struct featurelist *next; } featurelist_t; featurelist_t *first_feature = NULL; -#endif /* defined (HAVE_LIBSENSORS) */ +#endif /* if SENSORS_HAVE_READ */ + +static int sensors_config (char *key, char *value) +{ + if (sensor_list == NULL) + sensor_list = ignorelist_create (1); + + if (strcasecmp (key, "Sensor") == 0) + { + if (ignorelist_add (sensor_list, value)) + { + syslog (LOG_EMERG, "Cannot add value to ignorelist."); + return (1); + } + } + else if (strcasecmp (key, "IgnoreSelected") == 0) + { + ignorelist_set_invert (sensor_list, 1); + if ((strcasecmp (value, "True") == 0) + || (strcasecmp (value, "Yes") == 0) + || (strcasecmp (value, "On") == 0)) + ignorelist_set_invert (sensor_list, 0); + } + else if (strcasecmp (key, "ExtendedSensorNaming") == 0) + { + if ((strcasecmp (value, "True") == 0) + || (strcasecmp (value, "Yes") == 0) + || (strcasecmp (value, "On") == 0)) + sensor_extended_naming = 1; + else + sensor_extended_naming = 0; + } + else + { + return (-1); + } + + return (0); +} static void collectd_sensors_init (void) { -#ifdef HAVE_LIBSENSORS +#if SENSORS_HAVE_READ FILE *fh; featurelist_t *last_feature = NULL; featurelist_t *new_feature; @@ -92,7 +260,8 @@ static void collectd_sensors_init (void) if (sensors_init (fh)) { fclose (fh); - syslog (LOG_ERR, "sensors: Cannot initialize sensors. Data will not be collected."); + syslog (LOG_ERR, "sensors: Cannot initialize sensors. " + "Data will not be collected."); return; } @@ -104,49 +273,106 @@ static void collectd_sensors_init (void) data = NULL; data_num0 = data_num1 = 0; - while ((data = sensors_get_all_features (*chip, &data_num0, &data_num1)) != NULL) + while ((data = sensors_get_all_features (*chip, &data_num0, &data_num1)) + != NULL) { + int i; + /* "master features" only */ if (data->mapping != SENSORS_NO_MAPPING) continue; - /* Only temperature for now.. */ - if (strncmp (data->name, "temp", 4) - && strncmp (data->name, "fan", 3)) - continue; - - if ((new_feature = (featurelist_t *) malloc (sizeof (featurelist_t))) == NULL) + /* Only known features */ + for (i = 0; known_features[i].type >= 0; i++) { - perror ("malloc"); - continue; - } + if (strcmp (data->name, known_features[i].label) != 0) + continue; + + /* skip ignored in sensors.conf */ + if (sensors_get_ignored (*chip, data->number) == 0) + break; + + DBG ("Adding feature: %s-%s-%s", + chip->prefix, + sensor_type_prefix[known_features[i].type], + data->name); + + if ((new_feature = (featurelist_t *) malloc (sizeof (featurelist_t))) == NULL) + { + DBG ("sensors plugin: malloc: %s", + strerror (errno)); + syslog (LOG_ERR, "sensors plugin: malloc: %s", + strerror (errno)); + break; + } + + new_feature->chip = chip; + new_feature->data = data; + new_feature->type = known_features[i].type; + new_feature->next = NULL; + + if (first_feature == NULL) + { + first_feature = new_feature; + last_feature = new_feature; + } + else + { + last_feature->next = new_feature; + last_feature = new_feature; + } + + /* stop searching known features at first found */ + break; + } /* for i */ + } /* while sensors_get_all_features */ + } /* while sensors_get_detected_chips */ - /* - syslog (LOG_INFO, "sensors: Adding feature: %s/%s", chip->prefix, data->name); - */ + if (first_feature == NULL) + sensors_cleanup (); +#endif /* if SENSORS_HAVE_READ */ - new_feature->chip = chip; - new_feature->data = data; - new_feature->next = NULL; + return; +} - if (first_feature == NULL) - { - first_feature = new_feature; - last_feature = new_feature; - } - else - { - last_feature->next = new_feature; - last_feature = new_feature; - } - } +static void sensors_shutdown (void) +{ +#if SENSORS_HAVE_READ + featurelist_t *thisft = first_feature; + featurelist_t *nextft; + + while (thisft != NULL) + { + nextft = thisft->next; + sfree (thisft); + thisft = nextft; } - if (first_feature == NULL) - sensors_cleanup (); -#endif /* defined(HAVE_LIBSENSORS) */ + sensors_cleanup (); +#endif /* if SENSORS_HAVE_READ */ - return; + ignorelist_free (sensor_list); +} + +static void sensors_voltage_write (char *host, char *inst, char *val) +{ + char file[BUFSIZE]; + int status; + + /* skip ignored in our config */ + if (ignorelist_match (sensor_list, inst)) + return; + + /* extended sensor naming */ + if(sensor_extended_naming) + status = snprintf (file, BUFSIZE, extended_filename_format, inst); + else + status = snprintf (file, BUFSIZE, old_filename_format, inst); + + if ((status < 1) || (status >= BUFSIZE)) + return; + + rrd_update_file (host, file, val, sensor_voltage_ds_def, sensor_voltage_ds_num); } static void sensors_write (char *host, char *inst, char *val) @@ -154,41 +380,109 @@ static void sensors_write (char *host, char *inst, char *val) char file[BUFSIZE]; int status; - status = snprintf (file, BUFSIZE, filename_format, inst); - if (status < 1) + /* skip ignored in our config */ + if (ignorelist_match (sensor_list, inst)) return; - else if (status >= BUFSIZE) + + /* extended sensor naming */ + if (sensor_extended_naming) + status = snprintf (file, BUFSIZE, extended_filename_format, inst); + else + status = snprintf (file, BUFSIZE, old_filename_format, inst); + + if ((status < 1) || (status >= BUFSIZE)) return; rrd_update_file (host, file, val, ds_def, ds_num); } #if SENSORS_HAVE_READ -static void sensors_submit (const char *feat_name, const char *chip_prefix, double value) +static void sensors_submit (const char *feat_name, + const char *chip_prefix, double value, int type) { char buf[BUFSIZE]; char inst[BUFSIZE]; - if (snprintf (buf, BUFSIZE, "%u:%.3f", (unsigned int) curtime, value) >= BUFSIZE) + if (snprintf (inst, BUFSIZE, "%s-%s", chip_prefix, feat_name) + >= BUFSIZE) return; - if (snprintf (inst, BUFSIZE, "%s-%s", chip_prefix, feat_name) >= BUFSIZE) + /* skip ignored in our config */ + if (ignorelist_match (sensor_list, inst)) return; - plugin_submit (MODULE_NAME, inst, buf); + if (snprintf (buf, BUFSIZE, "%u:%.3f", (unsigned int) curtime, + value) >= BUFSIZE) + return; + + if (type == SENSOR_TYPE_VOLTAGE) + { + DBG ("%s: %s/%s, %s", MODULE_NAME_VOLTAGE, + sensor_type_prefix[type], inst, buf); + plugin_submit (MODULE_NAME_VOLTAGE, inst, buf); + } + else + { + DBG ("%s: %s/%s, %s", MODULE_NAME, + sensor_type_prefix[type], inst, buf); + plugin_submit (MODULE_NAME, inst, buf); + } } static void sensors_read (void) { featurelist_t *feature; double value; + char chip_fullprefix[BUFSIZE]; for (feature = first_feature; feature != NULL; feature = feature->next) { if (sensors_get_feature (*feature->chip, feature->data->number, &value) < 0) continue; - sensors_submit (feature->data->name, feature->chip->prefix, value); + if (sensor_extended_naming) + { + /* full chip name logic borrowed from lm_sensors */ + if (feature->chip->bus == SENSORS_CHIP_NAME_BUS_ISA) + { + if (snprintf (chip_fullprefix, BUFSIZE, "%s-isa-%04x/%s", + feature->chip->prefix, + feature->chip->addr, + sensor_type_prefix[feature->type]) + >= BUFSIZE) + continue; + } + else if (feature->chip->bus == SENSORS_CHIP_NAME_BUS_DUMMY) + { + if (snprintf (chip_fullprefix, BUFSIZE, "%s-%s-%04x/%s", + feature->chip->prefix, + feature->chip->busname, + feature->chip->addr, + sensor_type_prefix[feature->type]) + >= BUFSIZE) + continue; + } + else + { + if (snprintf (chip_fullprefix, BUFSIZE, "%s-i2c-%d-%02x/%s", + feature->chip->prefix, + feature->chip->bus, + feature->chip->addr, + sensor_type_prefix[feature->type]) + >= BUFSIZE) + continue; + } + + sensors_submit (feature->data->name, + chip_fullprefix, + value, feature->type); + } + else + { + sensors_submit (feature->data->name, + feature->chip->prefix, + value, feature->type); + } } } #else @@ -198,6 +492,9 @@ static void sensors_read (void) void module_register (void) { plugin_register (MODULE_NAME, collectd_sensors_init, sensors_read, sensors_write); + plugin_register (MODULE_NAME_VOLTAGE, NULL, NULL, sensors_voltage_write); + plugin_register_shutdown (MODULE_NAME, sensors_shutdown); + cf_register (MODULE_NAME, sensors_config, config_keys, config_keys_num); } #undef BUFSIZE diff --git a/src/utils_dns.c b/src/utils_dns.c new file mode 100644 index 00000000..e9aec38f --- /dev/null +++ b/src/utils_dns.c @@ -0,0 +1,977 @@ +/* + * collectd - src/utils_dns.c + * Modifications Copyright (C) 2006 Florian octo Forster + * Copyright (C) 2002 The Measurement Factory, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of The Measurement Factory nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Authors: + * The Measurement Factory, Inc. + * Florian octo Forster + */ + +#include "collectd.h" + +#if HAVE_NETINET_IN_H +# include +#endif +#if HAVE_PCAP_H +# include +#endif +#if HAVE_ARPA_INET_H +# include +#endif + +#if HAVE_ARPA_NAMESER_H +# include +#elif HAVE_ARPA_NAMESER_COMPAT_H +# include +#endif + +#if HAVE_NET_IF_ARP_H +# include +#endif +#if HAVE_NET_IF_H +# include +#endif +#if HAVE_NETINET_IF_ETHER_H +# include +#endif +#if HAVE_NET_IF_PPP_H +# include +#endif + +#if HAVE_SYS_SOCKET_H +# include +#endif +#if HAVE_NETDB_H +# include +#endif + +#if HAVE_NETINET_IN_SYSTM_H +# include +#endif +#if HAVE_NETINET_IN_H +# include +#endif +#if HAVE_NETINET_IP_H +# include +#endif +#ifdef HAVE_NETINET_IP_VAR_H +# include +#endif +#if HAVE_NETINET_IP6_H +# include +#endif +#if HAVE_NETINET_UDP_H +# include +#endif + +#define PCAP_SNAPLEN 1460 +#ifndef ETHER_HDR_LEN +#define ETHER_ADDR_LEN 6 +#define ETHER_TYPE_LEN 2 +#define ETHER_HDR_LEN (ETHER_ADDR_LEN * 2 + ETHER_TYPE_LEN) +#endif +#ifndef ETHERTYPE_8021Q +# define ETHERTYPE_8021Q 0x8100 +#endif +#ifndef ETHERTYPE_IPV6 +# define ETHERTYPE_IPV6 0x86DD +#endif + +#ifndef PPP_ADDRESS_VAL +# define PPP_ADDRESS_VAL 0xff /* The address byte value */ +#endif +#ifndef PPP_CONTROL_VAL +# define PPP_CONTROL_VAL 0x03 /* The control byte value */ +#endif + +#ifdef __linux__ +#define uh_sport source +#define uh_dport dest +#endif + +#include "utils_dns.h" + +/* + * Type definitions + */ +struct ip_list_s +{ + struct in6_addr addr; + void *data; + struct ip_list_s *next; +}; +typedef struct ip_list_s ip_list_t; + +typedef int (printer)(const char *, ...); + +/* + * flags/features for non-interactive mode + */ + +#ifndef T_A6 +#define T_A6 38 +#endif +#ifndef T_SRV +#define T_SRV 33 +#endif + +/* + * Global variables + */ +int qtype_counts[T_MAX]; +int opcode_counts[OP_MAX]; +int qclass_counts[C_MAX]; + +#if HAVE_PCAP_H +static pcap_t *pcap_obj = NULL; +#endif + +static ip_list_t *IgnoreList = NULL; + +#if HAVE_PCAP_H +static void (*Callback) (const rfc1035_header_t *) = NULL; + +static int query_count_intvl = 0; +static int query_count_total = 0; +# ifdef __OpenBSD__ +static struct bpf_timeval last_ts; +# else +static struct timeval last_ts; +# endif /* __OpenBSD__ */ +#endif /* HAVE_PCAP_H */ + +static int cmp_in6_addr (const struct in6_addr *a, + const struct in6_addr *b) +{ + int i; + + assert (sizeof (struct in6_addr) == 16); + + for (i = 0; i < 16; i++) + if (a->s6_addr[i] != b->s6_addr[i]) + break; + + if (i >= 16) + return (0); + + return (a->s6_addr[i] > b->s6_addr[i] ? 1 : -1); +} /* int cmp_addrinfo */ + +static inline int ignore_list_match (const struct in6_addr *addr) +{ + ip_list_t *ptr; + + for (ptr = IgnoreList; ptr != NULL; ptr = ptr->next) + if (cmp_in6_addr (addr, &ptr->addr) == 0) + return (1); + return (0); +} /* int ignore_list_match */ + +static void ignore_list_add (const struct in6_addr *addr) +{ + ip_list_t *new; + + if (ignore_list_match (addr) != 0) + return; + + new = malloc (sizeof (ip_list_t)); + if (new == NULL) + { + perror ("malloc"); + return; + } + + memcpy (&new->addr, addr, sizeof (struct in6_addr)); + new->next = IgnoreList; + + IgnoreList = new; +} /* void ignore_list_add */ + +void ignore_list_add_name (const char *name) +{ + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + struct in6_addr addr; + int status; + + status = getaddrinfo (name, NULL, NULL, &ai_list); + if (status != 0) + return; + + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) + { + if (ai_ptr->ai_family == AF_INET) + { + memset (&addr, '\0', sizeof (addr)); + addr.s6_addr[10] = 0xFF; + addr.s6_addr[11] = 0xFF; + memcpy (addr.s6_addr + 12, &((struct sockaddr_in *) ai_ptr->ai_addr)->sin_addr, 4); + + ignore_list_add (&addr); + } + else + { + ignore_list_add (&((struct sockaddr_in6 *) ai_ptr->ai_addr)->sin6_addr); + } + } /* for */ + + freeaddrinfo (ai_list); +} + +#if HAVE_PCAP_H +static void in6_addr_from_buffer (struct in6_addr *ia, + const void *buf, size_t buf_len, + int family) +{ + memset (ia, 0, sizeof (struct in6_addr)); + if ((AF_INET == family) && (sizeof (uint32_t) == buf_len)) + { + ia->s6_addr[10] = 0xFF; + ia->s6_addr[11] = 0xFF; + memcpy (ia->s6_addr + 12, buf, buf_len); + } + else if ((AF_INET6 == family) && (sizeof (struct in6_addr) == buf_len)) + { + memcpy (ia, buf, buf_len); + } +} /* void in6_addr_from_buffer */ + +void dnstop_set_pcap_obj (pcap_t *po) +{ + pcap_obj = po; +} + +void dnstop_set_callback (void (*cb) (const rfc1035_header_t *)) +{ + Callback = cb; +} + +#define RFC1035_MAXLABELSZ 63 +static int +rfc1035NameUnpack(const char *buf, size_t sz, off_t * off, char *name, size_t ns +) +{ + off_t no = 0; + unsigned char c; + size_t len; + assert(ns > 0); + do { + if ((*off) >= sz) + break; + c = *(buf + (*off)); + if (c > 191) { + /* blasted compression */ + unsigned short s; + off_t ptr; + memcpy(&s, buf + (*off), sizeof(s)); + s = ntohs(s); + (*off) += sizeof(s); + /* Sanity check */ + if ((*off) >= sz) + return 1; + ptr = s & 0x3FFF; + /* Make sure the pointer is inside this message */ + if (ptr >= sz) + return 2; + return rfc1035NameUnpack(buf, sz, &ptr, name + no, ns - no); + } else if (c > RFC1035_MAXLABELSZ) { + /* + * "(The 10 and 01 combinations are reserved for future use.)" + */ + break; + return 3; + } else { + (*off)++; + len = (size_t) c; + if (len == 0) + break; + if (len > (ns - 1)) + len = ns - 1; + if ((*off) + len > sz) /* message is too short */ + return 4; + memcpy(name + no, buf + (*off), len); + (*off) += len; + no += len; + *(name + (no++)) = '.'; + } + } while (c > 0); + *(name + no - 1) = '\0'; + /* make sure we didn't allow someone to overflow the name buffer */ + assert(no <= ns); + return 0; +} + +static int +handle_dns(const char *buf, int len, + const struct in6_addr *s_addr, + const struct in6_addr *d_addr) +{ + rfc1035_header_t qh; + uint16_t us; + off_t offset; + char *t; + int x; + + /* The DNS header is 12 bytes long */ + if (len < 12) + return 0; + + memcpy(&us, buf + 0, 2); + qh.id = ntohs(us); + + memcpy(&us, buf + 2, 2); + us = ntohs(us); + fprintf (stderr, "Bytes 0, 1: 0x%04hx\n", us); + qh.qr = (us >> 15) & 0x01; + qh.opcode = (us >> 11) & 0x0F; + qh.aa = (us >> 10) & 0x01; + qh.tc = (us >> 9) & 0x01; + qh.rd = (us >> 8) & 0x01; + qh.ra = (us >> 7) & 0x01; + qh.z = (us >> 6) & 0x01; + qh.ad = (us >> 5) & 0x01; + qh.cd = (us >> 4) & 0x01; + qh.rcode = us & 0x0F; + + memcpy(&us, buf + 4, 2); + qh.qdcount = ntohs(us); + + memcpy(&us, buf + 6, 2); + qh.ancount = ntohs(us); + + memcpy(&us, buf + 8, 2); + qh.nscount = ntohs(us); + + memcpy(&us, buf + 10, 2); + qh.arcount = ntohs(us); + + offset = 12; + memset(qh.qname, '\0', MAX_QNAME_SZ); + x = rfc1035NameUnpack(buf, len, &offset, qh.qname, MAX_QNAME_SZ); + if (0 != x) + return 0; + if ('\0' == qh.qname[0]) + strcpy(qh.qname, "."); + while ((t = strchr(qh.qname, '\n'))) + *t = ' '; + while ((t = strchr(qh.qname, '\r'))) + *t = ' '; + for (t = qh.qname; *t; t++) + *t = tolower(*t); + + memcpy(&us, buf + offset, 2); + qh.qtype = ntohs(us); + memcpy(&us, buf + offset + 2, 2); + qh.qclass = ntohs(us); + + qh.length = (uint16_t) len; + + /* gather stats */ + qtype_counts[qh.qtype]++; + qclass_counts[qh.qclass]++; + opcode_counts[qh.opcode]++; + + if (Callback != NULL) + Callback (&qh); + + return 1; +} + +static int +handle_udp(const struct udphdr *udp, int len, + const struct in6_addr *s_addr, + const struct in6_addr *d_addr) +{ + char buf[PCAP_SNAPLEN]; + if ((ntohs (udp->uh_dport) != 53) + && (ntohs (udp->uh_sport) != 53)) + return 0; + memcpy(buf, udp + 1, len - sizeof(*udp)); + if (0 == handle_dns(buf, len - sizeof(*udp), s_addr, d_addr)) + return 0; + return 1; +} + +static int +handle_ipv6 (struct ip6_hdr *ipv6, int len) +{ + char buf[PCAP_SNAPLEN]; + int offset; + int nexthdr; + + struct in6_addr s_addr; + struct in6_addr d_addr; + uint16_t payload_len; + + offset = sizeof (struct ip6_hdr); + nexthdr = ipv6->ip6_nxt; + s_addr = ipv6->ip6_src; + d_addr = ipv6->ip6_dst; + payload_len = ntohs (ipv6->ip6_plen); + + if (ignore_list_match (&s_addr)) + return (0); + + /* Parse extension headers. This only handles the standard headers, as + * defined in RFC 2460, correctly. Fragments are discarded. */ + while ((IPPROTO_ROUTING == nexthdr) /* routing header */ + || (IPPROTO_HOPOPTS == nexthdr) /* Hop-by-Hop options. */ + || (IPPROTO_FRAGMENT == nexthdr) /* fragmentation header. */ + || (IPPROTO_DSTOPTS == nexthdr) /* destination options. */ + || (IPPROTO_DSTOPTS == nexthdr) /* destination options. */ + || (IPPROTO_AH == nexthdr) /* destination options. */ + || (IPPROTO_ESP == nexthdr)) /* encapsulating security payload. */ + { + struct ip6_ext ext_hdr; + uint16_t ext_hdr_len; + + /* Catch broken packets */ + if ((offset + sizeof (struct ip6_ext)) > len) + return (0); + + /* Cannot handle fragments. */ + if (IPPROTO_FRAGMENT == nexthdr) + return (0); + + memcpy (&ext_hdr, (char *) ipv6 + offset, sizeof (struct ip6_ext)); + nexthdr = ext_hdr.ip6e_nxt; + ext_hdr_len = (8 * (ntohs (ext_hdr.ip6e_len) + 1)); + + /* This header is longer than the packets payload.. WTF? */ + if (ext_hdr_len > payload_len) + return (0); + + offset += ext_hdr_len; + payload_len -= ext_hdr_len; + } /* while */ + + /* Catch broken and empty packets */ + if (((offset + payload_len) > len) + || (payload_len == 0) + || (payload_len > PCAP_SNAPLEN)) + return (0); + + if (IPPROTO_UDP != nexthdr) + return (0); + + memcpy (buf, (char *) ipv6 + offset, payload_len); + if (handle_udp ((struct udphdr *) buf, payload_len, &s_addr, &d_addr) == 0) + return (0); + + return (1); /* Success */ +} /* int handle_ipv6 */ + +static int +handle_ip(const struct ip *ip, int len) +{ + char buf[PCAP_SNAPLEN]; + int offset = ip->ip_hl << 2; + struct in6_addr s_addr; + struct in6_addr d_addr; + + if (ip->ip_v == 6) + return (handle_ipv6 ((struct ip6_hdr *) ip, len)); + + in6_addr_from_buffer (&s_addr, &ip->ip_src.s_addr, sizeof (ip->ip_src.s_addr), AF_INET); + in6_addr_from_buffer (&d_addr, &ip->ip_dst.s_addr, sizeof (ip->ip_dst.s_addr), AF_INET); + if (ignore_list_match (&s_addr)) + return (0); + if (IPPROTO_UDP != ip->ip_p) + return 0; + memcpy(buf, (void *) ip + offset, len - offset); + if (0 == handle_udp((struct udphdr *) buf, len - offset, &s_addr, &d_addr)) + return 0; + return 1; +} + +#if HAVE_NET_IF_PPP_H +static int +handle_ppp(const u_char * pkt, int len) +{ + char buf[PCAP_SNAPLEN]; + unsigned short us; + unsigned short proto; + if (len < 2) + return 0; + if (*pkt == PPP_ADDRESS_VAL && *(pkt + 1) == PPP_CONTROL_VAL) { + pkt += 2; /* ACFC not used */ + len -= 2; + } + if (len < 2) + return 0; + if (*pkt % 2) { + proto = *pkt; /* PFC is used */ + pkt++; + len--; + } else { + memcpy(&us, pkt, sizeof(us)); + proto = ntohs(us); + pkt += 2; + len -= 2; + } + if (ETHERTYPE_IP != proto && PPP_IP != proto) + return 0; + memcpy(buf, pkt, len); + return handle_ip((struct ip *) buf, len); +} +#endif /* HAVE_NET_IF_PPP_H */ + +static int +handle_null(const u_char * pkt, int len) +{ + unsigned int family; + memcpy(&family, pkt, sizeof(family)); + if (AF_INET != family) + return 0; + return handle_ip((struct ip *) (pkt + 4), len - 4); +} + +#ifdef DLT_LOOP +static int +handle_loop(const u_char * pkt, int len) +{ + unsigned int family; + memcpy(&family, pkt, sizeof(family)); + if (AF_INET != ntohl(family)) + return 0; + return handle_ip((struct ip *) (pkt + 4), len - 4); +} + +#endif + +#ifdef DLT_RAW +static int +handle_raw(const u_char * pkt, int len) +{ + return handle_ip((struct ip *) pkt, len); +} + +#endif + +static int +handle_ether(const u_char * pkt, int len) +{ + char buf[PCAP_SNAPLEN]; + struct ether_header *e = (void *) pkt; + unsigned short etype = ntohs(e->ether_type); + if (len < ETHER_HDR_LEN) + return 0; + pkt += ETHER_HDR_LEN; + len -= ETHER_HDR_LEN; + if (ETHERTYPE_8021Q == etype) { + etype = ntohs(*(unsigned short *) (pkt + 2)); + pkt += 4; + len -= 4; + } + if ((ETHERTYPE_IP != etype) + && (ETHERTYPE_IPV6 != etype)) + return 0; + memcpy(buf, pkt, len); + if (ETHERTYPE_IPV6 == etype) + return (handle_ipv6 ((struct ip6_hdr *) buf, len)); + else + return handle_ip((struct ip *) buf, len); +} + +#ifdef DLT_LINUX_SLL +static int +handle_linux_sll (const u_char *pkt, int len) +{ + struct sll_header + { + uint16_t pkt_type; + uint16_t dev_type; + uint16_t addr_len; + uint8_t addr[8]; + uint16_t proto_type; + } *hdr; + uint16_t etype; + + if (len < sizeof (struct sll_header)) + return (0); + + hdr = (struct sll_header *) pkt; + pkt = (u_char *) (hdr + 1); + len -= sizeof (struct sll_header); + + etype = ntohs (hdr->proto_type); + + if ((ETHERTYPE_IP != etype) + && (ETHERTYPE_IPV6 != etype)) + return 0; + + if (ETHERTYPE_IPV6 == etype) + return (handle_ipv6 ((struct ip6_hdr *) pkt, len)); + else + return handle_ip((struct ip *) pkt, len); +} +#endif /* DLT_LINUX_SLL */ + +/* public function */ +void handle_pcap(u_char *udata, const struct pcap_pkthdr *hdr, const u_char *pkt) +{ + int status; + + fprintf (stderr, "handle_pcap (udata = %p, hdr = %p, pkt = %p): hdr->caplen = %i\n", + (void *) udata, (void *) hdr, (void *) pkt, + hdr->caplen); + + if (hdr->caplen < ETHER_HDR_LEN) + return; + + switch (pcap_datalink (pcap_obj)) + { + case DLT_EN10MB: + status = handle_ether (pkt, hdr->caplen); + break; +#if HAVE_NET_IF_PPP_H + case DLT_PPP: + status = handle_ppp (pkt, hdr->caplen); + break; +#endif +#ifdef DLT_LOOP + case DLT_LOOP: + status = handle_loop (pkt, hdr->caplen); + break; +#endif +#ifdef DLT_RAW + case DLT_RAW: + status = handle_raw (pkt, hdr->caplen); + break; +#endif +#ifdef DLT_LINUX_SLL + case DLT_LINUX_SLL: + status = handle_linux_sll (pkt, hdr->caplen); + break; +#endif + case DLT_NULL: + status = handle_null (pkt, hdr->caplen); + break; + + default: + fprintf (stderr, "unsupported data link type %d\n", + pcap_datalink(pcap_obj)); + status = 0; + break; + } /* switch (pcap_datalink(pcap_obj)) */ + + if (0 == status) + return; + + query_count_intvl++; + query_count_total++; + last_ts = hdr->ts; +} +#endif /* HAVE_PCAP_H */ + +const char *qtype_str(int t) +{ + static char buf[32]; + switch (t) { + case ns_t_a: return ("A"); + case ns_t_ns: return ("NS"); + case ns_t_md: return ("MD"); + case ns_t_mf: return ("MF"); + case ns_t_cname: return ("CNAME"); + case ns_t_soa: return ("SOA"); + case ns_t_mb: return ("MB"); + case ns_t_mg: return ("MG"); + case ns_t_mr: return ("MR"); + case ns_t_null: return ("NULL"); + case ns_t_wks: return ("WKS"); + case ns_t_ptr: return ("PTR"); + case ns_t_hinfo: return ("HINFO"); + case ns_t_minfo: return ("MINFO"); + case ns_t_mx: return ("MX"); + case ns_t_txt: return ("TXT"); + case ns_t_rp: return ("RP"); + case ns_t_afsdb: return ("AFSDB"); + case ns_t_x25: return ("X25"); + case ns_t_isdn: return ("ISDN"); + case ns_t_rt: return ("RT"); + case ns_t_nsap: return ("NSAP"); + case ns_t_nsap_ptr: return ("NSAP-PTR"); + case ns_t_sig: return ("SIG"); + case ns_t_key: return ("KEY"); + case ns_t_px: return ("PX"); + case ns_t_gpos: return ("GPOS"); + case ns_t_aaaa: return ("AAAA"); + case ns_t_loc: return ("LOC"); + case ns_t_nxt: return ("NXT"); + case ns_t_eid: return ("EID"); + case ns_t_nimloc: return ("NIMLOC"); + case ns_t_srv: return ("SRV"); + case ns_t_atma: return ("ATMA"); + case ns_t_naptr: return ("NAPTR"); + case ns_t_kx: return ("KX"); + case ns_t_cert: return ("CERT"); + case ns_t_a6: return ("A6"); + case ns_t_dname: return ("DNAME"); + case ns_t_sink: return ("SINK"); + case ns_t_opt: return ("OPT"); + case ns_t_tsig: return ("TSIG"); + case ns_t_ixfr: return ("IXFR"); + case ns_t_axfr: return ("AXFR"); + case ns_t_mailb: return ("MAILB"); + case ns_t_maila: return ("MAILA"); + case ns_t_any: return ("ANY"); + case ns_t_zxfr: return ("ZXFR"); + default: + snprintf (buf, 32, "#%i", t); + buf[31] = '\0'; + return (buf); + }; /* switch (t) */ + /* NOTREACHED */ + return (NULL); +} + +const char *opcode_str (int o) +{ + static char buf[30]; + switch (o) { + case 0: + return "Query"; + break; + case 1: + return "Iquery"; + break; + case 2: + return "Status"; + break; + case 4: + return "Notify"; + break; + case 5: + return "Update"; + break; + default: + snprintf(buf, 30, "Opcode%d", o); + return buf; + } + /* NOTREACHED */ +} + +const char *rcode_str (int rcode) +{ + static char buf[32]; + switch (rcode) + { + case ns_r_noerror: return ("NOERROR"); + case ns_r_formerr: return ("FORMERR"); + case ns_r_servfail: return ("SERVFAIL"); + case ns_r_nxdomain: return ("NXDOMAIN"); + case ns_r_notimpl: return ("NOTIMPL"); + case ns_r_refused: return ("REFUSED"); + case ns_r_yxdomain: return ("YXDOMAIN"); + case ns_r_yxrrset: return ("YXRRSET"); + case ns_r_nxrrset: return ("NXRRSET"); + case ns_r_notauth: return ("NOTAUTH"); + case ns_r_notzone: return ("NOTZONE"); + case ns_r_max: return ("MAX"); + case ns_r_badsig: return ("BADSIG"); + case ns_r_badkey: return ("BADKEY"); + case ns_r_badtime: return ("BADTIME"); + default: + snprintf (buf, 32, "RCode%i", rcode); + buf[31] = '\0'; + return (buf); + } + /* Never reached */ + return (NULL); +} /* const char *rcode_str (int rcode) */ + +#if 0 +static int +main(int argc, char *argv[]) +{ + char errbuf[PCAP_ERRBUF_SIZE]; + int x; + struct stat sb; + int readfile_state = 0; + struct bpf_program fp; + + port53 = htons(53); + SubReport = Sources_report; + ignore_addr.s_addr = 0; + progname = strdup(strrchr(argv[0], '/') ? strchr(argv[0], '/') + 1 : argv[0]); + srandom(time(NULL)); + ResetCounters(); + + while ((x = getopt(argc, argv, "ab:f:i:pst")) != -1) { + switch (x) { + case 'a': + anon_flag = 1; + break; + case 's': + sld_flag = 1; + break; + case 't': + nld_flag = 1; + break; + case 'p': + promisc_flag = 0; + break; + case 'b': + bpf_program_str = strdup(optarg); + break; + case 'i': + ignore_addr.s_addr = inet_addr(optarg); + break; + case 'f': + set_filter(optarg); + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + device = strdup(argv[0]); + + if (0 == stat(device, &sb)) + readfile_state = 1; + if (readfile_state) { + pcap_obj = pcap_open_offline(device, errbuf); + } else { + pcap_obj = pcap_open_live(device, PCAP_SNAPLEN, promisc_flag, 1000, errbuf); + } + if (NULL == pcap_obj) { + fprintf(stderr, "pcap_open_*: %s\n", errbuf); + exit(1); + } + + if (0 == isatty(1)) { + if (0 == readfile_state) { + fprintf(stderr, "Non-interactive mode requires savefile argument\n"); + exit(1); + } + interactive = 0; + print_func = printf; + } + + memset(&fp, '\0', sizeof(fp)); + x = pcap_compile(pcap_obj, &fp, bpf_program_str, 1, 0); + if (x < 0) { + fprintf(stderr, "pcap_compile failed\n"); + exit(1); + } + x = pcap_setfilter(pcap_obj, &fp); + if (x < 0) { + fprintf(stderr, "pcap_setfilter failed\n"); + exit(1); + } + + /* + * non-blocking call added for Mac OS X bugfix. Sent by Max Horn. + * ref http://www.tcpdump.org/lists/workers/2002/09/msg00033.html + */ + x = pcap_setnonblock(pcap_obj, 1, errbuf); + if (x < 0) { + fprintf(stderr, "pcap_setnonblock failed: %s\n", errbuf); + exit(1); + } + + switch (pcap_datalink(pcap_obj)) { + case DLT_EN10MB: + handle_datalink = handle_ether; + break; +#if HAVE_NET_IF_PPP_H + case DLT_PPP: + handle_datalink = handle_ppp; + break; +#endif +#ifdef DLT_LOOP + case DLT_LOOP: + handle_datalink = handle_loop; + break; +#endif +#ifdef DLT_RAW + case DLT_RAW: + handle_datalink = handle_raw; + break; +#endif + case DLT_NULL: + handle_datalink = handle_null; + break; + default: + fprintf(stderr, "unsupported data link type %d\n", + pcap_datalink(pcap_obj)); + return 1; + break; + } + if (interactive) { + init_curses(); + while (0 == Quit) { + if (readfile_state < 2) { + /* + * On some OSes select() might return 0 even when + * there are packets to process. Thus, we always + * ignore its return value and just call pcap_dispatch() + * anyway. + */ + if (0 == readfile_state) /* interactive */ + pcap_select(pcap_obj, 1, 0); + x = pcap_dispatch(pcap_obj, 50, handle_pcap, NULL); + } + if (0 == x && 1 == readfile_state) { + /* block on keyboard until user quits */ + readfile_state++; + nodelay(w, 0); + } + keyboard(); + cron_pre(); + report(); + cron_post(); + } + endwin(); /* klin, Thu Nov 28 08:56:51 2002 */ + } else { + while (pcap_dispatch(pcap_obj, 50, handle_pcap, NULL)) + (void) 0; + cron_pre(); + Sources_report(); print_func("\n"); + Destinatioreport(); print_func("\n"); + Qtypes_report(); print_func("\n"); + Opcodes_report(); print_func("\n"); + Tld_report(); print_func("\n"); + Sld_report(); print_func("\n"); + Nld_report(); print_func("\n"); + SldBySource_report(); + } + + pcap_close(pcap_obj); + return 0; +} /* static int main(int argc, char *argv[]) */ +#endif +/* + * vim:shiftwidth=4:tabstop=8:softtabstop=4 + */ diff --git a/src/utils_dns.h b/src/utils_dns.h new file mode 100644 index 00000000..b3f08acd --- /dev/null +++ b/src/utils_dns.h @@ -0,0 +1,90 @@ +#ifndef COLLECTD_UTILS_DNS_H +#define COLLECTD_UTILS_DNS_H 1 +/* + * collectd - src/utils_dns.h + * Copyright (C) 2006 Florian octo Forster + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of The Measurement Factory nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Authors: + * Florian octo Forster + */ + +#include "config.h" + +#include + +#if HAVE_PCAP_H +# include +#endif + +#define T_MAX 65536 +#define OP_MAX 16 +#define C_MAX 65536 +#define MAX_QNAME_SZ 512 + +struct rfc1035_header_s { + uint16_t id; + unsigned int qr:1; + unsigned int opcode:4; + unsigned int aa:1; + unsigned int tc:1; + unsigned int rd:1; + unsigned int ra:1; + unsigned int z:1; + unsigned int ad:1; + unsigned int cd:1; + unsigned int rcode:4; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; + uint16_t qtype; + uint16_t qclass; + char qname[MAX_QNAME_SZ]; + uint16_t length; +}; +typedef struct rfc1035_header_s rfc1035_header_t; + +extern int qtype_counts[T_MAX]; +extern int opcode_counts[OP_MAX]; +extern int qclass_counts[C_MAX]; + +#if HAVE_PCAP_H +void dnstop_set_pcap_obj (pcap_t *po); +#endif +void dnstop_set_callback (void (*cb) (const rfc1035_header_t *)); + +void ignore_list_add_name (const char *name); +#if HAVE_PCAP_H +void handle_pcap (u_char * udata, const struct pcap_pkthdr *hdr, const u_char * pkt); +#endif + +const char *qtype_str(int t); +const char *opcode_str(int o); +const char *rcode_str (int r); + +#endif /* !COLLECTD_UTILS_DNS_H */ diff --git a/src/utils_ignorelist.c b/src/utils_ignorelist.c new file mode 100644 index 00000000..532eb4b0 --- /dev/null +++ b/src/utils_ignorelist.c @@ -0,0 +1,365 @@ +/** + * collectd - src/utils_ignorelist.c + * Copyright (C) 2006 Lubos Stanek + * + * This program is free software; you can redistribute it and/ + * or modify it under the terms of the GNU General Public Li- + * cence as published by the Free Software Foundation; either + * version 2 of the Licence, or any later version. + * + * This program is distributed in the hope that it will be use- + * ful, but WITHOUT ANY WARRANTY; without even the implied war- + * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public Licence for more details. + * + * You should have received a copy of the GNU General Public + * Licence along with this program; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, + * USA. + * + * Authors: + * Lubos Stanek + **/ +/** + * ignorelist handles plugin's list of configured collectable + * entries with global ignore action + **/ +/** + * Usage: + * + * Define plugin's global pointer variable of type ignorelist_t: + * ignorelist_t *myconfig_ignore; + * If you know the state of the global ignore (IgnoreSelected), + * allocate the variable with: + * myconfig_ignore = ignorelist_create (YourKnownIgnore); + * If you do not know the state of the global ignore, + * initialize the global variable and set the ignore flag later: + * myconfig_ignore = ignorelist_init (); + * Append single entries in your cf_register'ed callback function: + * ignorelist_add (myconfig_ignore, newentry); + * When you hit the IgnoreSelected config option, + * offer it to the list: + * ignorelist_ignore (myconfig_ignore, instantly_got_value_of_ignore); + * That is all for the ignorelist initialization. + * Later during read and write (plugin's registered functions) get + * the information whether this entry would be collected or not: + * if (ignorelist_match (myconfig_ignore, thisentry)) + * return; + **/ + +#include "common.h" +#include "utils_debug.h" +#include "utils_ignorelist.h" + +/* + * private prototypes + */ +struct ignorelist_item_s +{ +#if HAVE_REGEX_H + regex_t *rmatch; /* regular expression entry identification */ +#endif + char *smatch; /* string entry identification */ + struct ignorelist_item_s *next; +}; +typedef struct ignorelist_item_s ignorelist_item_t; + +struct ignorelist_s +{ + int ignore; /* ignore entries */ + ignorelist_item_t *head; /* pointer to the first entry */ +}; + +/* *** *** *** ********************************************* *** *** *** */ +/* *** *** *** *** *** *** private functions *** *** *** *** *** *** */ +/* *** *** *** ********************************************* *** *** *** */ + +static inline void ignorelist_append (ignorelist_t *il, ignorelist_item_t *item) +{ + assert ((il != NULL) && (item != NULL)); + + item->next = il->head; + il->head = item; +} + +#if HAVE_REGEX_H +static int ignorelist_append_regex(ignorelist_t *il, const char *entry) +{ + int rcompile; + regex_t *regtemp; + int errsize; + char *regerr = NULL; + ignorelist_item_t *new; + + /* create buffer */ + if ((regtemp = malloc(sizeof(regex_t))) == NULL) + { + syslog (LOG_ERR, "cannot allocate new config entry"); + return (1); + } + memset (regtemp, '\0', sizeof(regex_t)); + + /* compile regex */ + if ((rcompile = regcomp (regtemp, entry, REG_EXTENDED)) != 0) + { + /* prepare message buffer */ + errsize = regerror(rcompile, regtemp, NULL, 0); + if (errsize) + regerr = smalloc(errsize); + /* get error message */ + if (regerror (rcompile, regtemp, regerr, errsize)) + { + fprintf (stderr, "Cannot compile regex %s: %i/%s", + entry, rcompile, regerr); + syslog (LOG_ERR, "Cannot compile regex %s: %i/%s", + entry, rcompile, regerr); + } + else + { + fprintf (stderr, "Cannot compile regex %s: %i", + entry, rcompile); + syslog (LOG_ERR, "Cannot compile regex %s: %i", + entry, rcompile); + } + + if (errsize) + sfree (regerr); + regfree (regtemp); + return (1); + } + DBG("regex compiled: %s - %i", entry, rcompile); + + /* create new entry */ + if ((new = malloc(sizeof(ignorelist_item_t))) == NULL) + { + syslog (LOG_ERR, "cannot allocate new config entry"); + regfree (regtemp); + return (1); + } + memset (new, '\0', sizeof(ignorelist_item_t)); + new->rmatch = regtemp; + + /* append new entry */ + ignorelist_append (il, new); + + return (0); +} /* int ignorelist_append_regex(ignorelist_t *il, const char *entry) */ +#endif + +static int ignorelist_append_string(ignorelist_t *il, const char *entry) +{ + ignorelist_item_t *new; + + /* create new entry */ + if ((new = malloc(sizeof(ignorelist_item_t))) == NULL ) + { + syslog (LOG_ERR, "cannot allocate new entry"); + return (1); + } + memset (new, '\0', sizeof(ignorelist_item_t)); + new->smatch = sstrdup(entry); + + /* append new entry */ + ignorelist_append (il, new); + + return (0); +} /* int ignorelist_append_string(ignorelist_t *il, const char *entry) */ + +#if HAVE_REGEX_H +/* + * check list for entry regex match + * return 1 if found + */ +static int ignorelist_match_regex (ignorelist_item_t *item, const char *entry) +{ + assert ((item != NULL) && (item->rmatch != NULL) + && (entry != NULL) && (strlen (entry) > 0)); + + /* match regex */ + if (regexec (item->rmatch, entry, 0, NULL, 0) == 0) + return (1); + + return (0); +} /* int ignorelist_match_regex (ignorelist_item_t *item, const char *entry) */ +#endif + +/* + * check list for entry string match + * return 1 if found + */ +static int ignorelist_match_string (ignorelist_item_t *item, const char *entry) +{ + assert ((item != NULL) && (item->smatch != NULL) + && (entry != NULL) && (strlen (entry) > 0)); + + if (strcmp (entry, item->smatch) == 0) + return (1); + + return (0); +} /* int ignorelist_match_string (ignorelist_item_t *item, const char *entry) */ + + +/* *** *** *** ******************************************** *** *** *** */ +/* *** *** *** *** *** *** public functions *** *** *** *** *** *** */ +/* *** *** *** ******************************************** *** *** *** */ + +/* + * create the ignorelist_t with known ignore state + * return pointer to ignorelist_t + */ +ignorelist_t *ignorelist_create (int invert) +{ + ignorelist_t *il; + + /* smalloc exits if it failes */ + il = (ignorelist_t *) smalloc (sizeof (ignorelist_t)); + DBG("Ignorelist created 0x%p, default is %s", + (void *) il, + invert ? "collect" : "ignore"); + + memset (il, '\0', sizeof (ignorelist_t)); + + /* + * ->ignore == 0 => collect + * ->ignore == 1 => ignore + */ + il->ignore = invert ? 0 : 1; + + return (il); +} /* ignorelist_t *ignorelist_create (int ignore) */ + +/* + * free memory used by ignorelist_t + */ +void ignorelist_free (ignorelist_t *il) +{ + ignorelist_item_t *this; + ignorelist_item_t *next; + + DBG ("(il = 0x%p)", (void *) il); + + if (il == NULL) + return; + + for (this = il->head; this != NULL; this = next) + { + next = this->next; +#if HAVE_REGEX_H + if (this->rmatch != NULL) + { + regfree (this->rmatch); + this->rmatch = NULL; + } +#endif + if (this->smatch != NULL) + { + sfree (this->smatch); + this->smatch = NULL; + } + sfree (this); + } + + sfree (il); + il = NULL; +} /* void ignorelist_destroy (ignorelist_t *il) */ + +/* + * set ignore state of the ignorelist_t + */ +void ignorelist_set_invert (ignorelist_t *il, int invert) +{ + if (il == NULL) + { + DBG("ignore call with ignorelist_t == NULL"); + return; + } + + il->ignore = invert ? 0 : 1; +} /* void ignorelist_set_invert (ignorelist_t *il, int ignore) */ + +/* + * append entry into ignorelist_t + * return 1 for success + */ +int ignorelist_add (ignorelist_t *il, const char *entry) +{ + int ret; + size_t entry_len; + + if (il == NULL) + { + DBG ("add called with ignorelist_t == NULL"); + return (1); + } + + entry_len = strlen (entry); + + /* append nothing */ + if (entry_len == 0) + { + DBG("not appending: empty entry"); + return (1); + } + +#if HAVE_REGEX_H + /* regex string is enclosed in "/.../" */ + if ((entry_len > 2) && (entry[0] == '/') && entry[entry_len - 1] == '/') + { + char *entry_copy; + + /* We need to copy `entry' since it's const */ + entry_copy = smalloc (entry_len); + memset (entry_copy, '\0', entry_len); + strncpy (entry_copy, entry + 1, entry_len - 2); + + DBG("I'm about to add regex entry: %s", entry_copy); + ret = ignorelist_append_regex(il, entry_copy); + sfree (entry_copy); + } + else +#endif + { + DBG("to add entry: %s", entry); + ret = ignorelist_append_string(il, entry); + } + + return (ret); +} /* int ignorelist_add (ignorelist_t *il, const char *entry) */ + +/* + * check list for entry + * return 1 for ignored entry + */ +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) + return (0); + + if ((entry == NULL) || (strlen (entry) == 0)) + return (0); + + /* traverse list and check entries */ + for (traverse = il->head; traverse != NULL; traverse = traverse->next) + { +#if HAVE_REGEX_H + if (traverse->rmatch != NULL) + { + if (ignorelist_match_regex (traverse, entry)) + return (il->ignore); + } + else +#endif + { + if (ignorelist_match_string (traverse, entry)) + return (il->ignore); + } + } /* for traverse */ + + return (1 - il->ignore); +} /* int ignorelist_match (ignorelist_t *il, const char *entry) */ + diff --git a/src/utils_ignorelist.h b/src/utils_ignorelist.h new file mode 100644 index 00000000..b47b55ad --- /dev/null +++ b/src/utils_ignorelist.h @@ -0,0 +1,70 @@ +/** + * collectd - src/utils_ignorelist.h + * Copyright (C) 2006 Lubos Stanek + * + * This program is free software; you can redistribute it and/ + * or modify it under the terms of the GNU General Public Li- + * cence as published by the Free Software Foundation; either + * version 2 of the Licence, or any later version. + * + * This program is distributed in the hope that it will be use- + * ful, but WITHOUT ANY WARRANTY; without even the implied war- + * ranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public Licence for more details. + * + * You should have received a copy of the GNU General Public + * Licence along with this program; if not, write to the Free + * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, + * USA. + * + * Authors: + * Lubos Stanek + **/ +/** + * ignorelist handles plugin's list of configured collectable + * entries with global ignore action + **/ + +#ifndef UTILS_IGNORELIST_H +#define UTILS_IGNORELIST_H 1 + +#include "collectd.h" + +#if HAVE_REGEX_H +# include +#endif + +/* public prototypes */ + +struct ignorelist_s; +typedef struct ignorelist_s ignorelist_t; + +/* + * create the ignorelist_t with known ignore state + * return pointer to ignorelist_t + */ +ignorelist_t *ignorelist_create (int invert); + +/* + * free memory used by ignorelist_t + */ +void ignorelist_free (ignorelist_t *il); + +/* + * set ignore state of the ignorelist_t + */ +void ignorelist_set_invert (ignorelist_t *il, int invert); + +/* + * append entry to ignorelist_t + * returns zero on success, non-zero upon failure. + */ +int ignorelist_add (ignorelist_t *il, const char *entry); + +/* + * check list for entry + * return 1 for ignored entry + */ +int ignorelist_match (ignorelist_t *il, const char *entry); + +#endif /* UTILS_IGNORELIST_H */ diff --git a/src/vserver.c b/src/vserver.c index 694b6d02..3a77961b 100644 --- a/src/vserver.c +++ b/src/vserver.c @@ -343,11 +343,17 @@ static void vserver_read (void) DIR *proc; struct dirent *dent; /* 42 */ + static complain_t complain_obj; + errno = 0; - if (NULL == (proc = opendir (PROCDIR))) { - syslog (LOG_ERR, "Cannot open '%s': %s", PROCDIR, strerror (errno)); + if (NULL == (proc = opendir (PROCDIR))) + { + plugin_complain (LOG_ERR, &complain_obj, "vserver plugin: " + "fopen (%s) failed: %s", PROCDIR, strerror (errno)); return; } + plugin_relief (LOG_NOTICE, &complain_obj, "vserver plugin: " + "fopen (%s) succeeded.", PROCDIR); while (NULL != (dent = readdir (proc))) { int len;