#!/usr/bin/perl -w
#-------------------------------------------------------------------------------
# smp_launcher.pl v1.0 - february 2010
# Author: F. LECERF
# see http://chicken.genouest.org for update or full documentation
#-------------------------------------------------------------------------------

use strict;
use Getopt::Long;
use Proc::Simple;

#-------------------------------------------------------------------------------
# CONSTANT value definition
#-------------------------------------------------------------------------------
$|=1;
my $soft = "smp_launcher.pl";
my $VERSION = "1.0";
my $year = "2010";

my $file = undef;
my $verbose = undef;
my $nb_cpu = undef;

my $unique_mode = undef;
my $repeat_mode = undef;
my $data_mode   = undef;

# Benchmark 
my $begin_time = times();


my %Options = (
        'file=s'   => \$file,
		'cpu=s'    => \$nb_cpu,
		'verbose'  => \$verbose,
		'unique'   => \$unique_mode,
		'repeat=s' => \$repeat_mode,
		'data=s'   => \$data_mode,
	      );
my %OptionsHelp = (
		    'file   ' => '-f [file], command file',
			'data   ' => '-d [data], file list of data file',
			'repeat ' => '-r [nb], repeat analysis X times',
			'unique ' => '-u, launch unique analysis from command file',
			'verbose' => '-v, verbose (optionnal)',
			'cpu    ' => '-c [nb], number of cpu',
		    );

#-------------------------------------------------------------------------------
sub usage ( $ ) {
    my ($msg)=@_;
    print "Error: $msg\n";
	print "--------------------------------------------------------------------------------\n";
	print "$soft $VERSION ($year)\n";
	print "--------------------------------------------------------------------------------\n";
	print "This script permits to launch several process in background\n";
	print "The command to launch in background have to be defined in a separate file\n";
	print "You can choose either to:\n";
	print "   - launch different analyses (specified in file) one time (-u)\n";
	print "   - launch same analyses with different input data (-d)\n";
	print "   - launch same analyses with same data many times (-r)\n";
	print "--------------------------------------------------------------------------------\n";
    print "Usage: $0 [options]\n";
    print "Options:\n";
    map {printf "\t$_: %s\n",$OptionsHelp{$_};} keys %OptionsHelp; 
    print "Please cite: Lecerf F., $year\n";
    exit(1);
}


#-------------------------------------------------------------------------------
# Check parameters
#-------------------------------------------------------------------------------
GetOptions(%Options);
defined $file	|| &usage('Command file required (-f)');
defined $nb_cpu	|| &usage('Number of cpu required (-c)');
((defined $unique_mode)||(defined $data_mode)||(defined $repeat_mode)) || &usage('Analysis parameter mode required (-u or -d or -r)');

print "--------------------------------------------------------------------------------\n";
print "$soft $VERSION ($year)\n";
print "--------------------------------------------------------------------------------\n";


#-------------------------------------------------------------------------------
# Loading commands from file
#-------------------------------------------------------------------------------
open (IN,$file) || die "cannot open COMMAND file: $file";
my @CommandList = ();

while (my $line = <IN>) {
	$line=~s/\s+$//;
	push (@CommandList,$line);
}
close (IN);

my $nb_command = scalar (@CommandList);
print "$nb_command command(s) loaded from file\n";


#-------------------------------------------------------------------------------
# Setting up ANALYSIS command
#-------------------------------------------------------------------------------
my @List_proc = ();
my @Command = ();

