]> git.donarmstrong.com Git - uiuc_igb_scripts.git/blob - dqsub
automatically specify a job name (no more STDIN)
[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            'dir=s',
111            'name=s',
112            'debug|d+','help|h|?','man|m');
113
114 # pod2usage() if $options{help};
115 # pod2usage({verbose=>2}) if $options{man};
116
117 $DEBUG = $options{debug};
118
119 my @USAGE_ERRORS;
120 if (not @ARGV and not $options{interactive}) {
121     push @USAGE_ERRORS,"You must provide a command to run";
122 }
123 if (defined $options{array} and $options{array} !~ /^(?:|chdir|xargs)$/i) {
124     push @USAGE_ERRORS,"--array must be one of chdir, xargs or '' if provided";
125     $options{array} = lc($options{array});
126     if ($options{array} eq '') {
127         $options{array} = undef;
128     }
129 }
130 if ($options{interactive} and @ARGV) {
131     push @USAGE_ERRORS,"Don't provide commands when you're asking for an interactive shell";
132 }
133
134 # pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
135 print STDERR join("\n",@USAGE_ERRORS) and exit 1 if @USAGE_ERRORS;
136
137 # OK. Generate the options to qsub which we'll be using
138 my @qsub_options = generate_qsub_options(\%options,\@ARGV);
139
140 if ($options{interactive}) {
141     print STDERR 'running: qsub '.join(' ',@qsub_options) if $DEBUG;
142     exec('qsub',@qsub_options);
143 } else {
144     my @array = ();
145     if ($options{array}) {
146         @array = read_array_options(\%options) if $options{array};
147         # the -t option gives the range of elements for an array job
148         if ($options{array_all_in_one_job}) {
149             $options{array_per_job} = scalar @array;
150         } else {
151             push @qsub_options,'-t','1-'. ceil(scalar @array / $options{array_per_job});
152             if ($options{array_slot_limit}) {
153                 $qsub_options[$#qsub_options] .= '%'.$options{array_slot_limit};
154             }
155         }
156     }
157     call_qsub(\@qsub_options,write_qsub_script(\%options,\@ARGV,\@array));
158 }
159
160 sub generate_qsub_options{
161     my ($options,$args) = @_;
162     my @qo;
163     if (defined $options->{queue} and length $options->{queue}) {
164         push @qo,'-q',$options->{queue};
165     }
166     if (defined $options->{dir}) {
167         push @qo,'-d',abs_path($options->{dir});
168     } else {
169         push @qo,'-d',getcwd;
170     }
171     ## handle the -l options
172     my @l;
173     push @l, 'nodes='.$options->{nodes};
174     if (defined $options->{ppn}) {
175         $l[$#l] .= ':ppn='.$options->{ppn};
176     }
177     if ($options->{mem}) {
178         push @l,'mem='.$options->{mem};
179     }
180     push @qo,'-l',join(',',@l) if @l;
181     if ($options->{interactive}) {
182         push @qo,'-I';
183     }
184     if ($options->{name}) {
185         push @qo,'-N',$options->{name};
186     } else {
187         push @qo,'-N',join('_',@{$args}[0..min($#{$args},2)]);
188     }
189     return @qo;
190 }
191
192 sub read_array_options{
193     my ($options) = @_;
194     my $fh = \*STDIN;
195     if (defined $options->{array_from}) {
196         $fh = IO::File->new(defined $options->{array_from}) or
197             die "Unable to open $options->{array_from} for reading: $!";
198     }
199     my @array;
200     for (<$fh>) {
201         chomp;
202         push @array,$_;
203     }
204     return @array;
205 }
206
207 sub call_qsub {
208     my ($qsub_options,$script) = @_;
209     my $qsub_fh;
210     open $qsub_fh,'|-','qsub',@{$qsub_options},'-' or
211         die "Unable to start qsub: $!";
212     print {$qsub_fh} $script or
213         die "Unable to print to qsub: $!";
214     close($qsub_fh) or
215         die "Unable to close qsub filehandle: $!";
216 }
217
218 sub write_qsub_script {
219     my ($opt,$arg,$array) = @_;
220
221     my $script = "#!/bin/bash\n";
222     my $command = join(' ',map {qq('$_')} @{$arg});
223         $script .= <<EOF;
224 # this script was written by dqsub
225 EOF
226     if (defined $opt->{array}) {
227         my @subshell = ('','');
228         my $array_opt = join("\n",@{$array});
229         my $max_array = scalar @{$array};
230         my $apjm1 = $opt->{array_per_job} - 1;
231         if ($opt->{array_per_job} > 1) {
232             # we will use subshells if there are more than one array
233             # items per job
234             @subshell = ('(',')');
235             $script .= <<EOF;
236 for i in \$(seq 1 $opt->{array_per_job}); do
237 # in some cases, the jobs aren't going to come out evenly. Handle that.
238 JOBNUM=\$(( \${PBS_ARRAYID:=1} * $opt->{array_per_job} + \$i - $opt->{array_per_job} ))
239 if [ \$JOBNUM -le $max_array ]; then 
240 OPT=\$(sed -n -e "\$JOBNUM p"<<'_HERE_DOC_END_'
241 EOF
242         } else {
243             $script .= <<EOF;
244 OPT=\$(sed -n -e "\${PBS_ARRAYID:=1} p"<<'_HERE_DOC_END_'
245 EOF
246         }
247         $script .= <<EOF;
248 $array_opt
249 _HERE_DOC_END_
250 )
251 EOF
252         if ($opt->{array} eq 'chdir') {
253             $script .= <<EOF;
254 $subshell[0]
255 cd "\$OPT";
256 exec ${command};
257 $subshell[1]
258 EOF
259         } else {
260             $script .= <<EOF;
261 $subshell[0]
262 exec ${command} "\$OPT";
263 $subshell[1]
264 EOF
265         }
266         if ($opt->{array_per_job} > 1) {
267             $script .= <<EOF
268 fi;
269 done;
270 EOF
271         }
272     } else {
273         $script .= <<EOF;
274 # there's no array, so just executing the command with arguments
275 exec $command;
276 EOF
277     }
278     return $script;
279 }
280
281
282 __END__