2 # dqsub submits jobs using qsub with better options
3 # and is released under the terms of the GNU GPL version 3, or any
4 # later version, at your option. See the file README and COPYING for
6 # Copyright 2014 by Don Armstrong <don@donarmstrong.com>.
17 dqsub - submits jobs using qsub with better options
24 --queue, -q Queue to use
25 --interactive, -I call qsub interactively
27 --array array mode (one of 'chdir' or 'xargs' or '')
28 --array-from file to read arrays from (default STDIN)
29 --array-per-job number of array items to handle in each job (default 1)
30 --array-all-in-one-job Run all of the array items in one job
31 --ppn processors per node to use
32 --mem memory to request
33 --dir Directory to run the script in (default current directory)
34 --account, -A Account name to use
35 --join, -J join error and output streams (default)
36 --name, -N Name of the job
37 --debug, -d debugging level (Default 0)
38 --help, -h display this help
39 --man, -m display manual
47 This describes how dqsub will generate array jobs.
49 If no B<--array> is given, then the command and any additional
50 arguments given will be run using qsub.
52 If B<--array> is C<chdir>, then each line of the input given in
53 B<--array-from> will be used as a directory and the command and any
54 additional arguments given will run in each directory.
56 IF B<--array> is C<xargs>, then each line of the input given will be
57 considered to be an additional argument which will be given to the
58 command run in the current directory.
62 File to read array arguments from. If not provided, and B<--array> is
63 given, arguments will be read from STDIN.
65 =item B<--account, -A>
71 Whether to join STDOUT and STDERR. On by default; disable with
76 Debug verbosity. (Default 0)
80 Display brief usage information.
95 use Cwd qw(getcwd abs_path);
97 use List::Util qw(min);
100 my %options = (nodes => 1,
111 GetOptions(\%options,
116 'array_from|array-from=s',
117 'array_per_job|array-per-job=i',
118 'array_slot_limit|array-slot-limit=i',
119 'array_all_in_one_job|array-all-in-one-job!',
120 'ppn|processors-per-node=i',
124 'time|walltime=s','cputime|cput=s','host=s',
125 'pmem|process_mem|process-mem=s',
126 'pvmem|process_virtual_mem|process-virtiual-mem=s',
127 'max_file|max-file|file=s',
130 'debug|d+','help|h|?','man|m');
132 # pod2usage() if $options{help};
133 # pod2usage({verbose=>2}) if $options{man};
135 $DEBUG = $options{debug};
138 if (not @ARGV and not $options{interactive}) {
139 push @USAGE_ERRORS,"You must provide a command to run";
141 if (defined $options{array} and $options{array} !~ /^(?:|chdir|xargs)$/i) {
142 push @USAGE_ERRORS,"--array must be one of chdir, xargs or '' if provided";
143 $options{array} = lc($options{array});
144 if ($options{array} eq '') {
145 $options{array} = undef;
148 if ($options{interactive} and @ARGV) {
149 push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
152 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
154 print STDERR map {"$_\n"} @USAGE_ERRORS;
158 # OK. Generate the options to qsub which we'll be using
159 my @qsub_options = generate_qsub_options(\%options,\@ARGV);
161 if ($options{interactive}) {
162 print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
163 exec('qsub',@qsub_options);
166 if ($options{array}) {
167 @array = read_array_options(\%options) if $options{array};
168 # the -t option gives the range of elements for an array job
169 if ($options{array_all_in_one_job}) {
170 $options{array_per_job} = scalar @array;
172 push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
173 if ($options{array_slot_limit}) {
174 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
178 call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
181 sub generate_qsub_options{
182 my ($options,$args) = @_;
184 if (defined $options->{queue} and length $options->{queue}) {
185 push @qo,'-q',$options->{queue};
187 if (defined $options->{dir}) {
188 push @qo,'-d',abs_path($options->{dir});
190 push @qo,'-d',getcwd;
192 ## handle the -l options
194 push @l, 'nodes='.$options->{nodes};
195 if (defined $options->{ppn}) {
196 $l[$#l] .= ':ppn='.$options->{ppn};
198 if (defined $options->{account}) {
199 push @qo,'-A',$options->{account};
210 for my $k (keys %l_options) {
211 if ($options->{$k}) {
212 push @l,$l_options{$k}.'='.$options{$k};
215 push @qo,'-l',join(',',@l) if @l;
216 if ($options->{interactive}) {
219 if ($options->{name}) {
220 push @qo,'-N',$options->{name};
222 push @qo,'-N',join('_',
223 map {my $a = $_; $a =~ s/[^a-zA-Z0-9]*//g; $a;}
224 @{$args}[0..min($#{$args},2)]);
226 # join error and output streams
227 if ($options->{join}) {
233 sub read_array_options{
236 if (defined $options->{array_from}) {
237 $fh = IO::File->new(defined $options->{array_from}) or
238 die "Unable to open $options->{array_from} for reading: $!";
249 my ($qsub_options,$script) = @_;
251 open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
252 die "Unable to start qsub: $!";
253 print {$qsub_fh} $script or
254 die "Unable to print to qsub: $!";
256 die "Unable to close qsub filehandle: $!";
259 sub write_qsub_script {
260 my ($opt,$arg,$array) = @_;
262 my $script = "#!/bin/bash\n";
263 my $command = join(' ',map {$_ =~ /\s/?qq('$_'):$_} @{$arg});
265 # this script was written by dqsub
267 if (defined $opt->{array}) {
268 my @subshell = ('','');
269 my $array_opt = join("\n",@{$array});
270 my $max_array = scalar @{$array};
271 my $apjm1 = $opt->{array_per_job} - 1;
272 if ($opt->{array_per_job} > 1) {
273 # we will use subshells if there are more than one array
275 @subshell = ('(',')');
277 for i in \$(seq 1 $opt->{array_per_job}); do
278 # in some cases, the jobs aren't going to come out evenly. Handle that.
279 JOBNUM=\$(( \${PBS_ARRAYID:=1} * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
280 if [ \$JOBNUM -le $max_array ]; then
281 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
285 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
293 if ($opt->{array} eq 'chdir') {
303 exec ${command} "\$OPT";
307 if ($opt->{array_per_job} > 1) {
315 # there's no array, so just executing the command with arguments