Your IP : 3.16.69.52


Current Path : /usr/sbin/
Upload File :
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