dizzyd719 has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to create a simple network monitoring tool with perl.

I've tried Net:Ping and it works but for some reason isn't successfully pinging my all my devices. ( I've tried rebinding, changing timeouts... etc) so I've resorted to using system ping (solaris)

I'm trying to get the pings out and complete as fast as possible so I'm calling a system command with "&" and for some reason the program isn't getting the correct exit statuses.

If I remove "&" everything work correctly, but takes minutes to run instead of seconds

use Time::HiRes qw( time ); $MDIR="/tmp/"; #save list into an array open(LIST, "$MDIR/list"); @list=<LIST>; close LIST; @updevices=grep(!/known down|unmanaged/i,@list); my $start = Time::HiRes::gettimeofday(); foreach $element (@updevices) { #hostname:status:ip @ip = split(':',$element); system("ping $ip[2] 1 &"); $result = $?; print "ping $ip[0] $ip[2] $result\n"; if ( $result != 0) { print "$ip[0] is dead\n"; } } my $end = Time::HiRes::gettimeofday(); printf("%.2f\n", $end - $start);

Replies are listed 'Best First'.
Re: Problem with exit status of bash script
by Athanasius (Archbishop) on Dec 04, 2014 at 04:41 UTC

    Hello dizzyd719, and welcome to the Monastery!

    If your code:

    system("ping ... &"); $result = $?;

    worked as you expect, what advantage would there be in backgrounding the ping process? Whether running in the foreground or the background, it would still have to complete before the system call returned.

    But with ping backgrounded you report a significant speedup. I suspect what is happening is this: the call to system("ping ... &") spawns a shell subprocess (1) which in turn spawns the call to ping as a background process (2). Process (1) then immediately returns to the system call a status code indicating whether process (2) was spawned successfully. So of course you aren’t getting the correct exit statuses, since the return values from (2) never make it back to your Perl script.

    The speedup you are seeing suggests that you will indeed benefit from running the ping commands in parallel (asynchronously). So you need another way to do this and get their exit statuses. Have a look at fork or a module such as Parallel::ForkManager. (And see perlfaq8#How-do-I-start-a-process-in-the-background%3f.)

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      Thanks, yeah after spitting out the errors codes (always 0) I realized that I wasn't getting the correct codes. I started looking into the "fork" option and just figuring out how to communicate between processes.

      do you have any good docs for me to read about IPC in perl? my research so far is leading me into "ALERTING A PROCESS VIA A SIGNAL"

Re: Problem with exit status of bash script
by Anonymous Monk on Dec 04, 2014 at 06:12 UTC
    Athanasius is correct, & is a shell thing and doesn't do what you want (man bash: "If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0."). And you don't want the shell in the first place. Use a module, or just use fork, exec and wait... this is what bash does anyway, except it doesn't wait in your script, so you don't get ping's exit code.

    Something like that:

    use strict; use warnings; use autodie; use POSIX ':sys_wait_h'; my @sites = qw( www.perlmonks.org www.stackoverflow.com www.whitehouse.gov ); open my $stdout, '>&', \*STDOUT; # dup STDOUT open STDOUT, '>', '/dev/null'; # to make ping shut up my %kids; for my $site (@sites) { my $pid = fork; $pid == 0 and exec 'ping', '-c', '3', $site; $kids{$pid} = $site; } while ( ( my $kid = wait ) != -1 ) { print $stdout "$kids{$kid} returned $?\n"; }
    Output:
    www.stackoverflow.com returned 0 www.perlmonks.org returned 0 www.whitehouse.gov returned 0

      interesting code you have there. I need some helping understanding it though

      after you fork, does the child process actually modify "my %kids" ? I tried to do something similar with an array, but when I called it I got back a reference to an array. do you know why? (code is below)

      in your second loop, you wait for the kids to finish their process and print to stdout the actual return value, is this possible to track? for example I want to have this run all day so I'm thinking I could keep a hash that has these return values and times for tracking purposes

      foreach $element (@updevices){ @ip = split(':',$element); die "Could not fork()\n" unless defined ($pid = fork); if ($pid) { push @pids, $pid; next;} system("ping $ip[2] 1 > /dev/null"); $result = $?; print "ping $ip[0] $ip[2] $result\n"; if ( $result != 0) { print "$ip[0] is dead\n"; push(@dead, $ip[0]); } exit; }
        after you fork, does the child process actually modify "my %kids" ?
        No it doesn't because "The 'exec' function executes a system command and never returns". So
        ... my $pid = fork; $pid == 0 and exec 'ping', '-c', '3', $site; # child ($pid 0) never reaches code below. # exec doesn't return, it can only exit # or it can fail to find ping, but I use autodie # (for this kind of program that's probably # not a great idea in something # that can be called 'production'... but w/e) $kids{$pid} = $site; # only parent gets here ...
        I tried to do something similar with an array, but when I called it I got back a reference to an array. do you know why? (code is below)
        What did you expect? Also I don't see any reference to an array (reference in Perl is something different). Anyway, parent can use the array @pids because why not.
        foreach $element (@updevices){ @ip = split(':',$element); die "Could not fork()\n" unless defined ($pid = fork); if ($pid) { push @pids, $pid; next;} # ^ this is parent code, it pushes $pid to array # and goes back to the top of the loop # below is child code # it does something and then exits system("ping $ip[2] 1 > /dev/null"); $result = $?; print "ping $ip[0] $ip[2] $result\n"; if ( $result != 0) { print "$ip[0] is dead\n"; push(@dead, $ip[0]); } exit; # ^ child exits here # and basically stops (that's how exit works, after all) # and its exit code is 0; see perdoc -f exit } # after parent is done with the loop # it continues here # it has the array @pids # and all other global vars that your script # is full of...
        BTW, note that $? is not exactly exit code. See the manual for details.
        in your second loop, you wait for the kids to finish their process and print to stdout the actual return value, is this possible to track? for example I want to have this run all day so I'm thinking I could keep a hash that has these return values and times for tracking purposes
        There are probably tons of modules on CPAN that help to automatize these kinds of things... I'd really recommend to look there.

        If not, then you probably should use signal handlers ($SIG{CHLD} specifically). See peripc.

Re: Problem with exit status of bash script
by NetWallah (Canon) on Dec 04, 2014 at 06:14 UTC
    Here is some old code that does parallel ping using perl Threads.

            "You're only given one little spark of madness. You mustn't lose it."         - Robin Williams

Re: Problem with exit status of bash script
by karlgoethebier (Abbot) on Dec 04, 2014 at 10:29 UTC
    ..."pinging my all my devices..."

    That's what fping is made for:

    fping -C 1 perlmonks.org cpan.org stackoverflow.com stackoverflow.com : [0], 84 bytes, 89.3 ms (89.3 avg, 0% loss) perlmonks.org : [0], 84 bytes, 163 ms (163 avg, 0% loss) cpan.org : [0], 84 bytes, 155 ms (155 avg, 0% loss) perlmonks.org : 163.50 cpan.org : 155.29 stackoverflow.com : 89.33 fping -C 1 -q perlmonks.org cpan.org stackoverflow.com perlmonks.org : 162.75 cpan.org : 155.49 stackoverflow.com : 88.75

    Edit: Fixed funny typo: perlmonks.

    Regards, Karl

    «The Crux of the Biscuit is the Apostrophe»