Index: trunk/oarutils/Makefile
===================================================================
--- trunk/oarutils/Makefile	(revision 31)
+++ trunk/oarutils/Makefile	(revision 31)
@@ -0,0 +1,30 @@
+
+CC=mpiCC
+CCFLAGS=-O2
+PODFLAG=-c 'User Contributed OAR Documentation' -r 'LEGI Trokata'
+
+
+.PHONY: all clean distclean
+
+all: mpilauncher mpilauncher.1 oar-envsh.1 oar-dispatch.1 oar-parexec.1
+
+clean:
+	rm -f *.o
+
+distclean: clean
+	rm -f mpilauncher
+
+
+mpilauncher: mpilauncher.o
+	$(CC) $(CCFLAGS) -o $@ $<
+
+%.o: %.cpp
+	$(CC) $(CCFLAGS) -c $<
+
+%.1: %.cpp
+	sed -re 's#^[/ ]\* ?##; s#^// ?##' $< | pod2man -n $* $(PODFLAG) > $@
+
+%.1: %
+	pod2man -n $* $(PODFLAG) $< > $@
+
+# astyle --style=banner -s3 mpilauncher.cpp
Index: trunk/oarutils/mpilauncher.cpp
===================================================================
--- trunk/oarutils/mpilauncher.cpp	(revision 31)
+++ trunk/oarutils/mpilauncher.cpp	(revision 31)
@@ -0,0 +1,221 @@
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <vector>
+#include <string>
+#include <iostream>
+#include <fstream>
+
+#include <mpi.h>
+
+using namespace std;
+
+/*
+ * These two variables sets the command to launch in each sub-directory
+ * and the arguments required. The name of the root directory of the
+ * datasets is given as an argument when starting the MPIlauncher. const
+ * string
+ * commandToLaunch("$HOME/SOURCES/MES-PROJETS/MPILAUNCHER/appli.exe");
+ * const string("-l -info");
+ */
+
+/*
+ * getListOfDir(string rep, vector<string> &files)
+ *
+ * Gets the list of tjhe subdirectories in the directory rep and returns
+ * them in a vector array of strings. Directories ";" and ".." are removed
+ * from the vector array.
+ */
+int getListOfDir(string rep, vector < string > &files) {
+   DIR *dir;
+   struct dirent *ent;
+   int count;
+
+   count = 0;
+   dir = opendir(rep.c_str());
+   if (dir != NULL) {
+      /*
+       * search for the files within directory
+       */
+      while ((ent = readdir(dir)) != NULL)
+         if (ent->d_type == DT_DIR) {
+            if (strcmp(ent->d_name, ".") *
+                  strcmp(ent->d_name, "..")) {
+               count++;
+               files.push_back(string(ent->d_name));
+               }
+            }
+      closedir(dir);
+      }
+   else {
+      cerr << "Directory " << rep.c_str() << " not found" << endl;
+      }
+   return count;
+   }
+
+/*
+ * getListOfCommand(string rep, vector<string> &files) Gets the list of
+ * commands in the ascii file fich. One command per line (no wrap) in this
+ * first version.
+ */
+int getListOfCommand(const string & fich, vector < string > &commands) {
+   int count;
+   string line;
+
+   count = 0;
+   commands.clear();
+
+   std::ifstream infile(fich.c_str(), std::ios_base::in);
+   while (getline(infile, line, '\n')) {
+      // remove all trailing blanks
+      while (line.size() > 0 && isspace(line[line.size() - 1]))
+         line.erase(line.size() - 1);
+
+      // no empty line
+      if (line.size() > 0) {
+         commands.push_back(line);
+         count++;
+         }
+      }
+   return count;
+   }
+
+/*
+ * Main program
+ */
+int main(int argc, char **argv) {
+   vector < string > files = vector < string > ();
+   string rep; // root folder where to find subfolders
+   int rank, size, nbdir, stride;
+   int nbcmd;
+   /*
+    * These two variables sets the command to launch in each
+    * sub-directory and the arguments required. The name of the root
+    * directory of the datasets is given as an argument when starting the
+    * MPIlauncher.
+    */
+   string commandToLaunch;
+   string arguments;
+   string commandfile;
+
+   string finalCommand;	// command to execute
+
+   MPI_Init(&argc, &argv);	// starts MPI
+   MPI_Comm_rank(MPI_COMM_WORLD, &rank); // get current process id
+   MPI_Comm_size(MPI_COMM_WORLD, &size); // get number of processes
+
+   if (argc < 3) {
+      cout << "USAGE:" << endl;
+      cout << argv[0] << " root_directory  command [arguments]" <<
+           endl;
+      cout << argv[0] << " -f  command_file" << endl;
+      }
+   else {
+      if (strcmp(argv[1], "-f") == 0) {
+         /*
+          * processing a command file now.
+          */
+         commandfile = argv[2];
+         nbcmd = getListOfCommand(commandfile, files);
+         int reste;
+         stride = nbcmd / size;
+         for (unsigned int i = 0; i < stride; i++) {
+            cerr << "Process " << rank << " execute " <<
+                 files[stride * rank + i] << endl;
+            system(files[stride * rank + i].c_str());
+            }
+         // remaining command lines
+         reste = nbcmd - stride * size;
+         if (rank > 0 && rank <= reste) {
+            cerr << "Process " << rank << " execute " <<
+                 files[nbcmd - rank] << endl;
+            system(files[nbcmd - rank].c_str());
+            }
+         }
+      else {
+         /*
+          * processing a list of dir now
+          */
+         rep = string(argv[1]);
+         commandToLaunch = string(argv[2]);
+         for (int i = 3; i < argc; i++)
+            (arguments += argv[i]) += " ";
+
+         nbdir = getListOfDir(rep, files);
+
+         // Number of dir should divide by number of cpus
+         if (nbdir % size != 0) {
+            if (rank == 0)
+               cerr << nbdir <<
+                    " dataset(s) to process cannot fit on "
+                    << size << " processes" << endl <<
+                    "FAILED" << endl;
+            }
+         else {
+            // execute the command
+            stride = nbdir / size;
+            for (unsigned int i = 0; i < stride; i++) {
+               string finalCommand("cd ");
+               finalCommand += rep;
+               finalCommand += "/";
+               finalCommand +=
+                  files[stride * rank + i];
+               finalCommand += ";";
+               finalCommand += commandToLaunch;
+               finalCommand += " ";
+               finalCommand += arguments;
+               // cout<<"On "<<rank<<" execute "<<finalCommand<<endl;
+               system(finalCommand.c_str());
+               }
+            }
+         }
+      }
+   MPI_Finalize();
+
+   return 0;
+   }
+
+/*
+ * Documentation in Perl POD format (man perlpod)
+ *
+ * =head1 NAME
+ *
+ *  mpilauncher - parallel execute lot of small job via mpi
+ *
+ * =head1 SYNOPSIS
+ *
+ *  mpilauncher root_folder command [args]
+ *  mpilauncher -f command_file
+ *
+ * =head1 DESCRIPTION
+ *
+ * C<mpilauncher> need to be executed inside an MPI environment (mpirun),
+ * typically a cluster... Job process are divide by the number of core and
+ * are launched on each core via the "system" command one after other.
+ *
+ * There is two case: jobs are list and define in a file (option -f) or
+ * one command is launched inside a lot of folder. In this last case, you
+ * need to give the root folder path in which you have all your subfolder...
+ *
+ *
+ * =head1 SEE ALSO
+ *
+ * oar-dispatch, oar-parexec
+ *
+ *
+ * =head1 AUTHORS
+ *
+ * Written by Patrick Begou - Gabriel Moreau, Grenoble - France
+ *
+ *
+ * =head1 LICENSE AND COPYRIGHT
+ *
+ * GPL version 2 or later
+ *
+ * Copyright (C) 2011 Patrick Begou / LEGI - CNRS UMR 5519 - France
+ *
+ * =cut
+ *
+ */
Index: trunk/oarutils/oar-dispatch
===================================================================
--- trunk/oarutils/oar-dispatch	(revision 31)
+++ trunk/oarutils/oar-dispatch	(revision 31)
@@ -0,0 +1,190 @@
+#!/usr/bin/perl
+#
+# 2011/11/03 gabriel
+
+use strict;
+
+use Getopt::Long();
+use Pod::Usage;
+use Coro;
+use Coro::Signal;
+use Coro::Semaphore;
+
+my $task = 0;
+my $overload = 1.1;
+my $file = '';
+my $verbose;
+my $help;
+
+Getopt::Long::GetOptions(
+	'task=i'		=> \$task,
+	'overload=f'	=> \$overload,
+	'file=s'		=> \$file,
+	'verbose' 	=> \$verbose,
+	'help' 		=> \$help,
+	) || pod2usage(-verbose => 0);
+pod2usage(-verbose => 2) if $help;
+
+if ($task == 0) {
+	open(NODE_FILE, '<', "$ENV{OAR_NODE_FILE}") or die "can't open ENV{OAR_NODE_FILE}: $!";
+	$task++ while <NODE_FILE>;
+	close NODE_FILE;
+}
+
+my @job = ();
+open (JOB_LIST, '<', "$file") or die "can't open $file: $!";
+while (<JOB_LIST>) {
+	chomp;
+	next if m/^#/;
+	push @job, $_ if m/^\s*oarsub/;
+}
+close JOB_LIST;
+
+my $container_id=$ENV{OAR_JOB_ID};
+my $insert_oar_option = "-t inner=$container_id";
+
+# interactive job
+if (not $container_id > 1) {
+	$insert_oar_option = '';
+	$overload = 1;
+}
+
+
+my $finished = new Coro::Signal;
+my $job_active = new Coro::Semaphore 0;
+my $job_todo = new Coro::Semaphore 0;
+$job_todo->up for (@job);
+
+my %scheduled = ();
+
+async {
+	for my $job (@job) {
+		while ($job_active->count >= $task*$overload) {
+			cede;
+		}
+		$job =~ s/^\s*oarsub//;
+		print "oarsub $insert_oar_option $job" if $verbose;
+		my $job_id = `oarsub $insert_oar_option $job|grep ^OAR_JOB_ID|cut -f 2 -d '='`;
+		chomp $job_id;
+		if ($job_id > 1) {
+			$scheduled{$job_id}++;
+			$job_active->up;
+		}
+		cede;
+	}
+}
+
+async {
+	while () {
+		for my $job_id (keys %scheduled) {
+			my $is_finish = `oarstat -s -j $job_id`;
+			chomp $is_finish;
+			if ($is_finish =~ m/Terminated/) {
+				delete $scheduled{$job_id};
+				$job_active->down;
+				$job_todo->down;
+			}
+			cede;
+		}
+
+		$finished->send if $job_todo->count == 0;
+		cede;
+	}
+}
+
+cede;
+   
+$finished->wait;
+
+
+__END__
+
+=head1 NAME
+
+oar-dispatch - dispatch lot of small oar job
+
+=head1 SYNOPSIS
+
+ oar-dispatch [--task integer] [--overload real] --file filepath [--verbose]
+ oar-dispatch --help
+
+=head1 OPTIONS
+
+=over 12
+
+=item B<[-t|--task integer]>
+
+Number of task to do in parallel.
+Default to the line number of the file OAR_NODE_FILE.
+ 
+=item B<[-o|--overload real]>
+
+Number of OAR job to create / number of task.
+Some job are create in advance to start whenever it's possible.
+1.1 by default.
+
+=item B<[-f|--file filepath]>
+
+File name which content OAR job list
+
+=item B<[-v|--verbose]>
+ 
+=item B<[-h|--help]>
+
+=back
+
+Input job file name content can have
+
+ - empty line
+ - comment line begin with #
+ - oarsub command without -t option
+ 
+C<oar-dispatch> will add C<-t inner=container_id> in this command line,
+just after C<oarsub>.
+
+=head1 EXAMPLE
+
+Example where the file F<$HOME/test/subjob.txt> is a list of OAR script job (and can be executable but not need here).
+
+ oarsub -n test -l /core=1,walltime=00:05:00 $HOME/test/subjob1.oar
+ oarsub -n test -l /core=1,walltime=00:05:00 $HOME/test/subjob2.oar
+ oarsub -n test -l /core=1,walltime=00:05:00 $HOME/test/subjob3.oar
+ oarsub -n test -l /core=1,walltime=00:05:00 $HOME/test/subjob4.oar
+ ...
+ oarsub -n test -l /core=1,walltime=00:05:00 $HOME/test/subjob38.oar
+ oarsub -n test -l /core=1,walltime=00:05:00 $HOME/test/subjob39.oar
+ oarsub -n test -l /core=1,walltime=00:05:00 $HOME/test/subjob40.oar
+
+These jobs could be launch with
+
+ oarsub -t container -n test-container -l /core=6,walltime=00:35:00 "oar-dispatch -f ./subjob.list.txt"
+
+Total C<walltime> is defined by the formula:
+
+ total_walltime = subjob_walltime * total_subjob / core + global_delay
+
+In practise, C<oar-dispatch> take few second and each subjob run in less than it's walltime so
+
+ total_walltime < subjob_walltime * total_subjob / core
+
+If launch in interactif, C<overload> parameter is equal to 1,
+C<task> must be define
+and no inner container is add to the C<oarsub> command line.
+
+
+=head1 SEE ALSO
+
+oar-parexec, mpilauncher
+
+
+=head1 AUTHORS
+
+Written by Gabriel Moreau, Grenoble - France
+
+
+=head1 LICENSE AND COPYRIGHT
+
+GPL version 2 or later and Perl equivalent
+
+Copyright (C) 2011 Gabriel Moreau / LEGI - CNRS UMR 5519 - France
+
Index: trunk/oarutils/oar-envsh
===================================================================
--- trunk/oarutils/oar-envsh	(revision 31)
+++ trunk/oarutils/oar-envsh	(revision 31)
@@ -0,0 +1,83 @@
+#!/bin/bash
+#
+# This wrapper try to remove system's unmodified environment variables
+# and propagate user environments on the nodes.
+#
+# This script is licence under the same licence as OAR program
+#  GNU GENERAL PUBLIC LICENSE
+#  http://oar.imag.fr/
+#
+# 1.0 Nicolas Capit  (initial version for PATH and LD_LIBRARY_PATH)
+# 1.1 Patrick Begou  (all the environment excepted... that could make problems!)
+# 1.2 Gabriel Moreau (format, small bug and licence)
+#
+
+HOST=$1
+shift
+
+MYENV=$( env \
+   | grep "^[ABCDEFGHIJKLMNOPQRSTUVWXYZ]" \
+   | egrep -v "^USER=|^TERM=|^OAR|^SGE_|^LS_COLORS=\
+      |^ENV=|^BASH_ENV=|^HOSTNAME=|^LOGNAME=|^MAIL=\
+      |^MANPATH=|^OMPI_MCA_plm_rsh_agent=|^PWD=\
+      |^SHELL=|^SSH_|^SUDO_COMMAND=|^HOME=|^DISPLAY=\
+      |^SHLVL=" \
+   | sort \
+   | sed -e 's/^/export /; s/=/="/; s/$/"/' \
+   | tr [\\\n] [\;] )
+
+exec /opt/oar/current/bin/oarsh $HOST "$MYENV $@"
+
+exit
+
+################################################################
+
+Documentation in Perl POD format (man perlpod)
+
+=head1 NAME
+
+ oar-envsh - oarsh with env variable transmit
+
+=head1 SYNOPSIS
+
+ oar-envsh node command
+
+=head1 DESCRIPTION
+
+C<oar-envsh> need to be executed inside an OAR cluster.
+It's a simple wrapper around C<oarsh> command
+which is a wrapper around C<ssh> command!
+
+With C<oar-envsh>, almost all env variable are export
+in the new shell except the following:
+
+ USER TERM OAR* SGE_* LS_COLORS
+ ENV BASH_ENV HOSTNAME LOGNAME MAIL
+ MANPATH OMPI_MCA_plm_rsh_agent PWD
+ SHELL SSH_* SUDO_COMMAND= HOME DISPLAY
+ SHLVL
+
+Alway use C<oarsh>,
+only use C<oar-envsh> if really needed!
+
+
+=head1 SEE ALSO
+
+oarsh
+
+
+=head1 AUTHORS
+
+Written by :
+
+ Nicolas Capit, Grenoble - France
+ Patrick Begou - Gabriel Moreau, Grenoble - France
+
+
+=head1 LICENSE AND COPYRIGHT
+
+GPL version 2 or later
+
+Copyright (C) 2011 Patrick Begou / LEGI - CNRS UMR 5519 - France
+
+=cut
Index: trunk/oarutils/oar-parexec
===================================================================
--- trunk/oarutils/oar-parexec	(revision 31)
+++ trunk/oarutils/oar-parexec	(revision 31)
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+#
+# 2011/11/27 gabriel
+
+use strict;
+
+use Getopt::Long();
+use Pod::Usage;
+use Coro;
+use Coro::Semaphore;
+use Coro::Signal;
+use Coro::Channel;
+use Coro::Handle;
+use IO::File;
+use POSIX qw( WNOHANG WEXITSTATUS );
+
+my $file = '';
+my $verbose;
+my $switchio;
+my $help;
+my $oarsh = 'oarsh -q -T';
+
+Getopt::Long::GetOptions(
+   'file=s'   => \$file,
+   'verbose'  => \$verbose,
+   'help'     => \$help,
+   'oarsh'    => \$oarsh,
+   'switchio' => \$switchio,
+   ) || pod2usage( -verbose => 0 );
+pod2usage( -verbose => 2 ) if $help;
+pod2usage( -verbose => 2 ) if not -e $file;
+
+my @job = ();
+open( JOB_LIST, '<', "$file" ) or die "can't open $file: $!";
+while (<JOB_LIST>) {
+   chomp;
+   next if m/^#/;
+   push @job, $_ if not m/^\s*$/;
+   }
+close JOB_LIST;
+
+my $stderr = $ENV{OAR_STDERR};
+$stderr =~ s/\.stderr$//;
+my $stdout = $ENV{OAR_STDOUT};
+$stdout =~ s/\.stdout$//;
+
+my $finished = new Coro::Signal;
+my $job_todo = new Coro::Semaphore 0;
+$job_todo->up for (@job);
+
+my $ressources = new Coro::Channel;
+open( NODE_FILE, '<', "$ENV{OAR_NODE_FILE}" )
+   or die "can't open ENV{OAR_NODE_FILE}: $!";
+while (<NODE_FILE>) {
+   chomp;
+   $ressources->put($_);
+   }
+close NODE_FILE;
+
+my $job_num   = 0;
+my %scheduled = ();
+
+async {
+   for my $job (@job) {
+      my $node = $ressources->get;
+
+      $job_num++;
+
+      my $fh      = IO::File->new();
+      my $job_pid = $fh->open("| $oarsh $node >/dev/null 2>&1")
+         or die "don't start subjob: $!";
+
+      $fh->autoflush;
+      $fh = unblock $fh;
+
+      $scheduled{$job_pid} = { fh => $fh, node => $node, num => $job_num };
+
+      printf "start job %5i / %5i on node %s at %s\n",
+         $job_num, $job_pid, $node, time
+         if $verbose;
+
+      my ( $job_stdout, $job_stderr );
+      $job_stdout = ">  $stdout-$job_num.stdout" if $stdout ne '' and $switchio;
+      $job_stderr = "2> $stderr-$job_num.stderr" if $stderr ne '' and $switchio;
+
+      $fh->print("cd $ENV{OAR_WORKDIR}\n");
+      $fh->print("$job $job_stdout $job_stderr\n");
+      $fh->print("exit\n");
+      cede;
+      }
+   }
+
+async {
+   while () {
+      for my $job_pid ( keys %scheduled ) {
+         if ( waitpid( $job_pid, WNOHANG ) ) {
+            printf "end   job %5i / %5i on node %s at %s\n",
+               $scheduled{$job_pid}->{num},
+               $job_pid, $scheduled{$job_pid}->{node}, time
+               if $verbose;
+            close $scheduled{$job_pid}->{fh};
+            $ressources->put( $scheduled{$job_pid}->{node} );
+            $job_todo->down;
+            delete $scheduled{$job_pid};
+            }
+         cede;
+         }
+
+      $finished->send if $job_todo->count == 0;
+      cede;
+      }
+   }
+
+cede;
+
+$finished->wait;
+
+__END__
+
+=head1 NAME
+
+oar-parexec - parallel execute lot of small job
+
+=head1 SYNOPSIS
+
+ oar-parexec --file filepath [--verbose] [--switchio] [--oarsh sssh]
+ oar-parexec --help
+
+=head1 OPTIONS
+
+ --file	file name which content job list
+
+ --verbose
+
+ --switchio each small job will have it's own output STDOUT and STDERR
+            base on master OAR job with JOB_NUM inside. Example :
+
+            OAR.151524.stdout -> OAR.151524-JOB_NUM.stdout
+
+            where 151524 here is the master OAR_JOB_ID
+
+ -oarsh command use to connect a shell on a node
+        by default
+
+        oarsh -q -T
+
+ --help
+
+
+=head1 DESCRIPTION
+
+C<oar-parexec> need to be executed inside an OAR job environment.
+because it need the two environment variable that OAR define by
+default:
+
+ OAR_NODE_FILE path to a file which content one node by line
+
+ OAR_WORKDIR   dir to launch job and do a chdir inside
+
+Content for the job file (option C<--file>) could have:
+
+ - empty line
+ - comment line begin with #
+ - valid shell command
+
+Example where F<$HOME/test/subjob1.sh> is a shell script (executable).
+
+ $HOME/test/subjob1.sh
+ $HOME/test/subjob2.sh
+ $HOME/test/subjob3.sh
+ $HOME/test/subjob4.sh
+
+ $HOME/test/subjob38.sh
+ $HOME/test/subjob39.sh
+ $HOME/test/subjob40.sh
+
+These jobs could be launch by
+
+ oarsub -n test -l /core=6,walltime=00:35:00 "oar-parexec -f ./subjob.list.txt"
+
+
+=head1 SEE ALSO
+
+oar-dispatch, mpilauncher
+
+
+=head1 AUTHORS
+
+Written by Gabriel Moreau, Grenoble - France
+
+
+=head1 LICENSE AND COPYRIGHT
+
+GPL version 2 or later and Perl equivalent
+
+Copyright (C) 2011 Gabriel Moreau / LEGI - CNRS UMR 5519 - France
+
