in reply to Pingger

Interesting problem. I had a similar one myself, except I don't really care which ones are up, just which ones are down, and I needed the results as a percentage so I could graph them.

I also discovered that trying to ping a huge number of hosts one at a time was painfully slow, so I wrote the following.

Bear in mind that this program is designed to pull the list of targets from a MySQL database, you could easily modify it to read in a config file though. It also doesn't handle forking the most efficient way possible, in that it doesn't ALWAYS keep $maxforks children running, it just runs them in bursts of $maxforks, but I find it's plenty fast enough even when pinging several hundred targets. I also used the system call to ping instead since I couldn't install CPAN modules on the machine it was running on. Again, that's probably a three-minute change.

Hope you find it interesting...
#!/usr/bin/perl -- # # ForkingPinger # Pings a list of remote hosts and calculates an uptime # percentage based on the number of responses. If run in # interactive mode, lists the sites that failed. # # (C) 2001 James Mancini <wfe@networkavatar.com> # Please send copies of modifications or improvements to # the author! # # Released under the same terms as perl itself. # # THIS PROGRAM IS PROVIDED WITH NO WARRANTY - USE AT YOUR # OWN RISK. # # 21 Mar 2001 my $Version = "0.2.2 - beta"; use DBI; use Getopt::Long; use POSIX ":sys_wait_h"; $|++; #autoflush output for speed ######################### # MAIN ######################### my (%opt, @targets); my $maxprocs = $opt{max} || 50; # set the maximum number of childre +n to spawn $main::avail = 0; &options(\%opt); &get_targets(); &check_remotes(); ######################### sub get_targets ######################### { ## Set up vars for main db call my ($dsn) = "DBI:mysql:routers:localhost"; my ($user_name) = "user"; my ($password) = "abcd1234"; my ($dbh, $sth); ## Connect to DB $dbh = DBI->connect ( $dsn, $user_name, $password, {RaiseError =>1} ); ## Fetch variables from database and write out config file ## The Database Query is option dependant if ($opt{cat}) { $sth = $dbh->prepare ("SELECT ip FROM routers WHERE category rlike + '$opt{cat}'"); } elsif ($opt{all}) { $sth = $dbh->prepare ("SELECT ip FROM routers"); } else { die "ERROR - Couldn't identify a target. $!"; } ## Go get it $sth->execute(); ## Process the data while( ($ip_add) = $sth->fetchrow_array() ) { push @targets, $ip_add; $noerror = "1"; } ## Disconnect the DB $sth->finish (); $dbh->disconnect(); unless ($noerror) {die "FATAL: Database did not return valid data! Che +ck Device name.\nError Response: $!\n";} } ######################### sub check_remotes ######################### { my $prox = int(scalar(@targets)/$maxprocs); my $prox_r = int(scalar(@targets)%$maxprocs); my $i = 0; if ($prox) { for ( 1 .. $prox ) # call the forking routine as many times as requ +ired to get most of the targets { &breed($maxprocs); } } if ($prox_r) { &breed($prox_r); # Then call the forking routine but this time on +ly enough to catch the modulo } # Calculate results and print to STDOUT my $uptime = ( $main::avail / scalar(@targets) ) * 100; # return res +ults as a percentage $uptime = sprintf('%.2f',$uptime); print "\nTOTAL:\t${uptime}% of devices are responding.\n"; } ######################### sub breed ($) ######################### { my $loop = shift; for ( 1 .. $loop ) { if ($pid=fork) { push @pids, $pid; $i ++; } elsif (defined $pid) { my $next = @targets[${i}]; &ping_remote($next); exit; } } foreach my $childpid (@pids) { waitpid($childpid, 0); $main::avail += ! $?; # 0 means success, non-zero means failure } } ######################### sub ping_remote($) ######################### { my $ip = shift; my $response = `ping -n -w 5 -i .3 $ip -c 3 | grep round-trip`; chomp $response; if ($response) { $response = 0; } else { $response = 1; } # 0 means su +ccess, non-zero means failure if (-t and $response) { print "Failed connect to $ip\n"; } exit($response); } ######################## sub options () ######################## { my $opt = shift; GetOptions( $opt, 'cat=s', 'max=s', 'all', ); $opt{cat} =~ s/[\"\;]//; # remove quotes and semicolons from querie +s unless (defined $opt{cat} || $opt{all}) #print usage if options not + given { print <<ECHO; ForkingPinger, Version $Version --------- Pulls a category from the database and checks it for availability, ret +urning a percentage. Designed to be run in batch mode but can be used at the command line f +or debugging or testing purposes. Highly parallel. Automatically prints the IP addresses of failed sites if used from the + command line. Usage: $0 [options] Options: --cat=name Pull all devices of category matching regexp 'name' from DB and test a +vailability. --all Pull all devices from DB and test availability. --max=num Spawn no more than num processes in parallel (default = 50) ECHO exit(1); } }
Update: Fixed a little typo I spotted. Oops.
Update2: Fixed a bigger omission: the -all switch was broken. I wasn't considering the option when validating command line switches. Oops again.
Signature void where prohibited by law.

Replies are listed 'Best First'.
Re: Forking Pinger
by MeowChow (Vicar) on Apr 26, 2001 at 08:53 UTC
    I can't resist mentioning that at first glance, I read your title as Porking Finger, a spoonerism which would certainly qualify as a Cool Use For PerlTM.
       MeowChow                                                                   
        "you can prick your finger, but don't finger your prick" - George Carlin