]> git.donarmstrong.com Git - uiuc_igb_scripts.git/blob - dqsub
tweak -N sanitation
[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 if (@USAGE_ERRORS) {
154     print STDERR map {"$_\n"} @USAGE_ERRORS;
155     exit 1;
156 }
157
158 # OK. Generate the options to qsub which we'll be using
159 my @qsub_options = generate_qsub_options(\%options,\@ARGV);
160
161 if ($options{interactive}) {
162     print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
163     exec('qsub',@qsub_options);
164 } else {
165     my @array = ();
166     if ($options{array}) {
167         @array = read_array_options(\%options) if $options{array};
168         # the -t option gives the range of elements for an array job
169         if ($options{array_all_in_one_job}) {
170             $options{array_per_job} = scalar @array;
171         } else {
172             push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
173             if ($options{array_slot_limit}) {
174                 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
175             }
176         }
177     }
178     call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
179 }
180
181 sub generate_qsub_options{
182     my ($options,$args) = @_;
183     my @qo;
184     if (defined $options->{queue} and length $options->{queue}) {
185         push @qo,'-q',$options->{queue};
186     }
187     if (defined $options->{dir}) {
188         push @qo,'-d',abs_path($options->{dir});
189     } else {
190         push @qo,'-d',getcwd;
191     }
192     ## handle the -l options
193     my @l;
194     push @l, 'nodes='.$options->{nodes};
195     if (defined $options->{ppn}) {
196         $l[$#l] .= ':ppn='.$options->{ppn};
197     }
198     if (defined $options->{account}) {
199         push @qo,'-A',$options->{account};
200     }
201     my %l_options =
202         (mem => 'vmem',
203          time => 'walltime',
204          cputime => 'cput',
205          host    => 'host',
206          pmem => 'pmem',
207          pvmem => 'pvmem',
208          max_file => 'file',
209         );
210     for my $k (keys %l_options) {
211         if ($options->{$k}) {
212             push @l,$l_options{$k}.'='.$options{$k};
213         }
214     }
215     push @qo,'-l',join(',',@l) if @l;
216     if ($options->{interactive}) {
217         push @qo,'-I';
218     }
219     if ($options->{name}) {
220         push @qo,'-N',$options->{name};
221     } else {
222         push @qo,'-N',join('_',
223                            map {my $a = $_; $a =~ s/[^a-zA-Z0-9]*//g; $a;}
224                           @{$args}[0..min($#{$args},2)]);
225     }
226     # join error and output streams
227     if ($options->{join}) {
228         push @qo,'-j','oe';
229     }
230     return @qo;
231 }
232
233 sub read_array_options{
234     my ($options) = @_;
235     my $fh = \*STDIN;
236     if (defined $options->{array_from}) {
237         $fh = IO::File->new(defined $options->{array_from}) or
238             die "Unable to open $options->{array_from} for reading: $!";
239     }
240     my @array;
241     for (<$fh>) {
242         chomp;
243         push @array,$_;
244     }
245     return @array;
246 }
247
248 sub call_qsub {
249     my ($qsub_options,$script) = @_;
250     my $qsub_fh;
251     open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
252         die "Unable to start qsub: $!";
253     print {$qsub_fh} $script or
254         die "Unable to print to qsub: $!";
255     close($qsub_fh) or
256         die "Unable to close qsub filehandle: $!";
257 }
258
259 sub write_qsub_script {
260     my ($opt,$arg,$array) = @_;
261
262     my $script = "#!/bin/bash\n";
263     my $command = join(' ',map {qq('$_')} @{$arg});
264         $script .= <<EOF;
265 # this script was written by dqsub
266 EOF
267     if (defined $opt->{array}) {
268         my @subshell = ('','');
269         my $array_opt = join("\n",@{$array});
270         my $max_array = scalar @{$array};
271         my $apjm1 = $opt->{array_per_job} - 1;
272         if ($opt->{array_per_job} > 1) {
273             # we will use subshells if there are more than one array
274             # items per job
275             @subshell = ('(',')');
276             $script .= <<EOF;
277 for i in \$(seq 1 $opt->{array_per_job}); do
278 # in some cases, the jobs aren't going to come out evenly. Handle that.
279 JOBNUM=\$(( \${PBS_ARRAYID:=1} * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
280 if [ \$JOBNUM -le $max_array ]; then 
281 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
282 EOF
283         } else {
284             $script .= <<EOF;
285 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
286 EOF
287         }
288         $script .= <<EOF;
289 $array_opt
290 _HERE_DOC_END_
291 )
292 EOF
293         if ($opt->{array} eq 'chdir') {
294             $script .= <<EOF;
295 $subshell[0]
296 cd "\$OPT";
297 exec ${command};
298 $subshell[1]
299 EOF
300         } else {
301             $script .= <<EOF;
302 $subshell[0]
303 exec ${command} "\$OPT";
304 $subshell[1]
305 EOF
306         }
307         if ($opt->{array_per_job} > 1) {
308             $script .= <<EOF
309 fi;
310 done;
311 EOF
312         }
313     } else {
314         $script .= <<EOF;
315 # there's no array, so just executing the command with arguments
316 exec $command;
317 EOF
318     }
319     return $script;
320 }
321
322
323 __END__