# same analysis many times - simulatin MODE
if (defined $repeat_mode) {
	print "setting up jobs in REPEAT mode...\n";
	my $proc = undef;
	
	my $single_command = shift (@CommandList);
	print "  - command: $single_command\n";
	print "  - preparing $repeat_mode job processes\n";
	for (my $i=1;$i<=$repeat_mode;$i++) {
		$proc = Proc::Simple->new();
		push (@List_proc,$proc);
		push (@Command,$single_command);
	}
}
# list of different commands
elsif (defined $unique_mode) {
	print "setting up jobs in UNIQUE mode...\n";
	my $proc = undef;
	my $nb_analysis = scalar (@CommandList);
	
	print "  - preparing $nb_analysis job processes\n";
	
	@Command = @CommandList;
	
	for (my $i=1;$i<=$nb_analysis;$i++) {
		$proc = Proc::Simple->new();
		push (@List_proc,$proc);
	}	
}
# same analysis on different datafiles
elsif (defined $data_mode) {
	print "setting up jobs in DATA mode...\n";
	my $proc = undef;
	
	# reading data filename from filelist
	my @Datafilename = ();
	open (DATA,"$data_mode") || die "cannot open datafile list: $data_mode!\n";
	while (my $line = <DATA>) {
		$line=~s/\s+$//;
		push (@Datafilename,$line);
	}
	close (DATA);
	
	my $single_command = shift (@CommandList);
	my $nb_analysis = scalar (@Datafilename);
	print "  - command: $single_command\n";
	print "  - preparing $nb_analysis job processes\n";
	
	# creating command line with data filename & list of process
	foreach my $datafile (@Datafilename) {
		my $commandline = $single_command." ".$datafile;
		push (@Command,$commandline);
		$proc = Proc::Simple->new();
		push (@List_proc,$proc);		
	}

}

#-------------------------------------------------------------------------------
# Launch ANALYSES
#-------------------------------------------------------------------------------

my %RunningJob = ();
my $nb_jobs = keys %RunningJob;
my $job_id = 0;
print "\nLaunching job processes in background...\n";
while (1) {
	my $nb_remaining_process = scalar(@List_proc);
	
	if (($nb_jobs < $nb_cpu)&&($nb_remaining_process != 0)) {
		# getting one job and one command
		my $job = shift (@List_proc);
		my $command = shift (@Command);
		
		# launching job
		$job_id++;
		my $o_stdout = "job_".$job_id.".stdout";
		my $o_stderr = "job_".$job_id.".stderr";
		$job->redirect_output($o_stdout,$o_stderr);			
		$job->start($command);
		
		my $pid = $job->pid;
		
		# store launched jobs
		$RunningJob{$job_id}{'command'} = $command;
		$RunningJob{$job_id}{'job'}     = $job;
		$RunningJob{$job_id}{'pid'}     = $pid;
		$nb_jobs++;
		print "  ++ job No. $job_id launched (pid $pid)\n";
	}
	foreach my $job_id (keys %RunningJob) {
		my $launched_job = $RunningJob{$job_id}{'job'};
		my $pid = $RunningJob{$job_id}{'pid'};
		
		#print " JOB No. $job_id with PID $pid\n";
		my $running = $launched_job->poll();
		if ($running == 0) {
			$nb_jobs--;
			print "  -- job No $job_id terminated (pid=$pid)\n";
			delete ($RunningJob{$job_id});
		}
	}
	last if ((keys %RunningJob == 0));
}
print "\nALL jobs performed.\n";

my $end_time=times();

printf "--\nExecution time: %.2f seconds CPU user-time\n",($end_time-$begin_time);


=head1 NAME

SMP Launcher - a script to run multiple job on a SMP computer

=head1 DESCRIPTION

This script will manage a queue job list (PERL programs or not, whatever) for you on a SMP computer. You can choose
between 3 analysis mode: run many different programs (batch mode), run the same program with different input data files (loop mode) or run
the same program many times (simulation mode). 

=head1 README

A full documentation is available at http://chicken.genouest.org

=head1 PREREQUISITES

This script requires the C<strict> module.  It also requires
C<Simple::Proc 1.26> module.

=pod OSNAMES

Linux (tested on Debian with PERL 5.10.0) and MacOS X (tested on 10.5.8 with PERL 5.8.8)

=head1 SCRIPT CATEGORIES

Unix/System_administration
ComputerScience

=cut