]> git.donarmstrong.com Git - uiuc_igb_scripts.git/blob - dqsub
add --join option
[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    --account, -A Account name to use
35    --join, -J join error and output streams (default)
36    --name, -N Name of the job
37    --debug, -d debugging level (Default 0)
38    --help, -h display this help
39    --man, -m display manual
40
41 =head1 OPTIONS
42
43 =over
44
45 =item B<--array>
46
47 This describes how dqsub will generate array jobs.
48
49 If no B<--array> is given, then the command and any additional
50 arguments given will be run using qsub.
51
52 If B<--array> is C<chdir>, then each line of the input given in
53 B<--array-from> will be used as a directory and the command and any
54 additional arguments given will run in each directory.
55
56 IF B<--array> is C<xargs>, then each line of the input given will be
57 considered to be an additional argument which will be given to the
58 command run in the current directory.
59
60 =item B<--array-from>
61
62 File to read array arguments from. If not provided, and B<--array> is
63 given, arguments will be read from STDIN.
64
65 =item B<--account, -A>
66
67 Account name to use
68
69 =item B<--join, J>
70
71 Whether to join STDOUT and STDERR. On by default; disable with
72 C<--nojoin>.
73
74 =item B<--debug, -d>
75
76 Debug verbosity. (Default 0)
77
78 =item B<--help, -h>
79
80 Display brief usage information.
81
82 =item B<--man, -m>
83
84 Display this manual.
85
86 =back
87
88 =head1 EXAMPLES
89
90 dqsub
91
92 =cut
93
94 use IO::File;
95 use Cwd qw(getcwd abs_path);
96 use POSIX qw(ceil);
97 use List::Util qw(min);
98 use vars qw($DEBUG);
99
100 my %options = (nodes           => 1,
101                ppn             => 2,
102                mem             => '2G',
103                debug           => 0,
104                help            => 0,
105                man             => 0,
106                interactive     => 0,
107                array_per_job   => 1,
108                join            => 1,
109               );
110
111 GetOptions(\%options,
112            'queue|q=s',
113            'interactive|I!',
114            'nodes=i',
115            'array=s',
116            'array_from|array-from=s',
117            'array_per_job|array-per-job=i',
118            'array_slot_limit|array-slot-limit=i',
119            'array_all_in_one_job|array-all-in-one-job!',
120            'ppn|processors-per-node=i',
121            'account|A=s',
122            'join|J!',
123            'mem|memory=s',
124            'time|walltime=s','cputime|cput=s','host=s',
125            'pmem|process_mem|process-mem=s',
126            'pvmem|process_virtual_mem|process-virtiual-mem=s',
127            'max_file|max-file|file=s',
128            'dir=s',
129            'name=s',
130            'debug|d+','help|h|?','man|m');
131
132 # pod2usage() if $options{help};
133 # pod2usage({verbose=>2}) if $options{man};
134
135 $DEBUG = $options{debug};
136
137 my @USAGE_ERRORS;
138 if (not @ARGV and not $options{interactive}) {
139     push @USAGE_ERRORS,"You must provide a command to run";
140 }
141 if (defined $options{array} and $options{array} !~ /^(?:|chdir|xargs)$/i) {
142     push @USAGE_ERRORS,"--array must be one of chdir, xargs or '' if provided";
143     $options{array} = lc($options{array});
144     if ($options{array} eq '') {
145         $options{array} = undef;
146     }
147 }
148 if ($options{interactive} and @ARGV) {
149     push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
150 }
151
152 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
153 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
154
155 # OK. Generate the options to qsub which we'll be using
156 my @qsub_options = generate_qsub_options(\%options,\@ARGV);
157
158 if ($options{interactive}) {
159     print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
160     exec('qsub',@qsub_options);
161 } else {
162     my @array = ();
163     if ($options{array}) {
164         @array = read_array_options(\%options) if $options{array};
165         # the -t option gives the range of elements for an array job
166         if ($options{array_all_in_one_job}) {
167             $options{array_per_job} = scalar @array;
168         } else {
169             push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
170             if ($options{array_slot_limit}) {
171                 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
172             }
173         }
174     }
175     call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
176 }
177
178 sub generate_qsub_options{
179     my ($options,$args) = @_;
180     my @qo;
181     if (defined $options->{queue} and length $options->{queue}) {
182         push @qo,'-q',$options->{queue};
183     }
184     if (defined $options->{dir}) {
185         push @qo,'-d',abs_path($options->{dir});
186     } else {
187         push @qo,'-d',getcwd;
188     }
189     ## handle the -l options
190     my @l;
191     push @l, 'nodes='.$options->{nodes};
192     if (defined $options->{ppn}) {
193         $l[$#l] .= ':ppn='.$options->{ppn};
194     }
195     if (defined $options->{account}) {
196         push @qo,'-A',$options->{account};
197     }
198     my %l_options =
199         (mem => 'vmem',
200          time => 'walltime',
201          cputime => 'cput',
202          host    => 'host',
203          pmem => 'pmem',
204          pvmem => 'pvmem',
205          max_file => 'file',
206         );
207     for my $k (keys %l_options) {
208         if ($options->{$k}) {
209             push @l,$l_options{$k}.'='.$options{$k};
210         }
211     }
212     push @qo,'-l',join(',',@l) if @l;
213     if ($options->{interactive}) {
214         push @qo,'-I';
215     }
216     if ($options->{name}) {
217         push @qo,'-N',$options->{name};
218     } else {
219         push @qo,'-N',join('_',@{$args}[0..min($#{$args},2)]);
220     }
221     # join error and output streams
222     if ($options->{join}) {
223         push @qo,'-j','oe';
224     }
225     return @qo;
226 }
227
228 sub read_array_options{
229     my ($options) = @_;
230     my $fh = \*STDIN;
231     if (defined $options->{array_from}) {
232         $fh = IO::File->new(defined $options->{array_from}) or
233             die "Unable to open $options->{array_from} for reading: $!";
234     }
235     my @array;
236     for (<$fh>) {
237         chomp;
238         push @array,$_;
239     }
240     return @array;
241 }
242
243 sub call_qsub {
244     my ($qsub_options,$script) = @_;
245     my $qsub_fh;
246     open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
247         die "Unable to start qsub: $!";
248     print {$qsub_fh} $script or
249         die "Unable to print to qsub: $!";
250     close($qsub_fh) or
251         die "Unable to close qsub filehandle: $!";
252 }
253
254 sub write_qsub_script {
255     my ($opt,$arg,$array) = @_;
256
257     my $script = "#!/bin/bash\n";
258     my $command = join(' ',map {qq('$_')} @{$arg});
259         $script .= <<EOF;
260 # this script was written by dqsub
261 EOF
262     if (defined $opt->{array}) {
263         my @subshell = ('','');
264         my $array_opt = join("\n",@{$array});
265         my $max_array = scalar @{$array};
266         my $apjm1 = $opt->{array_per_job} - 1;
267         if ($opt->{array_per_job} > 1) {
268             # we will use subshells if there are more than one array
269             # items per job
270             @subshell = ('(',')');
271             $script .= <<EOF;
272 for i in \$(seq 1 $opt->{array_per_job}); do
273 # in some cases, the jobs aren't going to come out evenly. Handle that.
274 JOBNUM=\$(( \${PBS_ARRAYID:=1} * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
275 if [ \$JOBNUM -le $max_array ]; then 
276 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
277 EOF
278         } else {
279             $script .= <<EOF;
280 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
281 EOF
282         }
283         $script .= <<EOF;
284 $array_opt
285 _HERE_DOC_END_
286 )
287 EOF
288         if ($opt->{array} eq 'chdir') {
289             $script .= <<EOF;
290 $subshell[0]
291 cd "\$OPT";
292 exec ${command};
293 $subshell[1]
294 EOF
295         } else {
296             $script .= <<EOF;
297 $subshell[0]
298 exec ${command} "\$OPT";
299 $subshell[1]
300 EOF
301         }
302         if ($opt->{array_per_job} > 1) {
303             $script .= <<EOF
304 fi;
305 done;
306 EOF
307         }
308     } else {
309         $script .= <<EOF;
310 # there's no array, so just executing the command with arguments
311 exec $command;
312 EOF
313     }
314     return $script;
315 }
316
317
318 __END__