#!/usr/bin/perl -w

=cut

=head1 NAME

ping - Plugin to monitor ping times

=head1 CONFIGURATION

 ping_args      - Arguments to ping (default "-c 2 -w 1")
 ping_args2     - Arguments after the host name (required for Solaris)
 ping           - Ping program to use
 ping6          - Ping program to use with IPv6
 hosts          - List of comma-separated hosts to ping (IPv4/6 address or FQDN)
                  You can prepend host name with:
                  'A:'    - to resolve all IPv4 addresses
                            and create an entry per address (requires 'host' program)
                  'AAAA:' - the same for IPv6 (requires 'host' program)
                  '6:'    - do not try to resolve, but use ping6 for this host
 names          - Friendly display name of each host given in the "hosts" parameter (comma-separated list).
                  If not set, "hosts" elements will be used
 fork           - If set to non-zero, fork each ping into parallel process to ping asynchronously

Configuration example

 [ping*]
 env.hosts mail.example.org,6:www.example.org,AAAA:search.example.org
 env.names Mail,Web,Search
 env.fork yes

Configuration example for Solaris

 [ping*]
 env.host www.example.org mail.example.org
 env.ping_args -s
 env.ping_args2 56 2

You can also append '_packetloss' to the plugin filename for packet loss statistics.

=head1 AUTHOR

Original Perl script Copyright (C) 2008 Thomas Gutzler (thomas.gutzler@gmail.com)
Original Shell script Copyright (C) 2004 Jimmy Olsen

=head1 LICENSE

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.

=head1 MAGIC MARKERS

#%# family=manual

=cut

use strict;
use Munin::Plugin;

my $ping = exists $ENV{ping} ? $ENV{ping} : "ping";
my $ping6 = exists $ENV{ping6} ? $ENV{ping6} : "ping6";
# Since ping sends a packet every second (-i 1) by default,
# we may need 2 total seconds (-w 2) for <1000msec replies
my $ping_args = exists $ENV{ping_args} ? $ENV{ping_args} : "-c 2 -w 2";
my $ping_args2 = exists $ENV{ping_args2} ? $ENV{ping_args2} : "";
my $fork = exists $ENV{fork} ? $ENV{fork} : 0;
my $packetloss_mode = ($0 =~ /_packetloss$/);

my @host_options = exists $ENV{hosts} ? split(/,/, $ENV{hosts}) : "A:www.google.com";

# bare addresses
my @host_addrs = @host_options;

# ping program to use
my @host_ping = ($ping) x @host_options;

# resolve record type, if specified
my @host_resolv = (0) x @host_options;

my $config_mode = ($ARGV[0] and $ARGV[0] eq 'config');
if ($config_mode) {
	print "graph_title " . ($packetloss_mode ? "Ping packet loss" : "Ping times") . "\n";
	print "graph_args --base 1000 -l 0\n";
	print "graph_vlabel " . ($packetloss_mode ? "%" : "seconds") . "\n";
	print "graph_category network\n";
	print "graph_info This graph shows ping " . ($packetloss_mode ? "packet loss" : "RTT") . " statistics.\n";
}

for (my $host_option = 0; $host_option < @host_options; ++$host_option) {
	my $h_addr = $host_options[$host_option];
	my @config = split(/:/, $h_addr, 2);
	if ($#config) {
		my $h_ping = $ping;
		my $h_resolv = 0;
		if ($config[0] eq "6") {
			# user wants this host to be pinged via IPv6
			$h_ping = $ping6;
			$h_addr = $config[1];
		} elsif ($config[0] eq "A") {
			# user wants this host to be resolved via IPv4
			$h_resolv = "A";
			$h_addr = $config[1];
		} elsif ($config[0] eq "AAAA") {
			# user wants this host to be resolved as IPv6
			$h_resolv = "AAAA";
			$h_addr = $config[1];
			$h_ping = $ping6;
		} elsif ($config[0] =~ /^[[:xdigit:]]+$/) {
			# this is IPv6 addr
			$h_ping = $ping6;
		} else {
			# let 'host' decide what resolve type do we need
			$h_resolv = $config[0];
			$h_addr = $config[1];
		}
		$host_addrs[$host_option] = $h_addr;
		$host_resolv[$host_option] = $h_resolv;
		$host_ping[$host_option] = $h_ping;
	}
}

# display names
my @host_names = exists $ENV{names} ? split(/,/, $ENV{names}) : @host_addrs;

if (@host_names != @host_addrs) {
	print "Warning: host names specified does not match addresses\n";
}

# forked children
my @children = ();

for (my $host_i = 0; $host_i < @host_addrs; ++$host_i) {
	my @h_addrs = ($host_addrs[$host_i]);
	my $h_ping = $host_ping[$host_i];
	my $h_resolv = $host_resolv[$host_i];
	my $h_name = ($host_names[$host_i] or $h_addrs[0]);

	if ($h_resolv) {
		# we have to resolve the host to (probably multiple) addresses
		my $h_base_addr = pop @h_addrs;
		my @host = `host -t $h_resolv $h_base_addr`;
		chomp @host;
		for (my $h_host_i = 0; $h_host_i < @host; ++$h_host_i) {
			my $h_host = $host[$h_host_i];
			my ($h_addr) = $h_host =~ /address (.*)$/;
			if ($h_addr) {
				push @h_addrs, $h_addr;
			}
		}
	}

	for (my $h_addr_i = 0; $h_addr_i < @h_addrs; ++$h_addr_i) {
		my $h_addr = $h_addrs[$h_addr_i];
		my $h_cur_name = $h_resolv ? $h_name . " ($h_addr)" : $h_name;
		my $h_norm_name = clean_fieldname($h_cur_name);
		if ($config_mode) {
			print "$h_norm_name.min 0\n$h_norm_name.label $h_cur_name\n$h_norm_name.draw LINE2\n";
                        my ( $warning, $critical ) =
                            get_thresholds($h_norm_name);
                        printf "%s.warning %s\n", $h_norm_name, $warning
                            if $warning;
                        printf "%s.critical %s\n", $h_norm_name, $critical
                            if $critical;
		} else {
			my $pid = $fork ? fork() : -1;
			if ($pid <= 0) {
				# either we can't fork, or don't want to, or we are child
				my @ping = `$h_ping $ping_args $h_addr $ping_args2`;
				chomp @ping;
				my $ping = join(" ", @ping);
				my $ping_time = "U";
				my $packet_loss = "U";
				$ping_time = ($1 / 1000) if ($ping =~ m@min/avg/max.*\s\d+(?:\.\d+)?/(\d+(?:\.\d+)?)/\d+(?:\.\d+)?@);
				$packet_loss = $1 if ($ping =~ /(\d+)% packet loss/);
				print "$h_norm_name.value ". ($packetloss_mode ? $packet_loss : $ping_time) . "\n";
				if ($pid == 0) {
					# this is a child process, don't forget to exit
					exit(0);
				}
			} else {
				# in parent
				push @children, $pid;
			}
		}
	}
}

for (my $child_i = 0; $child_i < @children; ++$child_i) {
	waitpid($children[$child_i], 0);
}
