1#!/usr/bin/env perl
2#eval 'exec perl $0 $*'
3#    if 0;
4
5$prefix = '@prefix@';
6$exec_prefix = "@exec_prefix@";
7$bindir = "@bindir@";
8$scdatadir = "@scdatadir@";
9
10$pwd = `pwd`;
11chomp $pwd;
12$pwd =~ s/\/$//;
13
14use POSIX;
15
16# The path to the ccaffiene executable
17$ccafe = "@CCAFE_BIN@/ccafe-batch";
18
19# The threadgrp specialization
20$threadgrp = "none";
21
22# The mpi launch command
23$launch = "@CCALAUNCH@";
24
25# The number of tasks
26$ntask = 1;
27
28# A filename with a list of nodes.
29$nodefile = "";
30
31# A command line argument with a list of nodes.
32$nodes = "";
33
34# A format string to convert a node number to a node name
35$nodename = "%d";
36
37# The number of nodes each job will use.
38$nnodeperjob = "nnode";
39
40# The number of threads each process will use.
41$nthreadperproc = 1;
42
43# The number of processes to run on each node.
44$nprocpernode = 1;
45
46# The directory where the input file is to be found.
47$inputprefix = "";
48
49# The directory where the output file is to be placed.
50$outputprefix = "";
51
52# If true, then print out help info and exit
53$help = 0;
54
55# If true, then don't actually run anything
56$debug = 0;
57
58# If true, then print out extra info.
59$verbose = 0;
60
61# If true, then overwrite output files that seem up-to-date
62$rerun = 0;
63
64# If true, then do not overwrite any output file.
65$onlynew = 0;
66
67######################################################################
68
69use Getopt::Long;
70
71if (!GetOptions("launch=s" => \$launch,
72                "threadgrp=s" => \$threadgrp,
73                "nnodeperjob=i" => \$nnodeperjob,
74                "nthreadperproc=i" => \$nthreadperproc,
75                "nprocpernode=i" => \$nprocpernode,
76                "outputprefix=s" => \$outputprefix,
77                "inputprefix=s" => \$inputprefix,
78                "nodefile=s" => \$nodefile,
79                "nodes=s" => \$nodes,
80                "nodename=s" => \$nodename,
81                "help!" => \$help,
82                "rerun!" => \$rerun,
83                "onlynew!" => \$onlynew,
84                "debug!" => \$debug,
85                "verbose!" => \$verbose,
86      )) {
87    $help=1;
88}
89
90if ("$launch" eq "@" . "CCALAUNCH@") {
91    $launch = "mpirun [-hf %NODEFILE%] -n %NPROC% %CCARUNPROC% %CCAFE% --ccafe-rc %INPUT% --ccafe-remap-stdio --ccafe-outputdir %OUTPUT%";
92}
93
94$outputprefix =~ s/\/$//;
95$inputprefix =~ s/\/$//;
96
97######################################################################
98
99if ($help) {
100    print  "Usage: $ARGV[0] [options] [rcfile1.in] [rcfile2.in] ...\n";
101    print  "Options:\n";
102    print  "  --nnodeperjob n    run with n nodes per job (value: $nnodeperjob)\n";
103    print  "  --nprocpernode n   run with n procs per node (value: $nprocpernode)\n";
104    print  "  --nthreadperproc n use n threads per process (value: $nthreadperproc)\n";
105    print  "  --threadgrp grp    use the given threading layer (value: $threadgrp)\n";
106    print  "                     none: uses MPQC's default\n";
107    print  "                     proc: does a single threaded run\n";
108    print  "                     posix: use POSIX threads\n";
109    printf "  --launch cmd       use the given cmd to launch jobs--see below (value: %s)\n",
110          (($launch eq "")?"<not set>":$launch);
111    printf "  --nodefile file    a file listing nodes to use (value: %s)\n",
112          (($nodefile eq "")?"<not set>":$nodefile);
113    printf "  --nodes nodes      a command line list of machines to use (value: %s)\n",
114          (($nodes eq "")?"<not set>":$nodes);
115    printf "                     groups can be given as 8-10,12,15-17 for example\n";
116    printf "  --nodename fmt     converts node num to name (value: %s)\n", $nodename;
117    print  "  --rerun            overwrite output file, even if up-to-date\n";
118    print  "  --onlynew          do not overwrite output file, even if not up-to-date\n";
119    print  "  --inputprefix dir  path to prefix input files with\n";
120    print  "  --outputprefix dir path to prefix output files with\n";
121    print  "  --debug            don't actually run mpqc\n";
122    print  "  --help             print this help\n";
123    print  "\n";
124    print  "The launch command can contain special strings that will be substituted.\n";
125    print  "These are:\n";
126    print  "  %NPROC%    The number of processes to start.\n";
127    print  "  %NODEFILE% The name of a file containing the node names.\n";
128    print  "  %NODELIST% A comma separated list of node names.\n";
129    print  "  For these last two, if they are contained within square brackets\n";
130    print  "  and a substitution is not available, then everything within the\n";
131    print  "  the brackets is removed.\n";
132    print  "Examples of the launch argument:\n";
133    print  "  mpirun [-hf %NODEFILE%] -n %NPROC% %CCAFE% --ccafe-rc %INPUT% --ccafe-remap-stdio --ccafe-outp
134utdir %OUTPUT%\n";
135    print  "  mpirun [-H %NODELIST%] -n %NPROC% %CCAFE% --ccafe-rc %INPUT% --ccafe-remap-stdio --ccafe-outp
136utdir %OUTPUT%\n";
137    exit 0;
138}
139
140######################################################################
141
142@nodelist = ();
143if ($nodes ne "") {
144    $nodes =~ s/-/../g;
145    foreach my $i (eval $nodes) {
146        $nodelist[$#nodelist + 1] = sprintf "$nodename", $i;
147    }
148}
149elsif ("$nodefile" eq "" && exists($ENV{"PBS_NODEFILE"})) {
150    $nodefile=$ENV{"PBS_NODEFILE"};
151}
152if ("$nodefile" ne "" && -f "$nodefile") {
153    my %nodesfound = {};
154    open(NODEFILE,"<$nodefile");
155    while(<NODEFILE>) {
156        if (/(\S+)/) {
157            my $nodename = $1;
158            if (!exists($nodesfound{$nodename})) {
159                $nodelist[$#nodelist + 1] = $nodename;
160                $nodesfound{$nodename} = 1;
161            }
162        }
163    }
164    close(NODEFILE);
165}
166
167if ($#nodelist == -1) {
168    $nnode = 1;
169}
170else {
171    $nnode = $#nodelist + 1;
172}
173
174if ($nnodeperjob eq "nnode") {
175    $nnodeperjob = $nnode;
176}
177
178$nprocperjob = $nnodeperjob * $nprocpernode;
179
180$nnodeperjob = POSIX::ceil($nprocperjob / $nprocpernode);
181
182@jobnodes = ();
183%jobnnodes = {};
184%nodelist = {};
185%nodefile = {};
186$maxjobs = 0;
187while (($maxjobs + 1) * $nnodeperjob <= $nnode) {
188    $jobnnodes[$maxjobs++] = $nnodeperjob;
189}
190
191if ($maxjobs == 0) {
192    die "requested $nnodeperjob nodes but have $nnode nodes";
193}
194
195$nodesbegin = 0;
196foreach my $i (0..$maxjobs-1) {
197    my $nodesend = $nodesbegin + $jobnnodes[$i];
198    my @slice = (@nodelist)[$nodesbegin..($nodesend-1)];
199    $jobnodes{$i} = \@slice;
200    $nodesbegin = $nodesend;
201    foreach my $j (@slice) {
202        if ($nodelist{$i} eq "") { $nodelist{$i} = $j; }
203        else { $nodelist{$i} = sprintf "%s,%s", $nodelist{$i}, $j; }
204    }
205    $nodefile{$i} = ".tmp.nodefile.$$.$i";
206    open(NODEFILE,">" . $nodefile{$i});
207    foreach my $j (@slice) {
208        printf NODEFILE "%s\n", $j;
209    }
210    close(NODEFILE);
211}
212
213######################################################################
214
215if ($threadgrp eq "none" && $nthreadperproc > 1) {
216    $threadgrp = "posix";
217}
218
219if ($threadgrp eq "proc") {
220  $ENV{"THREADGRP"} = "<ProcThreadGrp>:()";
221}
222elsif ($threadgrp eq "posix") {
223  $ENV{"THREADGRP"} = "<PthreadThreadGrp>:(num_threads=$nthreadperproc)";
224}
225
226######################################################################
227
228$usingthreads = 0;
229if ($maxjobs > 1) {
230    require threads;
231    require threads::shared;
232    $usingthreads = 1;
233}
234
235######################################################################
236
237# autoflush output
238$| = 1;
239
240@allfiles = reverse(get_file_list());
241
242my @seqfiles : shared = ();
243my @files : shared = ();
244foreach my $file (@allfiles) {
245    $files[$#files+1] = $file;
246}
247
248printf "Running a maximum of %d jobs at a time.\n", $maxjobs;
249printf "Running %d processes per job.\n", $nprocperjob;
250printf "Running %d threads per process.\n", $nthreadperproc;
251foreach my $i (0..$maxjobs-1) {
252    print "Nodes in slot $i:";
253    foreach my $j (@{$jobnodes{$i}}) {
254        printf " \"%s\"", $j;
255    }
256    print "\n";
257}
258printenvvar("MESSAGEGRP");
259printenvvar("THREADGRP");
260printenvvar("MEMORYGRP");
261printenvvar("SCLIBDIR");
262printenvvar("INTEGRAL");
263
264$thecount = 0;
265$n = 0;
266
267if ($usingthreads) {
268    @threads = ();
269
270    foreach my $jobnum (0..$maxjobs-1) {
271        my $thr = threads->new(\&jobrunner, $jobnum);
272        $threads[$#threads+1] = $thr;
273    }
274
275    foreach my $thr (@threads) {
276        $thr->join();
277    }
278
279    @threads = ();
280}
281else {
282    foreach my $jobnum (0..$maxjobs-1) {
283        jobrunner($jobnum);
284    }
285}
286
287foreach $i (values(%nodefile)) {
288    unlink "$i";
289}
290
291sub get_next_file {
292    my $jobnum = shift;
293    lock(@seqfiles) if ($usingthreads);
294    if ($#seqfiles >= 0 && $jobnum == 0) {
295        return pop(@seqfiles);
296    }
297    lock(@files) if ($usingthreads);
298    if ($#files >= 0) {
299        return pop(@files);
300    }
301    return "";
302}
303
304sub jobrunner {
305    my $jobslot = shift;
306
307    my $file;
308
309    while ( ($file = get_next_file($jobslot)) ne "") {
310        $file =~ s/\.in//;
311        $tmp_out = "$pwd/$outputprefix/$file.tmp";
312	$ENV{"CCACHEM_RESULTS_DIR"} = "$tmp_out";
313        my $in = "$inputprefix/$file.in";
314        my $cmd = "$launch";
315        $cmd =~ s/%CCAFE%/$ccafe/;
316        $cmd =~ s/%NPROC%/$nprocperjob/;
317        $cmd =~ s/%INPUT%/$in/;
318
319	my $ccarunproc = "$scdatadir/ccarunproc $ccafe";
320        if (exists($ENV{THREADGRP})) {
321           $ccarunproc = "$ccarunproc " . &isoencode("$ENV{THREADGRP}");
322        }
323        else {
324           $ccarunproc = "$ccarunproc none";
325        }
326        # no mem/message groups for now
327        $ccarunproc = "$ccarunproc none none";
328        #results dir
329        $ccarunproc = "$ccarunproc " . &isoencode("$ENV{CCACHEM_RESULTS_DIR}");
330
331        $cmd =~ s|%CCARUNPROC%|$ccarunproc|;
332
333        $cmd = substitute_optional_parameter($cmd, "%OUTPUT%", "$tmp_out");
334        $cmd = substitute_optional_parameter($cmd, "%NODELIST%", $nodelist{$jobslot});
335        $cmd = substitute_optional_parameter($cmd, "%NODEFILE%", $nodefile{$jobslot});
336
337        mkdir $tmp_out;
338        printf "starting in slot %d: %s\n", $jobslot, "$cmd";
339        $cmd = "true" if ($debug);
340        $pid = fork();
341        if ($pid == 0) {
342            exec("$cmd");
343            die "exec returned";
344        }
345        waitpid($pid,'');
346
347        rename "$tmp_out/pOut0", "$pwd/$outputprefix/$file.out";
348        unlink "$tmp_out/pErr0";
349        rename "$tmp_out/results.txt", "$pwd/$outputprefix/$file.results";
350        rmdir "$tmp_out";
351    }
352}
353
354sub get_file_list {
355    my @dirfiles;
356    my @argfiles;
357
358    if ($readdir ne "") {
359        opendir(DIR,"$readdir");
360        @tdirfiles = sort(readdir(DIR));
361        foreach my $j (@tdirfiles) {
362            if ($j =~ /\.in$/) {
363                $dirfiles[$#dirfiles+1] = $j;
364            }
365        }
366        closedir(DIR);
367    }
368
369    @argfiles = sort(@ARGV);
370
371    my @allfiles = (@dirfiles, @argfiles);
372
373    my @files;
374
375    foreach my $infile (@allfiles) {
376        my $out = outfile("$infile");
377        $out = "$outputprefix$out";
378        $in = "$inputprefix$infile";
379        if (!$rerun
380            && (-f "$out")
381            && ($onlynew
382                || (-M "$out" < -M "$in" && (! -f "$ccafe" || -M "$out" < -M "$mpqc")))) {
383            if ($verbose) {
384                print "$in: skipping: $out up-to-date\n";
385            }
386        }
387        else {
388            if ($verbose) {
389                print "$in: will be run\n";
390            }
391            $files[$#files+1] = "$infile";
392        }
393    }
394
395    return @files;
396}
397
398sub outfile {
399    my $in = shift;
400
401    my $outbase = "$in";
402    $outbase =~ s/\.[^.]*$//;
403    $outbase = sprintf "%s.out", "$outbase";
404    my $out;
405
406    if ($uniqout) {
407        $out = "$outbase";
408        my $outversion = 1;
409        while (-f "$out") {
410            $outversion++;
411            $out = sprintf "%s.%02d", "$outbase", $outversion;
412        }
413    }
414
415    return "$out";
416}
417
418sub printenvvar {
419    my $envvar = shift;
420    if (exists($ENV{$envvar})) {
421        printf "Using %s = \"%s\"\n", $envvar, $ENV{$envvar};
422    }
423}
424
425sub substitute_optional_parameter {
426    my $str = shift;
427    my $name = shift;
428    my $value = shift;
429    if ($value ne "") {
430        $str =~ s/\[([^[]*$name[^[]*)\]/$1/;
431        $str =~ s/$name/$value/;
432    }
433    else {
434        $str =~ s/\[([^[]*$name[^[]*)\]//;
435    }
436    return $str;
437}
438
439sub isoencode {
440    my $str = shift;
441    $str =~ s/ /%20/g;
442    $str =~ s/\</%3c/g;
443    $str =~ s/\>/%3e/g;
444    $str =~ s/\[/%5b/g;
445    $str =~ s/\]/%5d/g;
446    $str =~ s/\$/%24/g;
447    $str =~ s/:/%38/g;
448    $str =~ s/\(/%28/g;
449    $str =~ s/\)/%29/g;
450    return $str;
451}
452