-2

I have lots of hosts (around 40000+) that i need to test for ping using a perl script. There's a requirement to complete this verification before 3-4 minutes and i need to write/dump the ping results/output to a flat file. Another integration will be reading this output (may be tail) and generate some alerts.

I will be running this perl script on a machine with 16 CPU's (1 Socket, 8 Core's per Socket, and 2 Threads per Core). I have perl 5, version 14, subversion 1 (v5.14.1) I have used 'nmap' utility and i have timing like below:

Nmap done: 50233 IP addresses (39313 hosts up) scanned in 356.54 seconds ~ 6 Minutes

Nmap done: 79195 IP addresses (28178 hosts up) scanned in 641.93 seconds ~ 11 Minutes

Can I use multithreaded perl to speed up further? I have the Net::Ping module installed, but I am not interested in installing more custom modules.

Update##1

Based on 'Sobrique's' answer and sample code, I have the code like below that's working. Thanks a lot for sample as it helped me to know about Thread::Queue module in perl.

#!/usr/bin/perl

# Run under Oracle Perl, for DBI.

    BEGIN {
        my $ORACLE_HOME = "/opt/oracle/app/oracle/product/12.1.0/client_1";
        $ENV{ORACLE_HOME}=$ORACLE_HOME;

        die "ORACLE_HOME not set\n" unless $ENV{ORACLE_HOME};
        unless ($ENV{OrAcLePeRl}) {
        $ENV{OrAcLePeRl} = "$ENV{ORACLE_HOME}/perl";
        $ENV{PERL5LIB} = 
       "$ENV{PERL5LIB}:$ENV{OrAcLePeRl}/lib:$ENV{OrAcLePeRl}/lib/site_perl";
        $ENV{LD_LIBRARY_PATH} = 
      "$ENV{LD_LIBRARY_PATH}:$ENV{ORACLE_HOME}/lib32:$ENV{ORACLE_HOME}/lib";
        exec "$ENV{OrAcLePeRl}/bin/perl", $0, @ARGV;
    }
  }

use strict;
use warnings;
use threads;
use Thread::Queue;

my $process_q = Thread::Queue->new();

#insert tasks into thread queue.
open( my $input_fh, "<", "server_list" ) or die $!;
$process_q->enqueue(<$input_fh>);
close($input_fh);

my $OutputFile = "PingOutput.mout";
open (OUTPUT_FILEH, ">>$OutputFile") or die "Can't Open $OutputFile : $!";

  # Worker threads
  my $thread_limit = 25;

  # send markers.
  $process_q->enqueue(undef) for 1..$thread_limit;

   my @thr = map {
      threads->create(sub {
        while (defined (my $server = $process_q->dequeue())) {
                       chomp($server);
                       print OUTPUT_FILEH threads->self()->tid() . ":pinging$server\n";
                       my $result = `/usr/bin/ping -c 1 $server`;
                       print OUTPUT_FILEH $result;
        }
    });
} 1..$thread_limit;

# terminate.
$_->join() for @thr;
Kiran
  • 3
  • 3
  • 4
    Of course some code would be usefull in this situation – John Doe May 08 '17 at 11:12
  • As a first attempt, I would use [Parallel::ForkManager](https://metacpan.org/pod/Parallel::ForkManager) with 64 children (since your programs is going to mostly twiddle its thumbs). Try half and double to see if there is improvement. I know starvation is possible, but see if just chopping work equally does OK. – Sinan Ünür May 08 '17 at 13:08
  • *"I am not interested in installing more custom modules"* It sounds like you're completely exhausted after installing `Net::Ping`. It seems strange to reject the idea of another Perl module, even if it would enable you to achieve your goal. – Borodin May 08 '17 at 19:15
  • See https://nmap.org/book/man-performance.html for suggestions on speeding up nmap (starting with making sure you have the latest version) – ysth May 09 '17 at 03:17
  • Thanks a lot for the link and other suggestions. My issue with installing more perl modules is i am using the perl that comes packaged with 'Oracle'. (This because as i need Oracle DBD and installing it separately seems a big pain) – Kiran May 11 '17 at 06:19

1 Answers1

3

You can indeed use multithreading to speed things up further. This is a perfect scenario for using a worker-queue:

#!/usr/bin/perl

use strict;
use warnings;

use threads;

use Thread::Queue;

my $nthreads = 5;

my $process_q = Thread::Queue->new();
my $failed_q  = Thread::Queue->new();

#this is a subroutine, but that runs 'as a thread'.
#when it starts, it inherits the program state 'as is'. E.g.
#the variable declarations above all apply - but changes to
#values within the program are 'thread local' unless the
#variable is defined as 'shared'.
#Behind the scenes - Thread::Queue are 'shared' arrays.

sub worker {
    #NB - this will sit a loop indefinitely, until you close the queue.
    #using $process_q -> end
    #we do this once we've queued all the things we want to process
    #and the sub completes and exits neatly.
    #however if you _don't_ end it, this will sit waiting forever.
    while ( my $server = $process_q->dequeue() ) {
        chomp($server);
        print threads->self()->tid() . ": pinging $server\n";
        my $result = `/bin/ping -c 1 $server`;
        if ($?) { $failed_q->enqueue($server) }
        print $result;
    }
}

#insert tasks into thread queue.
open( my $input_fh, "<", "server_list" ) or die $!;
$process_q->enqueue(<$input_fh>);
close($input_fh);

#we 'end' process_q  - when we do, no more items may be inserted,
#and 'dequeue' returns 'undefined' when the queue is emptied.
#this means our worker threads (in their 'while' loop) will then exit.
$process_q->end();

#start some threads
for ( 1 .. $nthreads ) {
    threads->create( \&worker );
}

#Wait for threads to all finish processing.
foreach my $thr ( threads->list() ) {
    $thr->join();
}

#collate results. ('synchronise' operation)
while ( my $server = $failed_q->dequeue_nb() ) {
    print "$server failed to ping\n";
}

(note: My code, from another answer I gave: Perl daemonize with child daemons)

However - I don't think you'll find much that's faster than nmap as that already does parallelism in a way that's quite well well suited to network scanning scenarios.

Community
  • 1
  • 1
Sobrique
  • 52,974
  • 7
  • 60
  • 101
  • Thanks for the sample code. I got working code (as posted in Update1 in question) based on your sample. – Kiran May 11 '17 at 09:03
  • For Oracle::DBD module, i used Perl packaged with Oracle & it comes with 2.12 version of Thread::Queue.This version of Thread::Queue is not having 'end' sub-routine. Therefore i queued 'undef' and used it as a marker to end. Not sure if this is the correct way or not. Anyhow as you mentioned, the performance is not matching that of 'nmap'. So i will end up using 'nmap' only, but in the process got a bit understanding of Perl Threads. Thanks – Kiran May 11 '17 at 09:09
  • 1
    If you queue undef, you need to queue at least one undef per thread. But probably should just update Thread::Queue instead. – Sobrique May 11 '17 at 09:13