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 --debug, -d debugging level (Default 0)
35 --help, -h display this help
36 --man, -m display manual
44 This describes how dqsub will generate array jobs.
46 If no B<--array> is given, then the command and any additional
47 arguments given will be run using qsub.
49 If B<--array> is C<chdir>, then each line of the input given in
50 B<--array-from> will be used as a directory and the command and any
51 additional arguments given will run in each directory.
53 IF B<--array> is C<xargs>, then each line of the input given will be
54 considered to be an additional argument which will be given to the
55 command run in the current directory.
59 File to read array arguments from. If not provided, and B<--array> is
60 given, arguments will be read from STDIN.
64 Debug verbosity. (Default 0)
68 Display brief usage information.
83 use Cwd qw(getcwd abs_path);
87 my %options = (nodes => 1,
102 'array_from|array-from=s',
103 'array_per_job|array-per-job=i',
104 'array_slot_limit|array-slot-limit=i',
105 'array_all_in_one_job|array-all-in-one-job!',
106 'ppn|processors-per-node=i',
109 'debug|d+','help|h|?','man|m');
111 # pod2usage() if $options{help};
112 # pod2usage({verbose=>2}) if $options{man};
114 $DEBUG = $options{debug};
117 if (not @ARGV and not $options{interactive}) {
118 push @USAGE_ERRORS,"You must provide a command to run";
120 if (defined $options{array} and $options{array} !~ /^(?:|chdir|xargs)$/i) {
121 push @USAGE_ERRORS,"--array must be one of chdir, xargs or '' if provided";
122 $options{array} = lc($options{array});
123 if ($options{array} eq '') {
124 $options{array} = undef;
127 if ($options{interactive} and @ARGV) {
128 push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
131 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
132 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
134 # OK. Generate the options to qsub which we'll be using
135 my @qsub_options = generate_qsub_options(\%options);
137 if ($options{interactive}) {
138 print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
139 exec('qsub',@qsub_options);
142 if ($options{array}) {
143 @array = read_array_options(\%options) if $options{array};
144 # the -t option gives the range of elements for an array job
145 if ($options{array_all_in_one_job}) {
146 $options{array_per_job} = scalar @array;
148 push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
149 if ($options{array_slot_limit}) {
150 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
154 call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
157 sub generate_qsub_options{
160 if (defined $options->{queue} and length $options->{queue}) {
161 push @qo,'-q',$options->{queue};
163 if (defined $options->{dir}) {
164 push @qo,'-d',abs_path($options->{dir});
166 push @qo,'-d',getcwd;
168 ## handle the -l options
170 push @l, 'nodes='.$options->{nodes};
171 if (defined $options->{ppn}) {
172 $l[$#l] .= ':ppn='.$options->{ppn};
174 if ($options->{mem}) {
175 push @l,'mem='.$options->{mem};
177 push @qo,'-l',join(',',@l) if @l;
178 if ($options->{interactive}) {
184 sub read_array_options{
187 if (defined $options->{array_from}) {
188 $fh = IO::File->new(defined $options->{array_from}) or
189 die "Unable to open $options->{array_from} for reading: $!";
200 my ($qsub_options,$script) = @_;
202 open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
203 die "Unable to start qsub: $!";
204 print {$qsub_fh} $script or
205 die "Unable to print to qsub: $!";
207 die "Unable to close qsub filehandle: $!";
210 sub write_qsub_script {
211 my ($opt,$arg,$array) = @_;
213 my $script = "#!/bin/bash\n";
214 my $command = join(' ',map {qq('$_')} @{$arg});
216 # this script was written by dqsub
218 if (defined $opt->{array}) {
219 my @subshell = ('','');
220 my $array_opt = join("\n",@{$array});
221 my $max_array = scalar @{$array};
222 my $apjm1 = $opt->{array_per_job} - 1;
223 if ($opt->{array_per_job} > 1) {
224 # we will use subshells if there are more than one array
226 @subshell = ('(',')');
228 for i in \$(seq 1 $opt->{array_per_job}); do
229 # in some cases, the jobs aren't going to come out evenly. Handle that.
230 JOBNUM=\$(( \${PBS_ARRAYID:=1} * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
231 if [ \$JOBNUM -le $max_array ]; then
232 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
236 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
244 if ($opt->{array} eq 'chdir') {
254 exec ${command} "\$OPT";
258 if ($opt->{array_per_job} > 1) {
266 # there's no array, so just executing the command with arguments