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