]> git.donarmstrong.com Git - uiuc_igb_scripts.git/blob - dqsub
add support for more -l options to qsub
[uiuc_igb_scripts.git] / dqsub
1 #!/usr/bin/perl
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
5 # more information.
6 # Copyright 2014 by Don Armstrong <don@donarmstrong.com>.
7
8
9 use warnings;
10 use strict;
11
12 use Getopt::Long;
13 # use Pod::Usage;
14
15 =head1 NAME
16
17 dqsub - submits jobs using qsub with better options
18
19 =head1 SYNOPSIS
20
21 dqsub [options]
22
23  Options:
24    --queue, -q Queue to use
25    --interactive, -I call qsub interactively
26    --nodes nodes to use
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
38
39 =head1 OPTIONS
40
41 =over
42
43 =item B<--array>
44
45 This describes how dqsub will generate array jobs.
46
47 If no B<--array> is given, then the command and any additional
48 arguments given will be run using qsub.
49
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.
53
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.
57
58 =item B<--array-from>
59
60 File to read array arguments from. If not provided, and B<--array> is
61 given, arguments will be read from STDIN.
62
63 =item B<--debug, -d>
64
65 Debug verbosity. (Default 0)
66
67 =item B<--help, -h>
68
69 Display brief usage information.
70
71 =item B<--man, -m>
72
73 Display this manual.
74
75 =back
76
77 =head1 EXAMPLES
78
79 dqsub
80
81 =cut
82
83 use IO::File;
84 use Cwd qw(getcwd abs_path);
85 use POSIX qw(ceil);
86 use List::Util qw(min);
87 use vars qw($DEBUG);
88
89 my %options = (nodes           => 1,
90                ppn             => 2,
91                mem             => '2G',
92                debug           => 0,
93                help            => 0,
94                man             => 0,
95                interactive     => 0,
96                array_per_job   => 1,
97               );
98
99 GetOptions(\%options,
100            'queue|q=s',
101            'interactive|I!',
102            'nodes=i',
103            'array=s',
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',
109            'mem|memory=s',
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',
114            'dir=s',
115            'name=s',
116            'debug|d+','help|h|?','man|m');
117
118 # pod2usage() if $options{help};
119 # pod2usage({verbose=>2}) if $options{man};
120
121 $DEBUG = $options{debug};
122
123 my @USAGE_ERRORS;
124 if (not @ARGV and not $options{interactive}) {
125     push @USAGE_ERRORS,"You must provide a command to run";
126 }
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;
132     }
133 }
134 if ($options{interactive} and @ARGV) {
135     push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
136 }
137
138 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
139 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
140
141 # OK. Generate the options to qsub which we'll be using
142 my @qsub_options = generate_qsub_options(\%options,\@ARGV);
143
144 if ($options{interactive}) {
145     print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
146     exec('qsub',@qsub_options);
147 } else {
148     my @array = ();
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;
154         } else {
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};
158             }
159         }
160     }
161     call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
162 }
163
164 sub generate_qsub_options{
165     my ($options,$args) = @_;
166     my @qo;
167     if (defined $options->{queue} and length $options->{queue}) {
168         push @qo,'-q',$options->{queue};
169     }
170     if (defined $options->{dir}) {
171         push @qo,'-d',abs_path($options->{dir});
172     } else {
173         push @qo,'-d',getcwd;
174     }
175     ## handle the -l options
176     my @l;
177     push @l, 'nodes='.$options->{nodes};
178     if (defined $options->{ppn}) {
179         $l[$#l] .= ':ppn='.$options->{ppn};
180     }
181     my %l_options =
182         (mem => 'vmem',
183          time => 'walltime',
184          cputime => 'cput',
185          host    => 'host',
186          pmem => 'pmem',
187          pvmem => 'pvmem',
188          max_file => 'file',
189         );
190     for my $k (keys %l_options) {
191         if ($options->{$k}) {
192             push @l,$l_options{$k}.'='.$options{$k};
193         }
194     }
195     push @qo,'-l',join(',',@l) if @l;
196     if ($options->{interactive}) {
197         push @qo,'-I';
198     }
199     if ($options->{name}) {
200         push @qo,'-N',$options->{name};
201     } else {
202         push @qo,'-N',join('_',@{$args}[0..min($#{$args},2)]);
203     }
204     return @qo;
205 }
206
207 sub read_array_options{
208     my ($options) = @_;
209     my $fh = \*STDIN;
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: $!";
213     }
214     my @array;
215     for (<$fh>) {
216         chomp;
217         push @array,$_;
218     }
219     return @array;
220 }
221
222 sub call_qsub {
223     my ($qsub_options,$script) = @_;
224     my $qsub_fh;
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: $!";
229     close($qsub_fh) or
230         die "Unable to close qsub filehandle: $!";
231 }
232
233 sub write_qsub_script {
234     my ($opt,$arg,$array) = @_;
235
236     my $script = "#!/bin/bash\n";
237     my $command = join(' ',map {qq('$_')} @{$arg});
238         $script .= <<EOF;
239 # this script was written by dqsub
240 EOF
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
248             # items per job
249             @subshell = ('(',')');
250             $script .= <<EOF;
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_'
256 EOF
257         } else {
258             $script .= <<EOF;
259 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
260 EOF
261         }
262         $script .= <<EOF;
263 $array_opt
264 _HERE_DOC_END_
265 )
266 EOF
267         if ($opt->{array} eq 'chdir') {
268             $script .= <<EOF;
269 $subshell[0]
270 cd "\$OPT";
271 exec ${command};
272 $subshell[1]
273 EOF
274         } else {
275             $script .= <<EOF;
276 $subshell[0]
277 exec ${command} "\$OPT";
278 $subshell[1]
279 EOF
280         }
281         if ($opt->{array_per_job} > 1) {
282             $script .= <<EOF
283 fi;
284 done;
285 EOF
286         }
287     } else {
288         $script .= <<EOF;
289 # there's no array, so just executing the command with arguments
290 exec $command;
291 EOF
292     }
293     return $script;
294 }
295
296
297 __END__