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