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