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 --name, -N Name of the job
35 --debug, -d debugging level (Default 0)
36 --help, -h display this help
37 --man, -m display manual
45 This describes how dqsub will generate array jobs.
47 If no B<--array> is given, then the command and any additional
48 arguments given will be run using qsub.
50 If B<--array> is C<chdir>, then each line of the input given in
51 B<--array-from> will be used as a directory and the command and any
52 additional arguments given will run in each directory.
54 IF B<--array> is C<xargs>, then each line of the input given will be
55 considered to be an additional argument which will be given to the
56 command run in the current directory.
60 File to read array arguments from. If not provided, and B<--array> is
61 given, arguments will be read from STDIN.
65 Debug verbosity. (Default 0)
69 Display brief usage information.
84 use Cwd qw(getcwd abs_path);
86 use List::Util qw(min);
89 my %options = (nodes => 1,
104 'array_from|array-from=s',
105 'array_per_job|array-per-job=i',
106 'array_slot_limit|array-slot-limit=i',
107 'array_all_in_one_job|array-all-in-one-job!',
108 'ppn|processors-per-node=i',
110 'time|walltime=s','cputime|cput=s','host=s',
111 'pmem|process_mem|process-mem=s',
112 'pvmem|process_virtual_mem|process-virtiual-mem=s',
113 'max_file|max-file|file=s',
116 'debug|d+','help|h|?','man|m');
118 # pod2usage() if $options{help};
119 # pod2usage({verbose=>2}) if $options{man};
121 $DEBUG = $options{debug};
124 if (not @ARGV and not $options{interactive}) {
125 push @USAGE_ERRORS,"You must provide a command to run";
127 if (defined $options{array} and $options{array} !~ /^(?:|chdir|xargs)$/i) {
128 push @USAGE_ERRORS,"--array must be one of chdir, xargs or '' if provided";
129 $options{array} = lc($options{array});
130 if ($options{array} eq '') {
131 $options{array} = undef;
134 if ($options{interactive} and @ARGV) {
135 push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
138 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
139 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
141 # OK. Generate the options to qsub which we'll be using
142 my @qsub_options = generate_qsub_options(\%options,\@ARGV);
144 if ($options{interactive}) {
145 print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
146 exec('qsub',@qsub_options);
149 if ($options{array}) {
150 @array = read_array_options(\%options) if $options{array};
151 # the -t option gives the range of elements for an array job
152 if ($options{array_all_in_one_job}) {
153 $options{array_per_job} = scalar @array;
155 push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
156 if ($options{array_slot_limit}) {
157 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
161 call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
164 sub generate_qsub_options{
165 my ($options,$args) = @_;
167 if (defined $options->{queue} and length $options->{queue}) {
168 push @qo,'-q',$options->{queue};
170 if (defined $options->{dir}) {
171 push @qo,'-d',abs_path($options->{dir});
173 push @qo,'-d',getcwd;
175 ## handle the -l options
177 push @l, 'nodes='.$options->{nodes};
178 if (defined $options->{ppn}) {
179 $l[$#l] .= ':ppn='.$options->{ppn};
190 for my $k (keys %l_options) {
191 if ($options->{$k}) {
192 push @l,$l_options{$k}.'='.$options{$k};
195 push @qo,'-l',join(',',@l) if @l;
196 if ($options->{interactive}) {
199 if ($options->{name}) {
200 push @qo,'-N',$options->{name};
202 push @qo,'-N',join('_',@{$args}[0..min($#{$args},2)]);
207 sub read_array_options{
210 if (defined $options->{array_from}) {
211 $fh = IO::File->new(defined $options->{array_from}) or
212 die "Unable to open $options->{array_from} for reading: $!";
223 my ($qsub_options,$script) = @_;
225 open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
226 die "Unable to start qsub: $!";
227 print {$qsub_fh} $script or
228 die "Unable to print to qsub: $!";
230 die "Unable to close qsub filehandle: $!";
233 sub write_qsub_script {
234 my ($opt,$arg,$array) = @_;
236 my $script = "#!/bin/bash\n";
237 my $command = join(' ',map {qq('$_')} @{$arg});
239 # this script was written by dqsub
241 if (defined $opt->{array}) {
242 my @subshell = ('','');
243 my $array_opt = join("\n",@{$array});
244 my $max_array = scalar @{$array};
245 my $apjm1 = $opt->{array_per_job} - 1;
246 if ($opt->{array_per_job} > 1) {
247 # we will use subshells if there are more than one array
249 @subshell = ('(',')');
251 for i in \$(seq 1 $opt->{array_per_job}); do
252 # in some cases, the jobs aren't going to come out evenly. Handle that.
253 JOBNUM=\$(( \${PBS_ARRAYID:=1} * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
254 if [ \$JOBNUM -le $max_array ]; then
255 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
259 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
267 if ($opt->{array} eq 'chdir') {
277 exec ${command} "\$OPT";
281 if ($opt->{array_per_job} > 1) {
289 # there's no array, so just executing the command with arguments