#!/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); }