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 --name, -N Name of the job
36 --debug, -d debugging level (Default 0)
37 --help, -h display this help
38 --man, -m display manual
46 This describes how dqsub will generate array jobs.
48 If no B<--array> is given, then the command and any additional
49 arguments given will be run using qsub.
51 If B<--array> is C<chdir>, then each line of the input given in
52 B<--array-from> will be used as a directory and the command and any
53 additional arguments given will run in each directory.
55 IF B<--array> is C<xargs>, then each line of the input given will be
56 considered to be an additional argument which will be given to the
57 command run in the current directory.
61 File to read array arguments from. If not provided, and B<--array> is
62 given, arguments will be read from STDIN.
64 =item B<--account, -A>
70 Debug verbosity. (Default 0)
74 Display brief usage information.
89 use Cwd qw(getcwd abs_path);
91 use List::Util qw(min);
94 my %options = (nodes => 1,
104 GetOptions(\%options,
109 'array_from|array-from=s',
110 'array_per_job|array-per-job=i',
111 'array_slot_limit|array-slot-limit=i',
112 'array_all_in_one_job|array-all-in-one-job!',
113 'ppn|processors-per-node=i',
116 'time|walltime=s','cputime|cput=s','host=s',
117 'pmem|process_mem|process-mem=s',
118 'pvmem|process_virtual_mem|process-virtiual-mem=s',
119 'max_file|max-file|file=s',
122 'debug|d+','help|h|?','man|m');
124 # pod2usage() if $options{help};
125 # pod2usage({verbose=>2}) if $options{man};
127 $DEBUG = $options{debug};
130 if (not @ARGV and not $options{interactive}) {
131 push @USAGE_ERRORS,"You must provide a command to run";
133 if (defined $options{array} and $options{array} !~ /^(?:|chdir|xargs)$/i) {
134 push @USAGE_ERRORS,"--array must be one of chdir, xargs or '' if provided";
135 $options{array} = lc($options{array});
136 if ($options{array} eq '') {
137 $options{array} = undef;
140 if ($options{interactive} and @ARGV) {
141 push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
144 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
145 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
147 # OK. Generate the options to qsub which we'll be using
148 my @qsub_options = generate_qsub_options(\%options,\@ARGV);
150 if ($options{interactive}) {
151 print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
152 exec('qsub',@qsub_options);
155 if ($options{array}) {
156 @array = read_array_options(\%options) if $options{array};
157 # the -t option gives the range of elements for an array job
158 if ($options{array_all_in_one_job}) {
159 $options{array_per_job} = scalar @array;
161 push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
162 if ($options{array_slot_limit}) {
163 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
167 call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
170 sub generate_qsub_options{
171 my ($options,$args) = @_;
173 if (defined $options->{queue} and length $options->{queue}) {
174 push @qo,'-q',$options->{queue};
176 if (defined $options->{dir}) {
177 push @qo,'-d',abs_path($options->{dir});
179 push @qo,'-d',getcwd;
181 ## handle the -l options
183 push @l, 'nodes='.$options->{nodes};
184 if (defined $options->{ppn}) {
185 $l[$#l] .= ':ppn='.$options->{ppn};
187 if (defined $options->{account}) {
188 push @qo,'-A',$options->{account};
199 for my $k (keys %l_options) {
200 if ($options->{$k}) {
201 push @l,$l_options{$k}.'='.$options{$k};
204 push @qo,'-l',join(',',@l) if @l;
205 if ($options->{interactive}) {
208 if ($options->{name}) {
209 push @qo,'-N',$options->{name};
211 push @qo,'-N',join('_',@{$args}[0..min($#{$args},2)]);
216 sub read_array_options{
219 if (defined $options->{array_from}) {
220 $fh = IO::File->new(defined $options->{array_from}) or
221 die "Unable to open $options->{array_from} for reading: $!";
232 my ($qsub_options,$script) = @_;
234 open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
235 die "Unable to start qsub: $!";
236 print {$qsub_fh} $script or
237 die "Unable to print to qsub: $!";
239 die "Unable to close qsub filehandle: $!";
242 sub write_qsub_script {
243 my ($opt,$arg,$array) = @_;
245 my $script = "#!/bin/bash\n";
246 my $command = join(' ',map {qq('$_')} @{$arg});
248 # this script was written by dqsub
250 if (defined $opt->{array}) {
251 my @subshell = ('','');
252 my $array_opt = join("\n",@{$array});
253 my $max_array = scalar @{$array};
254 my $apjm1 = $opt->{array_per_job} - 1;
255 if ($opt->{array_per_job} > 1) {
256 # we will use subshells if there are more than one array
258 @subshell = ('(',')');
260 for i in \$(seq 1 $opt->{array_per_job}); do
261 # in some cases, the jobs aren't going to come out evenly. Handle that.
262 JOBNUM=\$(( \${PBS_ARRAYID:=1} * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
263 if [ \$JOBNUM -le $max_array ]; then
264 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
268 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
276 if ($opt->{array} eq 'chdir') {
286 exec ${command} "\$OPT";
290 if ($opt->{array_per_job} > 1) {
298 # there's no array, so just executing the command with arguments