Current Path : /usr/sbin/ |
Current File : //usr/sbin/munin-asyncd |
#! /usr/bin/perl # -*- cperl -*- # # Copyright (C) 2010 Steve Schnepp # # 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; version 2 dated June, # 1991. # # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. # use strict; use warnings; use IO::Socket; use IO::File; use File::Path qw(mkpath); use Getopt::Long; use Pod::Usage; use List::Util qw(min max); use Munin::Common::Daemon; use Munin::Node::SpoolWriter; my $host = "localhost:4949"; my $metahostname; my $SPOOLDIR = "/var/lib/munin/spool"; my $intervalsize = 86400; my $timeout = 3600; my $minrate = 300; my $retaincount = 7; # Asyncd collects data from plugins periodically. We want to avoid skipping the next value due to # slight deviations of this period. Thus we tolerate a slightly shorter time distance between two # successive requests for values from a plugin. my $min_update_rate_threshold = 0.75; my $nocleanup; my $do_fork; my $verbose; my $debug; my $help; GetOptions( "host=s" => \$host, "spooldir|s=s" => \$SPOOLDIR, "interval|i=i" => \$intervalsize, "timeout=i" => \$timeout, "minrate=i" => \$minrate, "retain|r=i" => \$retaincount, "fork" => \$do_fork, "help|h" => \$help, "verbose|v" => \$verbose, "nocleanup|n" => \$nocleanup, "debug" => \$debug, ) or pod2usage(1); if ($help) { pod2usage(1); } # Debug implies Verbose $verbose = 1 if $debug; unless (-d $SPOOLDIR) { mkpath($SPOOLDIR, { verbose => $verbose, } ) or die ("Cannot create '$SPOOLDIR': $!"); } my $process_name = "main"; my $sock = new IO::Socket::INET( PeerAddr => "$host", Proto => 'tcp' ); if (!$sock) { print STDERR "[$$][$process_name] Failed to connect to munin-node - trying again in a few seconds ...\n" if $verbose; sleep 20; $sock = new IO::Socket::INET( PeerAddr => "$host", Proto => 'tcp' ) || die "Error connecting to munin node ($host): $!"; } my $nodeheader = <$sock>; print $sock "quit\n"; close ($sock); ( $metahostname ) = ( $nodeheader =~ /munin node at (\S+)\n/); $metahostname = "unknown" unless $metahostname; my $spoolwriter = Munin::Node::SpoolWriter->new( spooldir => $SPOOLDIR, interval_size => $intervalsize, interval_keep => $retaincount, hostname => $metahostname, ); $0 = "munin-asyncd [$metahostname] [idle]"; my @plugins; { print STDERR "[$$][$process_name] Reading config from $host\n" if $verbose; my $sock = new IO::Socket::INET( PeerAddr => "$host", Proto => 'tcp' ) || die "Error creating socket: $!"; local $0 = "munin-asyncd [$metahostname] [list]"; print STDERR "[sock][>] cap multigraph\n" if $debug; print $sock "cap multigraph\n"; print STDERR "[sock][>] list\n" if $debug; print $sock "list\n"; <$sock>; # Read the first header comment line <$sock>; # Read the multigraph response line my $plugins_line = <$sock>; chomp($plugins_line); { my $fh_list = IO::File->new( "$SPOOLDIR/munin-daemon.list", "w", ); my $sanitised_plugins_line = $plugins_line; $sanitised_plugins_line =~ s/[^_A-Za-z0-9 ]/_/g; print $fh_list $sanitised_plugins_line; print $fh_list "\n"; } @plugins = split(/ /, $plugins_line); } my $keepgoing = 1; sub termhandler() { $keepgoing = 0; } # Q&D child collection $SIG{CHLD} = 'IGNORE'; $SIG{HUP} = 'IGNORE'; $SIG{INT} = 'termhandler'; $SIG{TERM} = 'termhandler'; # now, update regularly... # ... but each plugin in its own process to avoid delay-leaking my %last_updated; my $last_cleanup=0; # all preparations are finished: we can notify our caller, that we are ready Munin::Common::Daemon::emit_sd_notify_message(); MAIN: while($keepgoing) { my $when = time; # start the next run close to the end of a munin-node update operation # (i.e. try to avoid overlapping activities) my $when_next = int((int($when / $minrate) + 0.75) * $minrate); while ($when_next <= $when) { $when_next = $when_next + $minrate; } my $sock; PLUGIN: foreach my $plugin (@plugins) { # See if this plugin should be updated my $plugin_rate = $spoolwriter->get_metadata("plugin_rates/$plugin") || 300; if ($when < ($last_updated{$plugin} || 0) + ($plugin_rate * $min_update_rate_threshold)) { # not yet, next plugin next; } # Should update it $last_updated{$plugin} = $when; if ($do_fork && fork()) { # parent, return directly next PLUGIN; } unless ($sock) { $sock = new IO::Socket::INET( PeerAddr => "$host", Proto => 'tcp' ); unless ($sock) { if ($do_fork) { die "Error creating socket: $!"; } else { warn "Error creating socket: $!, moving to next plugin to try again"; next; } } <$sock>; # skip header } # Setting the command name for a useful top information $process_name = "plugin:$plugin"; local $0 = "munin-asyncd [$metahostname] [$process_name]"; fetch_data($plugin, $when, $sock); # We end here if we forked last MAIN if $do_fork; } print STDERR "[$$][$process_name][>] quit\n" if $verbose; print $sock "quit\n" if $sock; print STDERR "[$$][$process_name] closing sock\n" if $verbose; $sock = undef; $spoolwriter->set_metadata("lastruntime", $when); # Clean spool dir if (!$nocleanup && $last_cleanup<(time - 600)) { $last_cleanup = time; $spoolwriter->cleanup(); } # Sleep until next plugin exec. my $sleep_sec = $when_next - time; # "sleep" expects an unsigned integer - thus we may not let a wrapped number splip through. if ($sleep_sec > 0) { print STDERR "[$$][$process_name] Sleeping $sleep_sec sec\n" if $verbose; sleep $sleep_sec; } else { print STDERR "[$$][$process_name] Already late : should sleep $sleep_sec sec\n" if $verbose; } } print STDERR "[$$][$process_name] Exiting\n" if $verbose; sub fetch_data { my $plugin = shift; my $when = shift; my $sock = shift; print STDERR "[$$][$process_name][>][$plugin] asking for config\n" if $verbose; print STDERR "[sock][>][$plugin] config $plugin\n" if $debug; print $sock "config $plugin\n"; my $output_rows = []; while(my $line = <$sock>) { chomp($line); print STDERR "[sock][<][$plugin] $line\n" if $debug; if ($line =~ m/^\./) { # Starting with . => end last; } push @$output_rows, $line; if ($line =~ m/^update_rate (\d+)/) { # The plugin has a special update_rate: overriding it # XXX - Doesn't take into account a per field update_rate # This has to be sent back to the master $spoolwriter->set_metadata("plugin_rates/$plugin", $1); } } print STDERR "[$$][$process_name][>][$plugin] asking for data\n" if $verbose; print STDERR "[sock][>][$plugin] fetch $plugin\n" if $debug; print $sock "fetch $plugin\n"; while(my $line = <$sock>) { chomp($line); print STDERR "[sock][<][$plugin] $line\n" if $debug; if ($line =~ m/^\./) { # Starting with . => end last; } # Save the line push @$output_rows, $line; } # Write the whole load into the spool $spoolwriter->write($when, $plugin, $output_rows); } __END__ =head1 NAME munin-asyncd - A program to spool munin-node calls =head1 SYNOPSIS munin-asyncd [options] Options: --host <hostname:port> Connect to this munin-node [localhost:4949] -s --spool <spooldir> Store the spooled data in this dir [/var/lib/munin-async] -i --interval <seconds> Override default interval size of one day [86400] --timeout <seconds> Wake up at least this number of seconds. [3600] --minrate <seconds> This is the minimal rate you want to poll a node [300] -r --retain <count> Specify number of interval files to retain [7] -n --nocleanup Disable automated spool dir cleanup --fork Do fork -v --verbose Be verbose -h --help View this message