File Coverage

blib/lib/Runner/Init.pm
Criterion Covered Total %
statement 39 117 33.3
branch 0 20 0.0
condition n/a
subroutine 13 21 61.9
pod n/a
total 52 158 32.9


line stmt bran cond sub pod time code
1             package Runner::Init;
2              
3             #use 5.006;
4              
5 1     1   15829 use Carp;
  1         2  
  1         65  
6 1     1   2343 use Data::Dumper;
  1         13217  
  1         69  
7 1     1   545 use IPC::Open3;
  1         3633  
  1         45  
8 1     1   3406 use IO::Select;
  1         1300  
  1         40  
9 1     1   5 use Symbol;
  1         1  
  1         46  
10 1     1   755 use Log::Log4perl qw(:easy);
  1         41683  
  1         6  
11 1     1   1679 use DateTime;
  1         113295  
  1         37  
12 1     1   612 use DateTime::Format::Duration;
  1         5148  
  1         52  
13 1     1   7 use Cwd;
  1         1  
  1         52  
14 1     1   4 use File::Path qw(make_path);
  1         2  
  1         50  
15 1     1   5 use File::Spec;
  1         1  
  1         14  
16              
17             #use Moose::Role;
18 1     1   542 use Moose;
  1         382826  
  1         8  
19 1     1   5568 use Moose::Util::TypeConstraints;
  1         2  
  1         8  
