]> git.donarmstrong.com Git - uiuc_igb_scripts.git/blob - dqsub
handle running multiple array options in each job to work around low queue limits
[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    --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
36
37 =head1 OPTIONS
38
39 =over
40
41 =item B<--array>
42
43 This describes how dqsub will generate array jobs.
44
45 If no B<--array> is given, then the command and any additional
46 arguments given will be run using qsub.
47
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.
51
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.
55
56 =item B<--array-from>
57
58 File to read array arguments from. If not provided, and B<--array> is
59 given, arguments will be read from STDIN.
60
61 =item B<--debug, -d>
62
63 Debug verbosity. (Default 0)
64
65 =item B<--help, -h>
66
67 Display brief usage information.
68
69 =item B<--man, -m>
70
71 Display this manual.
72
73 =back
74
75 =head1 EXAMPLES
76
77 dqsub
78
79 =cut
80
81 use IO::File;
82 use Cwd qw(getcwd abs_path);
83 use POSIX qw(ceil);
84 use vars qw($DEBUG);
85
86 my %options = (nodes           => 1,
87                ppn             => 2,
88                mem             => '2G',
89                debug           => 0,
90                help            => 0,
91                man             => 0,
92                interactive     => 0,
93                array_per_job   => 1,
94               );
95
96 GetOptions(\%options,
97            'queue|q=s',
98            'interactive|I!',
99            'nodes=i',
100            'array=s',
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',
105            'mem|memory=s',
106            'dir=s',
107            'debug|d+','help|h|?','man|m');
108
109 # pod2usage() if $options{help};
110 # pod2usage({verbose=>2}) if $options{man};
111
112 $DEBUG = $options{debug};
113
114 my @USAGE_ERRORS;
115 if (not @ARGV and not $options{interactive}) {
116     push @USAGE_ERRORS,"You must provide a command to run";
117 }
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;
123     }
124 }
125 if ($options{interactive} and @ARGV) {
126     push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
127 }
128
129 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
130 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
131
132 # OK. Generate the options to qsub which we'll be using
133 my @qsub_options = generate_qsub_options(\%options);
134
135 if ($options{interactive}) {
136     print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
137     exec('qsub',@qsub_options);
138 } else {
139     my @array = ();
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};
146         }
147     }
148     call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
149 }
150
151 sub generate_qsub_options{
152     my ($options) = @_;
153     my @qo;
154     if (defined $options->{queue} and length $options->{queue}) {
155         push @qo,'-q',$options->{queue};
156     }
157     if (defined $options->{dir}) {
158         push @qo,'-d',abs_path($options->{dir});
159     } else {
160         push @qo,'-d',getcwd;
161     }
162     ## handle the -l options
163     my @l;
164     push @l, 'nodes='.$options->{nodes};
165     if (defined $options->{ppn}) {
166         $l[$#l] .= ':ppn='.$options->{ppn};
167     }
168     if ($options->{mem}) {
169         push @l,'mem='.$options->{mem};
170     }
171     push @qo,'-l',join(',',@l) if @l;
172     if ($options->{interactive}) {
173         push @qo,'-I';
174     }
175     return @qo;
176 }
177
178 sub read_array_options{
179     my ($options) = @_;
180     my $fh = \*STDIN;
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: $!";
184     }
185     my @array;
186     for (<$fh>) {
187         chomp;
188         push @array,$_;
189     }
190     return @array;
191 }
192
193 sub call_qsub {
194     my ($qsub_options,$script) = @_;
195     my $qsub_fh;
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: $!";
200     close($qsub_fh) or
201         die "Unable to close qsub filehandle: $!";
202 }
203
204 sub write_qsub_script {
205     my ($opt,$arg,$array) = @_;
206
207     my $script = "#!/bin/bash\n";
208     my $command = join(' ',map {qq('$_')} @{$arg});
209         $script .= <<EOF;
210 # this script was written by dqsub
211 EOF
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
219             # items per job
220             @subshell = ('(',')');
221             $script .= <<EOF;
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_'
227 EOF
228         } else {
229             $script .= <<EOF;
230 OPT=\$(sed -n -e "\$PBS_ARRAYID p"<<'_HERE_DOC_END_'
231 EOF
232         }
233         $script .= <<EOF;
234 $array_opt
235 _HERE_DOC_END_
236 )
237 EOF
238         if ($opt->{array} eq 'chdir') {
239             $script .= <<EOF;
240 $subshell[0]
241 cd "\$OPT";
242 exec ${command};
243 $subshell[1]
244 EOF
245         } else {
246             $script .= <<EOF;
247 $subshell[0]
248 exec ${command} "\$OPT";
249 $subshell[1]
250 EOF
251         }
252         if ($opt->{array_per_job} > 1) {
253             $script .= <<EOF
254 fi;
255 done;
256 EOF
257         }
258     } else {
259         $script .= <<EOF;
260 # there's no array, so just executing the command with arguments
261 exec $command;
262 EOF
263     }
264     return $script;
265 }
266
267
268 __END__