2010-07-03 04:13:18 +02:00
|
|
|
#!/usr/bin/env perl
|
|
|
|
package Munin::Plugin::Multiping::Async;
|
|
|
|
use 5.10.0;
|
|
|
|
use MooseX::POE;
|
|
|
|
use MooseX::POE::SweetArgs qw(event);
|
|
|
|
use POE::Quickie;
|
|
|
|
use Munin::Plugin;
|
|
|
|
use Storable;
|
|
|
|
use Digest;
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
multiping_async - Like the multiping plugin but runs asynchronously
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
munin-run multiping_async
|
|
|
|
|
|
|
|
=head1 CONFIGURATION
|
|
|
|
|
|
|
|
The following environment variables are used:
|
|
|
|
|
2014-12-05 00:37:42 +01:00
|
|
|
host - Whitespace-separated list of hosts to ping
|
2010-07-03 04:13:18 +02:00
|
|
|
times - How many times to ping the hosts, 3 by default
|
2010-07-18 12:49:33 +02:00
|
|
|
timeout - The ping timeout (ping -W), 1 by default (ignored on Solaris)
|
2010-07-03 04:13:18 +02:00
|
|
|
title - The graph_title to use for the munin RRD graph
|
|
|
|
category - What category the graph should be in, network by default
|
|
|
|
|
|
|
|
Configuration example, ping all the Linode clusters:
|
|
|
|
|
|
|
|
# An optional custom category
|
|
|
|
[multiping_async_*]
|
|
|
|
env.category ping
|
|
|
|
|
|
|
|
[multiping_async_linode]
|
|
|
|
# From http://www.linode.com/speedtest/
|
|
|
|
env.title Ping times to all the Linode clusters
|
|
|
|
env.host london1.linode.com newark1.linode.com atlanta1.linode.com dallas1.linode.com fremont1.linode.com
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
Like the L<munin
|
|
|
|
multiping|http://munin-monitoring.org/browser/people/janl/src/node/node.d/multiping>
|
|
|
|
plugin except that it runs L<ping(1)> asynchronously with POE, and you
|
|
|
|
can add/remove hosts later on without screwing up your RRD files
|
|
|
|
(multiping reports statistics based on the order of hosts in
|
|
|
|
C<hosts=>).
|
|
|
|
|
|
|
|
This plugin used to use L<POE::Component::Client::Ping> but I switched
|
|
|
|
away from it due to having odd timing issues with it, and it had to
|
|
|
|
run as root.
|
|
|
|
|
|
|
|
This plugin requires the L<MooseX::POE> and L<POE::Quickie> modules
|
2010-07-18 12:49:33 +02:00
|
|
|
from CPAN. It has been tested with the Linux, FreeBSD and Solaris
|
|
|
|
L<ping(1)> implementations.
|
2010-07-03 04:13:18 +02:00
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
|
|
|
E<AElig>var ArnfjE<ouml>rE<eth> Bjarmason <avar@cpan.org>
|
|
|
|
|
|
|
|
=head1 LICENSE
|
|
|
|
|
|
|
|
This program is in the public domain.
|
|
|
|
|
|
|
|
=head1 MAGIC MARKERS
|
|
|
|
|
|
|
|
#%# family=manual
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
has graph_title => (
|
|
|
|
isa => 'Str',
|
|
|
|
is => 'ro',
|
|
|
|
default => $ENV{title} // 'Ping times',
|
|
|
|
documentation => 'The munin graph_title',
|
|
|
|
);
|
|
|
|
|
|
|
|
has hosts => (
|
|
|
|
isa => 'ArrayRef',
|
|
|
|
is => 'ro',
|
|
|
|
auto_deref => 1,
|
|
|
|
default => sub {
|
|
|
|
my $host = $ENV{host} // '';
|
|
|
|
return [ split /\s+/, $host ]
|
|
|
|
},
|
|
|
|
documentation => "Hosts we're going to ping",
|
|
|
|
);
|
|
|
|
|
|
|
|
has times => (
|
|
|
|
isa => 'Int',
|
|
|
|
is => 'ro',
|
|
|
|
default => $ENV{times} // 3,
|
|
|
|
documentation => "How many times we ping each host (ping -c)",
|
|
|
|
);
|
|
|
|
|
|
|
|
has timeout => (
|
|
|
|
isa => 'Int',
|
|
|
|
is => 'ro',
|
|
|
|
default => $ENV{timeout} // 1,
|
|
|
|
documentation => "How long until ping timeouts (ping -W)",
|
|
|
|
);
|
|
|
|
|
|
|
|
has category => (
|
|
|
|
isa => 'Str',
|
|
|
|
is => 'ro',
|
|
|
|
default => $ENV{category} // 'network',
|
|
|
|
documentation => "What munin category we should appear in",
|
|
|
|
);
|
|
|
|
|
|
|
|
has should_config => (
|
|
|
|
isa => 'Bool',
|
|
|
|
is => 'ro',
|
|
|
|
default => sub { defined $ARGV[0] and $ARGV[0] eq "config" },
|
|
|
|
documentation => 'Spew out config section?',
|
|
|
|
);
|
|
|
|
|
|
|
|
has response => (
|
|
|
|
isa => 'HashRef',
|
|
|
|
is => 'ro',
|
|
|
|
auto_deref => 0,
|
|
|
|
default => sub { +{} },
|
|
|
|
documentation => 'To store ping responses',
|
|
|
|
);
|
|
|
|
|
|
|
|
has statefile => (
|
|
|
|
isa => 'Str',
|
|
|
|
is => 'ro',
|
|
|
|
default => $ENV{MUNIN_STATEFILE},
|
|
|
|
documentation => 'Where we store state between invocations',
|
|
|
|
);
|
|
|
|
|
|
|
|
sub START {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
die "You must supply some hosts" unless @{ $self->hosts } > 0;
|
|
|
|
|
|
|
|
if ($self->should_config) {
|
|
|
|
$self->print_config;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-07-18 12:49:33 +02:00
|
|
|
for my $host ($self->hosts) {
|
2010-07-03 04:13:18 +02:00
|
|
|
POE::Quickie->new->run(
|
2010-07-18 12:49:33 +02:00
|
|
|
Program => [ $self->ping_arguments($host) ],
|
2010-07-03 04:13:18 +02:00
|
|
|
StdoutEvent => 'stdout',
|
|
|
|
ExitEvent => 'exit',
|
2010-07-18 12:49:33 +02:00
|
|
|
Context => $host,
|
2010-07-03 04:13:18 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-18 12:49:33 +02:00
|
|
|
sub ping_arguments {
|
|
|
|
my ($self, $host) = @_;
|
|
|
|
|
|
|
|
given ($^O) {
|
|
|
|
when ('solaris') {
|
|
|
|
return ('ping', '-s', $host, '64', $self->times);
|
|
|
|
}
|
|
|
|
default {
|
|
|
|
# Linux and FreeBSD
|
|
|
|
return ('ping', '-c', $self->times, '-W', $self->timeout, => $host);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-03 04:13:18 +02:00
|
|
|
event stdout => sub {
|
|
|
|
my ($self, $output, undef, $context) = @_;
|
|
|
|
|
|
|
|
given ($output) {
|
|
|
|
my $noslash = qr{[^/]+};
|
2010-07-18 12:49:33 +02:00
|
|
|
# Linux output: rtt min/avg/max/mdev = 7.218/7.255/7.293/0.030 ms
|
|
|
|
# BSD output : round-trip min/avg/max/stddev = 34.935/35.665/36.684/0.743 ms
|
|
|
|
# Solaris : round-trip (ms) min/avg/max/stddev = 5.82/5.95/6.01/0.11
|
|
|
|
when (m[= (?<min>$noslash)/(?<avg>$noslash)/(?<max>$noslash)/]) {
|
2010-07-03 04:13:18 +02:00
|
|
|
$self->response->{ $context } = $+{avg};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
event exit => sub {
|
|
|
|
my ($self, $code, $x, $context) = @_;
|
|
|
|
|
|
|
|
given ($code) {
|
|
|
|
when (0) {
|
|
|
|
die "Got no response from $context" unless exists $self->response->{ $context };
|
|
|
|
$self->yield( print_host => $context => $self->response->{ $context } );
|
|
|
|
}
|
|
|
|
default {
|
|
|
|
# Host down, probably
|
|
|
|
$self->yield( print_host => $context => 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
|
|
|
sub STOP {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
if (not $self->should_config and my $file = $self->statefile) {
|
|
|
|
my $res = $self->response;
|
|
|
|
my $ret = store($res, $file);
|
|
|
|
# use Data::Dumper;
|
|
|
|
# say Dumper { gonna_store => $res, ret => $ret, file => $file };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub print_config {
|
|
|
|
my ($self) = @_;
|
|
|
|
my $title = $self->graph_title;
|
|
|
|
my $times = $self->times;
|
|
|
|
my $category = $self->category;
|
|
|
|
print <<GRAPH;
|
|
|
|
graph_title $title
|
|
|
|
graph_args --base 1000 -l 0
|
|
|
|
graph_vlabel milliseconds
|
|
|
|
graph_category $category
|
|
|
|
graph_info Average ping times (over $times pings)
|
|
|
|
GRAPH
|
|
|
|
for my $host ($self->sorted_hosts) {
|
|
|
|
my $fieldname = $self->fieldname($host);
|
|
|
|
print <<HOST;
|
|
|
|
$fieldname.label $host
|
|
|
|
$fieldname.info Average ping time over $times pings for $host
|
|
|
|
$fieldname.draw LINE2
|
|
|
|
HOST
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
sub sorted_hosts {
|
|
|
|
my ($self) = @_;
|
|
|
|
|
|
|
|
my @hosts = $self->hosts;
|
|
|
|
my $state = $self->statefile;
|
|
|
|
|
|
|
|
given ($self->statefile) {
|
|
|
|
when (-e and -r) {
|
|
|
|
my $last_res = retrieve($_);
|
|
|
|
my @sorted = sort { $last_res->{$b} <=> $last_res->{$a} } keys %$last_res;
|
|
|
|
if ($last_res and @hosts == @sorted) {
|
|
|
|
return @sorted;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return @hosts;
|
|
|
|
}
|
|
|
|
|
|
|
|
event print_host => sub {
|
|
|
|
my ($self, $context, $time) = @_;
|
|
|
|
|
|
|
|
my $fieldname = $self->fieldname($context);
|
|
|
|
my $value = sprintf "%6.6f", $time;
|
|
|
|
|
|
|
|
say "$fieldname.value $value";
|
|
|
|
};
|
|
|
|
|
|
|
|
sub fieldname {
|
|
|
|
my ($self, $name) = @_;
|
|
|
|
my $sha1 = substr Digest->new("SHA-1")->add($name)->hexdigest, 0, 10;
|
|
|
|
return clean_fieldname($name) . '_' . $sha1;
|
|
|
|
}
|
|
|
|
|
|
|
|
no MooseX::POE;
|
|
|
|
Munin::Plugin::Multiping::Async->new;
|
|
|
|
POE::Kernel->run;
|