20             with 'MooseX::Getopt';
21              
22              
23             =head1 NAME
24              
25             Runner::Init - HPC Runner::Slurm, Runner::MCE and Runner::Threads base class
26              
27             =head1 VERSION
28              
29             Version 0.01
30              
31             =cut
32              
33             our $VERSION = '2.30';
34              
35             =head1 SYNOPSIS
36              
37             This is a base class for Runner::MCE and Runner:Threads. You should not need to call this module directly.
38              
39             =head1 EXPORT
40             =cut
41              
42             =head1 VARIABLES
43              
44             =cut
45              
46             =head2 infile
47              
48             File of commands separated by newline. The command 'wait' indicates all previous commands should finish before starting the next one.
49              
50             =cut
51              
52             has 'infile' => (
53             is => 'rw',
54             isa => 'Str',
55             required => 1,
56             documentation => q{File of commands separated by newline. The command 'wait' indicates all previous commands should finish before starting the next one.},
57             trigger => \&_set_infile,
58             );
59              
60             =head2 _set_infile
61             =cut
62              
63             sub _set_infile{
64 0     0     my($self, $infile) = @_;
65              
66 0           $infile = File::Spec->rel2abs($infile);
67 0           $self->{infile} = $infile;
68             }
69              
70             =head2 outdir
71              
72             Directory to write out files and logs.
73              
74             =cut
75              
76             has 'outdir' => (
77             is => 'rw',
78             isa => 'Str',
79             required => 1,
80             # default => sub {return getcwd() },
81             default => sub { return "$ENV{HOME}/hpcjobs" },
82             documentation => q{Directory to write out files.},
83             trigger => \&_set_outdir,
84             );
85              
86             =head2 _set_outdir
87             =cut
88              
89             sub _set_outdir{
90 0     0     my($self, $outdir) = @_;
91              
92 0 0         make_path($outdir) if ! -d $outdir;
93 0           $outdir = File::Spec->rel2abs($outdir);
94 0           $self->{outdir} = $outdir;
95             }
96              
97             =head2 logdir
98              
99             Pattern to use to write out logs directory. Defaults to outdir/prunner_current_date_time/log1 .. log2 .. log3.
100              
101             =cut
102              
103             has 'logdir' => (
104             is => 'rw',
105             isa => 'Str',
106             lazy => 1,
107             required => 1,
108             default => \&set_logdir,
109             documentation => q{Directory where logfiles are written. Defaults to current_working_directory/prunner_current_date_time/log1 .. log2 .. log3'},
110             );
111              
112             =head2 procs
113              
114             Total number of running children allowed at any time. Defaults to 10. The command 'wait' can be used to have a variable number of children running. It is best to wrap this script in a slurm job to not overuse resources. This isn't used within this module, but passed off to mcerunner/parallelrunner.
115              
116             =cut
117              
118             has 'procs' => (
119             is => 'rw',
120             isa => 'Int',
121             default => 4,
122             required => 0,
123             documentation => q{Total number of running children allowed at any time. Defaults to 10. The command 'wait' can be used to have a variable number of children running. It is best to wrap this script in a slurm job to not overuse resources.}
124             );
125              
126              
127             has 'verbose' => (
128             is => 'rw',
129             isa => enum([qw[0 1]]),
130             required => 1,
131             default => 1,
132             documentation => q{Level of verbosity},
133             );
134              
135             =head1 Internal VARIABLES
136              
137             You shouldn't be calling these directly.
138              
139             =cut
140              
141             has 'cmd' => (
142             traits => ['String', 'NoGetopt'],
143             is => 'rw',
144             isa => 'Str',
145             lazy_build => 1,
146             required => 0,
147             handles => {
148             add_cmd => 'append',
149             match_cmd => 'match',
150             }
151             );
152              
153             has 'counter' => (
154             traits => ['Counter', 'NoGetopt'],
155             is => 'rw',
156             isa => 'Num',
157             required => 1,
158             default => 1,
159             handles => {
160             inc_counter => 'inc',
161             dec_counter => 'dec',
162             reset_counter => 'reset',
163             },
164             );
165              
166             #this needs to be called in the main app
167             has 'log' => (
168             traits => ['NoGetopt'],
169             is => 'rw',
170             # default => \&init_log,
171             );
172              
173             has 'logfile' => (
174             traits => ['String', 'NoGetopt'],
175             is => 'rw',
176             default => \&set_logfile,
177             handles => {
178             add_logfile => 'append',
179             prepend_logfile => 'prepend',
180             clear_logfile => 'clear',
181             }
182             );
183              
184             has 'logname' => (
185             isa => 'Str',
186             is => 'rw',
187             default => 'prunner_logs',
188             );
189              
190             =head1 Subroutines
191              
192             =head2 set_logdir
193             =cut
194              
195             sub set_logdir{
196 0     0     my $self = shift;
197              
198 0           my $logdir;
199 0           $logdir = $self->outdir."/".$self->logname."_".$self->set_logfile;
200             # #forget this it makes the log files too log
201             # $logdir =~ s/\.log$//;
202             # my @chars = ("A".."Z", "a".."z");
203             # my $string;
204             # $string .= $chars[rand @chars] for 1..8;
205             # $logdir .= $string;
206              
207             #Don't want to overwrite existing logdirs
208 0           while(-d $logdir){
209 0           sleep(1);
210 0           $logdir = getcwd()."/".$self->logname."_".$self->set_logfile;
211 0           $logdir =~ s/\.log$//;
212             }
213 0 0         make_path($logdir) if ! -d $logdir;
214 0           return $logdir;
215             }
216              
217             =head2 set_logfile
218             =cut
219              
220             sub set_logfile{
221 0     0     my $self = shift;
222              
223 0           my $dt = DateTime->now();
224 0           $dt =~ s/[^\w]/_/g;
225 0           return "$dt.log";
226             }
227              
228             =head2 init_log
229             =cut
230              
231             sub init_log {
232 0     0     my $self = shift;
233              
234 0           Log::Log4perl->easy_init(
235             {
236             level => $TRACE,
237             utf8 => 1,
238             mode => 'append',
239             file => ">>".$self->logdir."/".$self->logfile,
240             layout => '%d: %p %m%n '
241             }
242             );
243              
244 0           my $log = get_logger();
245 0           return $log;
246             }
247              
248             =head2 run_command_threads
249              
250             Start the thread, run the command, and finish the thread
251              
252             =cut
253              
254             sub run_command_threads{
255 0     0     my $self = shift;
256              
257 0 0         my $pid = $self->threads->start($self->cmd) and return;
258              
259 0           my $exitcode = $self->_log_commands($pid);
260              
261 0           $self->threads->finish($exitcode); # pass an exit code to finish
262              
263 0           return;
264             }
265              
266             =head2 run_command_mce
267              
268             MCE knows which subcommand to use from Runner/MCE - object mce
269              
270             =cut
271              
272             sub run_command_mce{
273 0     0     my $self = shift;
274              
275 0           my $pid = $$;
276              
277 0           $DB::single=2;
278            
279             #Mce doesn't take exitcode to end
280 0           $self->_log_commands($pid);
281              
282 0           return;
283             }
284              
285             =head2 _log_commands
286              
287             Log the commands run them. Cat stdout/err with IO::Select so we hopefully don't break things.
288              
289             This example was just about 100% from the following perlmonks discussions.
290              
291             http://www.perlmonks.org/?node_id=151886
292              
293             You can use the script at the top to test the runner. Just download it, make it executable, and put it in the infile as
294              
295             perl command.pl 1
296             perl command.pl 2
297             #so on and so forth
298              
299             =cut
300              
301             sub _log_commands {
302 0     0     my($self, $pid) = @_;
303              
304             #same here
305 0           my $dt1 = DateTime->now();
306              
307             #Create logdir
308 0           $DB::single=2;
309              
310             #Create new log for each job
311 0           $self->logfile($self->set_logfile);
312 0           $self->prepend_logfile("CMD".$self->counter."_PID_$pid"."_DT_");
313 0           my $logger = $self->init_log;
314              
315             #Start running job
316 0           my ($infh,$outfh,$errfh);
317 0           $errfh = gensym(); # if you uncomment this line, $errfh will
318             # never be initialized for you and you
319             # will get a warning in the next print
320             # line.
321 0           my $cmdpid;
322 0           eval{
323 0           $cmdpid = open3($infh, $outfh, $errfh, $self->cmd);
324             };
325 0 0         die $@ if $@;
326 0 0         if(! $cmdpid) {
327 0           print "There is no $cmdpid please contact your administrator with the full command given\n";
328 0           die;
329             }
330 0           $infh->autoflush();
331              
332 0           $DB::single=2;
333              
334 0           $logger->debug("Starting job ".$self->counter." with PID $cmdpid");
335 0           $logger->debug("Cmd is ".$self->cmd);
336              
337 0 0         $logger->debug("@ is ".$@) if $@;
338              
339 0           $DB::single=2;
340              
341             # now our child is running, happily printing to
342             # its stdout and stderr (our $outfh and $errfh).
343              
344 0           my $sel = new IO::Select; # create a select object
345 0           $sel->add($outfh,$errfh); # and add the fhs
346              
347 0           while(my @ready = $sel->can_read) {
348 0           foreach my $fh (@ready) { # loop through them
349 0           my $line;
350             # read up to 4096 bytes from this fh.
351 0           my $len = sysread $fh, $line, 4096;
352 0 0         if(not defined $len){
    0          
353             # There was an error reading
354 0           $logger->fatal("Error from child: $!");
355             } elsif ($len == 0){
356             # Finished reading from this FH because we read
357             # 0 bytes. Remove this handle from $sel.
358 0           $sel->remove($fh);
359 0           next;
360             } else { # we read data alright
361 0 0         if($fh == $outfh) {
    0          
362 0           $logger->info($line);
363             } elsif($fh == $errfh) {
364 0           $logger->error($line);
365             } else {
366 0           $logger->fatal("Shouldn't be here!\n");
367             }
368             }
369             }
370             }
371              
372 0           waitpid($cmdpid, 1);
373 0           my $exitcode = $?;
374              
375 0           $DB::single=2;
376 0           $logger->debug("Finishing job ".$self->counter." with PID $cmdpid and ExitCode $exitcode");
377              
378 0           my $dt2 = DateTime->now();
379 0           my $duration = $dt2 - $dt1;
380 0           my $format = DateTime::Format::Duration->new(
381             pattern => '%Y years, %m months, %e days, %H hours, %M minutes, %S seconds'
382             );
383 0           $logger->info("Total execution time ".$format->format_duration($duration));
384              
385 0           return $exitcode;
386             }
387              
388              
389             #__PACKAGE__->meta->make_immutable;
390             1;
391              
392             =head1 AUTHOR
393              
394             Jillian Rowe, C<< <jillian.e.rowe at gmail.com> >>
395              
396             =head1 BUGS
397              
398             Please report any bugs or feature requests to C<bug-runner-init at rt.cpan.org>, or through
399             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Runner-Init>. I will be notified, and then you'll
400             automatically be notified of progress on your bug as I make changes.
401              
402              
403              
404              
405             =head1 SUPPORT
406              
407             You can find documentation for this module with the perldoc command.
408              
409             perldoc Runner::Init
410              
411              
412             You can also look for information at:
413              
414             =over 4
415              
416             =item * RT: CPAN's request tracker (report bugs here)
417              
418             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Runner-Init>
419              
420             =item * AnnoCPAN: Annotated CPAN documentation
421              
422             L<http://annocpan.org/dist/Runner-Init>
423              
424             =item * CPAN Ratings
425              
426             L<http://cpanratings.perl.org/d/Runner-Init>
427              
428             =item * Search CPAN
429              
430             L<http://search.cpan.org/dist/Runner-Init/>
431              
432             =back
433              
434              
435             =head1 ACKNOWLEDGEMENTS
436              
437             This module was originally developed at and for Weill Cornell Medical College in Qatar. With approval from WCMC-Q, this information was generalized and put on github, for which the authors would like to express their gratitude.
438              
439             =head1 LICENSE AND COPYRIGHT
440              
441             Copyright 2014 Jillian Rowe.
442              
443             This program is free software; you can redistribute it and/or modify it
444             under the terms of the the Artistic License (2.0). You may obtain a
445             copy of the full license at:
446              
447             L<http://www.perlfoundation.org/artistic_license_2_0>
448              
449             Any use, modification, and distribution of the Standard or Modified
450             Versions is governed by this Artistic License. By using, modifying or
451             distributing the Package, you accept this license. Do not use, modify,
452             or distribute the Package, if you do not accept this license.
453              
454             If your Modified Version has been derived from a Modified Version made
455             by someone other than you, you are nevertheless required to ensure that
456             your Modified Version complies with the requirements of this license.
457              
458             This license does not grant you the right to use any trademark, service
459             mark, tradename, or logo of the Copyright Holder.
460              
461             This license includes the non-exclusive, worldwide, free-of-charge
462             patent license to make, have made, use, offer to sell, sell, import and
463             otherwise transfer the Package with respect to any patent claims
464             licensable by the Copyright Holder that are necessarily infringed by the
465             Package. If you institute patent litigation (including a cross-claim or
466             counterclaim) against any party alleging that the Package constitutes
467             direct or contributory patent infringement, then this Artistic License
468             to you shall terminate on the date that such litigation is filed.
469              
470             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
471             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
472             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
473             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
474             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
475             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
476             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
477             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
478              
479              
480             =cut
481              
482             #End of Runner::Init