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 --ppn processors per node to use
31 --mem memory to request
32 --dir Directory to run the script in (default current directory)
33 --debug, -d debugging level (Default 0)
34 --help, -h display this help
35 --man, -m display manual
43 This describes how dqsub will generate array jobs.
45 If no B<--array> is given, then the command and any additional
46 arguments given will be run using qsub.
48 If B<--array> is C<chdir>, then each line of the input given in
49 B<--array-from> will be used as a directory and the command and any
50 additional arguments given will run in each directory.
52 IF B<--array> is C<xargs>, then each line of the input given will be
53 considered to be an additional argument which will be given to the
54 command run in the current directory.
58 File to read array arguments from. If not provided, and B<--array> is
59 given, arguments will be read from STDIN.
63 Debug verbosity. (Default 0)
67 Display brief usage information.
82 use Cwd qw(getcwd abs_path);
86 my %options = (nodes => 1,
101 'array_from|array-from=s',
102 'array_per_job|array-per-job=i',
103 'array_slot_limit|array-slot-limit=i',
104 'ppn|processors-per-node=i',
107 'debug|d+','help|h|?','man|m');
109 # pod2usage() if $options{help};
110 # pod2usage({verbose=>2}) if $options{man};
112 $DEBUG = $options{debug};
115 if (not @ARGV and not $options{interactive}) {
116 push @USAGE_ERRORS,"You must provide a command to run";
118 if (defined $options{array} and $options{array} !~ /^(?:|chdir|xargs)$/i) {
119 push @USAGE_ERRORS,"--array must be one of chdir, xargs or '' if provided";
120 $options{array} = lc($options{array});
121 if ($options{array} eq '') {
122 $options{array} = undef;
125 if ($options{interactive} and @ARGV) {
126 push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
129 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
130 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
132 # OK. Generate the options to qsub which we'll be using
133 my @qsub_options = generate_qsub_options(\%options);
135 if ($options{interactive}) {
136 print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
137 exec('qsub',@qsub_options);
140 if ($options{array}) {
141 @array = read_array_options(\%options) if $options{array};
142 # the -t option gives the range of elements for an array job
143 push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
144 if ($options{array_slot_limit}) {
145 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
148 call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
151 sub generate_qsub_options{
154 if (defined $options->{queue} and length $options->{queue}) {
155 push @qo,'-q',$options->{queue};
157 if (defined $options->{dir}) {
158 push @qo,'-d',abs_path($options->{dir});
160 push @qo,'-d',getcwd;
162 ## handle the -l options
164 push @l, 'nodes='.$options->{nodes};
165 if (defined $options->{ppn}) {
166 $l[$#l] .= ':ppn='.$options->{ppn};
168 if ($options->{mem}) {
169 push @l,'mem='.$options->{mem};
171 push @qo,'-l',join(',',@l) if @l;
172 if ($options->{interactive}) {
178 sub read_array_options{
181 if (defined $options->{array_from}) {
182 $fh = IO::File->new(defined $options->{array_from}) or
183 die "Unable to open $options->{array_from} for reading: $!";
194 my ($qsub_options,$script) = @_;
196 open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
197 die "Unable to start qsub: $!";
198 print {$qsub_fh} $script or
199 die "Unable to print to qsub: $!";
201 die "Unable to close qsub filehandle: $!";
204 sub write_qsub_script {
205 my ($opt,$arg,$array) = @_;
207 my $script = "#!/bin/bash\n";
208 my $command = join(' ',map {qq('$_')} @{$arg});
210 # this script was written by dqsub
212 if (defined $opt->{array}) {
213 my @subshell = ('','');
214 my $array_opt = join("\n",@{$array});
215 my $max_array = scalar @{$array};
216 my $apjm1 = $opt->{array_per_job} - 1;
217 if ($opt->{array_per_job} > 1) {
218 # we will use subshells if there are more than one array
220 @subshell = ('(',')');
222 for i in \$(seq 1 $opt->{array_per_job}); do
223 # in some cases, the jobs aren't going to come out evenly. Handle that.
224 JOBNUM=\$(( \$PBS_ARRAYID * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
225 if [ \$JOBNUM -le $max_array ]; then
226 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
230 OPT=\$(sed -n -e "\$PBS_ARRAYID p"<<'_HERE_DOC_END_'
238 if ($opt->{array} eq 'chdir') {
248 exec ${command} "\$OPT";
252 if ($opt->{array_per_job} > 1) {
260 # there's no array, so just executing the command with arguments