| Filename | /home/hejohns/perl5/lib/perl5/Capture/Tiny.pm |
| Statements | Executed 13835692 statements in 53.0s |
| Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
|---|---|---|---|---|---|
| 133035 | 1 | 1 | 20.9s | 53695s | Capture::Tiny::_capture_tee |
| 798210 | 1 | 1 | 10.0s | 10.0s | Capture::Tiny::CORE:open (opcode) |
| 266070 | 2 | 1 | 5.29s | 7.28s | Capture::Tiny::_relayer |
| 798210 | 3 | 1 | 3.70s | 13.7s | Capture::Tiny::_open |
| 133035 | 1 | 1 | 3.59s | 12.4s | Capture::Tiny::_copy_std |
| 266070 | 2 | 1 | 3.48s | 13.8s | Capture::Tiny::_open_std |
| 798210 | 3 | 1 | 1.23s | 1.23s | Capture::Tiny::CORE:binmode (opcode) |
| 266070 | 1 | 1 | 1.19s | 1.19s | Capture::Tiny::CORE:close (opcode) |
| 266070 | 1 | 1 | 989ms | 2.18s | Capture::Tiny::_close |
| 133035 | 1 | 1 | 553ms | 553ms | Capture::Tiny::_proxy_std |
| 133035 | 1 | 1 | 479ms | 479ms | Capture::Tiny::_unproxy |
| 133035 | 1 | 1 | 305ms | 305ms | Capture::Tiny::CORE:seek (opcode) |
| 133035 | 1 | 1 | 36.9ms | 36.9ms | Capture::Tiny::CORE:tell (opcode) |
| 1 | 1 | 1 | 165µs | 166µs | Capture::Tiny::BEGIN@14 |
| 1 | 1 | 1 | 13µs | 13µs | main::BEGIN@1.5 |
| 1 | 1 | 1 | 5µs | 10µs | Capture::Tiny::BEGIN@102 |
| 1 | 1 | 1 | 5µs | 24µs | Capture::Tiny::BEGIN@11 |
| 1 | 1 | 1 | 5µs | 15µs | Capture::Tiny::BEGIN@12 |
| 1 | 1 | 1 | 4µs | 6µs | main::BEGIN@2 |
| 1 | 1 | 1 | 4µs | 22µs | main::BEGIN@3.6 |
| 1 | 1 | 1 | 2µs | 2µs | Capture::Tiny::BEGIN@9 |
| 1 | 1 | 1 | 2µs | 2µs | Capture::Tiny::BEGIN@7 |
| 1 | 1 | 1 | 2µs | 2µs | Capture::Tiny::BEGIN@10 |
| 1 | 1 | 1 | 2µs | 2µs | Capture::Tiny::BEGIN@8 |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::__ANON__[:17] |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::_files_exist |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::_fork_exec |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::_kill_tees |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::_name |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::_slurp |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::_start_tee |
| 0 | 0 | 0 | 0s | 0s | Capture::Tiny::_wait_for_tees |
| Line | State ments |
Time on line |
Calls | Time in subs |
Code |
|---|---|---|---|---|---|
| 1 | 2 | 28µs | 1 | 13µs | # spent 13µs within main::BEGIN@1.5 which was called:
# once (13µs+0s) by main::BEGIN@26 at line 1 # spent 13µs making 1 call to main::BEGIN@1.5 |
| 2 | 2 | 14µs | 2 | 8µs | # spent 6µs (4+2) within main::BEGIN@2 which was called:
# once (4µs+2µs) by main::BEGIN@26 at line 2 # spent 6µs making 1 call to main::BEGIN@2
# spent 2µs making 1 call to strict::import |
| 3 | 2 | 24µs | 2 | 40µs | # spent 22µs (4+18) within main::BEGIN@3.6 which was called:
# once (4µs+18µs) by main::BEGIN@26 at line 3 # spent 22µs making 1 call to main::BEGIN@3.6
# spent 18µs making 1 call to warnings::import |
| 4 | package Capture::Tiny; | ||||
| 5 | # ABSTRACT: Capture STDOUT and STDERR from Perl, XS or external programs | ||||
| 6 | 1 | 400ns | our $VERSION = '0.48'; | ||
| 7 | 2 | 11µs | 1 | 2µs | # spent 2µs within Capture::Tiny::BEGIN@7 which was called:
# once (2µs+0s) by main::BEGIN@26 at line 7 # spent 2µs making 1 call to Capture::Tiny::BEGIN@7 |
| 8 | 2 | 9µs | 1 | 2µs | # spent 2µs within Capture::Tiny::BEGIN@8 which was called:
# once (2µs+0s) by main::BEGIN@26 at line 8 # spent 2µs making 1 call to Capture::Tiny::BEGIN@8 |
| 9 | 2 | 8µs | 1 | 2µs | # spent 2µs within Capture::Tiny::BEGIN@9 which was called:
# once (2µs+0s) by main::BEGIN@26 at line 9 # spent 2µs making 1 call to Capture::Tiny::BEGIN@9 |
| 10 | 2 | 11µs | 1 | 2µs | # spent 2µs within Capture::Tiny::BEGIN@10 which was called:
# once (2µs+0s) by main::BEGIN@26 at line 10 # spent 2µs making 1 call to Capture::Tiny::BEGIN@10 |
| 11 | 2 | 17µs | 2 | 43µs | # spent 24µs (5+19) within Capture::Tiny::BEGIN@11 which was called:
# once (5µs+19µs) by main::BEGIN@26 at line 11 # spent 24µs making 1 call to Capture::Tiny::BEGIN@11
# spent 19µs making 1 call to Exporter::import |
| 12 | 2 | 40µs | 2 | 24µs | # spent 15µs (5+10) within Capture::Tiny::BEGIN@12 which was called:
# once (5µs+10µs) by main::BEGIN@26 at line 12 # spent 15µs making 1 call to Capture::Tiny::BEGIN@12
# spent 10µs making 1 call to Exporter::import |
| 13 | # Get PerlIO or fake it | ||||
| 14 | # spent 166µs (165+1) within Capture::Tiny::BEGIN@14 which was called:
# once (165µs+1µs) by main::BEGIN@26 at line 18 | ||||
| 15 | 1 | 200ns | local $@; | ||
| 16 | 2 | 161µs | 1 | 1µs | eval { require PerlIO; PerlIO->can('get_layers') } # spent 1µs making 1 call to UNIVERSAL::can |
| 17 | 1 | 2µs | or *PerlIO::get_layers = sub { return () }; | ||
| 18 | 1 | 160µs | 1 | 166µs | } # spent 166µs making 1 call to Capture::Tiny::BEGIN@14 |
| 19 | |||||
| 20 | #--------------------------------------------------------------------------# | ||||
| 21 | # create API subroutines and export them | ||||
| 22 | # [do STDOUT flag, do STDERR flag, do merge flag, do tee flag] | ||||
| 23 | #--------------------------------------------------------------------------# | ||||
| 24 | |||||
| 25 | 1 | 3µs | my %api = ( | ||
| 26 | capture => [1,1,0,0], | ||||
| 27 | capture_stdout => [1,0,0,0], | ||||
| 28 | capture_stderr => [0,1,0,0], | ||||
| 29 | capture_merged => [1,1,1,0], | ||||
| 30 | tee => [1,1,0,1], | ||||
| 31 | tee_stdout => [1,0,0,1], | ||||
| 32 | tee_stderr => [0,1,0,1], | ||||
| 33 | tee_merged => [1,1,1,1], | ||||
| 34 | ); | ||||
| 35 | |||||
| 36 | 1 | 1µs | for my $sub ( keys %api ) { | ||
| 37 | 8 | 7µs | my $args = join q{, }, @{$api{$sub}}; | ||
| 38 | 8 | 177µs | eval "sub $sub(&;@) {unshift \@_, $args; goto \\&_capture_tee;}"; ## no critic # spent 1.22s executing statements in string eval # includes 487ms spent executing 133035 calls to 1 sub defined therein. | ||
| 39 | } | ||||
| 40 | |||||
| 41 | 1 | 9µs | our @ISA = qw/Exporter/; | ||
| 42 | 1 | 900ns | our @EXPORT_OK = keys %api; | ||
| 43 | 1 | 700ns | our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); | ||
| 44 | |||||
| 45 | #--------------------------------------------------------------------------# | ||||
| 46 | # constants and fixtures | ||||
| 47 | #--------------------------------------------------------------------------# | ||||
| 48 | |||||
| 49 | 1 | 1µs | my $IS_WIN32 = $^O eq 'MSWin32'; | ||
| 50 | |||||
| 51 | ##our $DEBUG = $ENV{PERL_CAPTURE_TINY_DEBUG}; | ||||
| 52 | ## | ||||
| 53 | ##my $DEBUGFH; | ||||
| 54 | ##open $DEBUGFH, "> DEBUG" if $DEBUG; | ||||
| 55 | ## | ||||
| 56 | ##*_debug = $DEBUG ? sub(@) { print {$DEBUGFH} @_ } : sub(){0}; | ||||
| 57 | |||||
| 58 | 1 | 100ns | our $TIMEOUT = 30; | ||
| 59 | |||||
| 60 | #--------------------------------------------------------------------------# | ||||
| 61 | # command to tee output -- the argument is a filename that must | ||||
| 62 | # be opened to signal that the process is ready to receive input. | ||||
| 63 | # This is annoying, but seems to be the best that can be done | ||||
| 64 | # as a simple, portable IPC technique | ||||
| 65 | #--------------------------------------------------------------------------# | ||||
| 66 | 1 | 1µs | my @cmd = ($^X, '-C0', '-e', <<'HERE'); | ||
| 67 | use Fcntl; | ||||
| 68 | $SIG{HUP}=sub{exit}; | ||||
| 69 | if ( my $fn=shift ) { | ||||
| 70 | sysopen(my $fh, qq{$fn}, O_WRONLY|O_CREAT|O_EXCL) or die $!; | ||||
| 71 | print {$fh} $$; | ||||
| 72 | close $fh; | ||||
| 73 | } | ||||
| 74 | my $buf; while (sysread(STDIN, $buf, 2048)) { | ||||
| 75 | syswrite(STDOUT, $buf); syswrite(STDERR, $buf); | ||||
| 76 | } | ||||
| 77 | HERE | ||||
| 78 | |||||
| 79 | #--------------------------------------------------------------------------# | ||||
| 80 | # filehandle manipulation | ||||
| 81 | #--------------------------------------------------------------------------# | ||||
| 82 | |||||
| 83 | sub _relayer { | ||||
| 84 | 266070 | 138ms | my ($fh, $apply_layers) = @_; | ||
| 85 | # _debug("# requested layers (@{$layers}) for @{[fileno $fh]}\n"); | ||||
| 86 | |||||
| 87 | # eliminate pseudo-layers | ||||
| 88 | 266070 | 1.16s | 266070 | 389ms | binmode( $fh, ":raw" ); # spent 389ms making 266070 calls to Capture::Tiny::CORE:binmode, avg 1µs/call |
| 89 | # strip off real layers until only :unix is left | ||||
| 90 | 266070 | 3.44s | 798210 | 995ms | while ( 1 < ( my $layers =()= PerlIO::get_layers( $fh, output => 1 ) ) ) { # spent 762ms making 532140 calls to PerlIO::get_layers, avg 1µs/call
# spent 234ms making 266070 calls to Capture::Tiny::CORE:binmode, avg 878ns/call |
| 91 | binmode( $fh, ":pop" ); | ||||
| 92 | } | ||||
| 93 | # apply other layers | ||||
| 94 | 266070 | 471ms | my @to_apply = @$apply_layers; | ||
| 95 | 266070 | 91.1ms | shift @to_apply; # eliminate initial :unix | ||
| 96 | # _debug("# applying layers (unix @to_apply) to @{[fileno $fh]}\n"); | ||||
| 97 | 266070 | 2.22s | 266070 | 604ms | binmode($fh, ":" . join(":",@to_apply)); # spent 604ms making 266070 calls to Capture::Tiny::CORE:binmode, avg 2µs/call |
| 98 | } | ||||
| 99 | |||||
| 100 | sub _name { | ||||
| 101 | my $glob = shift; | ||||
| 102 | 2 | 1.36ms | 2 | 16µs | # spent 10µs (5+5) within Capture::Tiny::BEGIN@102 which was called:
# once (5µs+5µs) by main::BEGIN@26 at line 102 # spent 10µs making 1 call to Capture::Tiny::BEGIN@102
# spent 5µs making 1 call to strict::unimport |
| 103 | return *{$glob}{NAME}; | ||||
| 104 | } | ||||
| 105 | |||||
| 106 | # spent 13.7s (3.70+10.0) within Capture::Tiny::_open which was called 798210 times, avg 17µs/call:
# 266070 times (1.91s+4.29s) by Capture::Tiny::_open_std at line 194, avg 23µs/call
# 266070 times (850ms+3.27s) by Capture::Tiny::_open_std at line 195, avg 15µs/call
# 266070 times (943ms+2.46s) by Capture::Tiny::_copy_std at line 184, avg 13µs/call | ||||
| 107 | 798210 | 14.8s | 798210 | 10.0s | open $_[0], $_[1] or Carp::confess "Error from open(" . join(q{, }, @_) . "): $!"; # spent 10.0s making 798210 calls to Capture::Tiny::CORE:open, avg 13µs/call |
| 108 | # _debug( "# open " . join( ", " , map { defined $_ ? _name($_) : 'undef' } @_ ) . " as " . fileno( $_[0] ) . "\n" ); | ||||
| 109 | } | ||||
| 110 | |||||
| 111 | # spent 2.18s (989ms+1.19) within Capture::Tiny::_close which was called 266070 times, avg 8µs/call:
# 266070 times (989ms+1.19s) by Capture::Tiny::_capture_tee at line 392, avg 8µs/call | ||||
| 112 | # _debug( "# closing " . ( defined $_[0] ? _name($_[0]) : 'undef' ) . " on " . fileno( $_[0] ) . "\n" ); | ||||
| 113 | 266070 | 2.67s | 266070 | 1.19s | close $_[0] or Carp::confess "Error from close(" . join(q{, }, @_) . "): $!"; # spent 1.19s making 266070 calls to Capture::Tiny::CORE:close, avg 4µs/call |
| 114 | } | ||||
| 115 | |||||
| 116 | 1 | 200ns | my %dup; # cache this so STDIN stays fd0 | ||
| 117 | my %proxy_count; | ||||
| 118 | # spent 553ms within Capture::Tiny::_proxy_std which was called 133035 times, avg 4µs/call:
# 133035 times (553ms+0s) by Capture::Tiny::_capture_tee at line 350, avg 4µs/call | ||||
| 119 | 133035 | 23.7ms | my %proxies; | ||
| 120 | 133035 | 57.6ms | if ( ! defined fileno STDIN ) { | ||
| 121 | $proxy_count{stdin}++; | ||||
| 122 | if (defined $dup{stdin}) { | ||||
| 123 | _open \*STDIN, "<&=" . fileno($dup{stdin}); | ||||
| 124 | # _debug( "# restored proxy STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); | ||||
| 125 | } | ||||
| 126 | else { | ||||
| 127 | _open \*STDIN, "<" . File::Spec->devnull; | ||||
| 128 | # _debug( "# proxied STDIN as " . (defined fileno STDIN ? fileno STDIN : 'undef' ) . "\n" ); | ||||
| 129 | _open $dup{stdin} = IO::Handle->new, "<&=STDIN"; | ||||
| 130 | } | ||||
| 131 | $proxies{stdin} = \*STDIN; | ||||
| 132 | binmode(STDIN, ':utf8') if $] >= 5.008; ## no critic | ||||
| 133 | } | ||||
| 134 | 133035 | 249ms | if ( ! defined fileno STDOUT ) { | ||
| 135 | $proxy_count{stdout}++; | ||||
| 136 | if (defined $dup{stdout}) { | ||||
| 137 | _open \*STDOUT, ">&=" . fileno($dup{stdout}); | ||||
| 138 | # _debug( "# restored proxy STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); | ||||
| 139 | } | ||||
| 140 | else { | ||||
| 141 | _open \*STDOUT, ">" . File::Spec->devnull; | ||||
| 142 | # _debug( "# proxied STDOUT as " . (defined fileno STDOUT ? fileno STDOUT : 'undef' ) . "\n" ); | ||||
| 143 | _open $dup{stdout} = IO::Handle->new, ">&=STDOUT"; | ||||
| 144 | } | ||||
| 145 | $proxies{stdout} = \*STDOUT; | ||||
| 146 | binmode(STDOUT, ':utf8') if $] >= 5.008; ## no critic | ||||
| 147 | } | ||||
| 148 | 133035 | 36.7ms | if ( ! defined fileno STDERR ) { | ||
| 149 | $proxy_count{stderr}++; | ||||
| 150 | if (defined $dup{stderr}) { | ||||
| 151 | _open \*STDERR, ">&=" . fileno($dup{stderr}); | ||||
| 152 | # _debug( "# restored proxy STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); | ||||
| 153 | } | ||||
| 154 | else { | ||||
| 155 | _open \*STDERR, ">" . File::Spec->devnull; | ||||
| 156 | # _debug( "# proxied STDERR as " . (defined fileno STDERR ? fileno STDERR : 'undef' ) . "\n" ); | ||||
| 157 | _open $dup{stderr} = IO::Handle->new, ">&=STDERR"; | ||||
| 158 | } | ||||
| 159 | $proxies{stderr} = \*STDERR; | ||||
| 160 | binmode(STDERR, ':utf8') if $] >= 5.008; ## no critic | ||||
| 161 | } | ||||
| 162 | 133035 | 439ms | return %proxies; | ||
| 163 | } | ||||
| 164 | |||||
| 165 | # spent 479ms within Capture::Tiny::_unproxy which was called 133035 times, avg 4µs/call:
# 133035 times (479ms+0s) by Capture::Tiny::_capture_tee at line 396, avg 4µs/call | ||||
| 166 | 133035 | 56.9ms | my (%proxies) = @_; | ||
| 167 | # _debug( "# unproxying: " . join(" ", keys %proxies) . "\n" ); | ||||
| 168 | 133035 | 801ms | for my $p ( keys %proxies ) { | ||
| 169 | $proxy_count{$p}--; | ||||
| 170 | # _debug( "# unproxied " . uc($p) . " ($proxy_count{$p} left)\n" ); | ||||
| 171 | if ( ! $proxy_count{$p} ) { | ||||
| 172 | _close $proxies{$p}; | ||||
| 173 | _close $dup{$p} unless $] < 5.008; # 5.6 will have already closed this as dup | ||||
| 174 | delete $dup{$p}; | ||||
| 175 | } | ||||
| 176 | } | ||||
| 177 | } | ||||
| 178 | |||||
| 179 | # spent 12.4s (3.59+8.84) within Capture::Tiny::_copy_std which was called 133035 times, avg 93µs/call:
# 133035 times (3.59s+8.84s) by Capture::Tiny::_capture_tee at line 357, avg 93µs/call | ||||
| 180 | 133035 | 33.3ms | my %handles; | ||
| 181 | 133035 | 314ms | for my $h ( qw/stdout stderr stdin/ ) { | ||
| 182 | 399105 | 128ms | next if $h eq 'stdin' && ! $IS_WIN32; # WIN32 hangs on tee without STDIN copied | ||
| 183 | 266070 | 275ms | my $redir = $h eq 'stdin' ? "<&" : ">&"; | ||
| 184 | 266070 | 2.20s | 532140 | 8.84s | _open $handles{$h} = IO::Handle->new(), $redir . uc($h); # ">&STDOUT" or "<&STDIN" # spent 5.43s making 266070 calls to IO::Handle::new, avg 20µs/call
# spent 3.40s making 266070 calls to Capture::Tiny::_open, avg 13µs/call |
| 185 | } | ||||
| 186 | 133035 | 372ms | return \%handles; | ||
| 187 | } | ||||
| 188 | |||||
| 189 | # In some cases we open all (prior to forking) and in others we only open | ||||
| 190 | # the output handles (setting up redirection) | ||||
| 191 | sub _open_std { | ||||
| 192 | 266070 | 133ms | my ($handles) = @_; | ||
| 193 | 266070 | 125ms | _open \*STDIN, "<&" . fileno $handles->{stdin} if defined $handles->{stdin}; | ||
| 194 | 266070 | 1.33s | 266070 | 6.20s | _open \*STDOUT, ">&" . fileno $handles->{stdout} if defined $handles->{stdout}; # spent 6.20s making 266070 calls to Capture::Tiny::_open, avg 23µs/call |
| 195 | 266070 | 1.48s | 266070 | 4.12s | _open \*STDERR, ">&" . fileno $handles->{stderr} if defined $handles->{stderr}; # spent 4.12s making 266070 calls to Capture::Tiny::_open, avg 15µs/call |
| 196 | } | ||||
| 197 | |||||
| 198 | #--------------------------------------------------------------------------# | ||||
| 199 | # private subs | ||||
| 200 | #--------------------------------------------------------------------------# | ||||
| 201 | |||||
| 202 | sub _start_tee { | ||||
| 203 | my ($which, $stash) = @_; # $which is "stdout" or "stderr" | ||||
| 204 | # setup pipes | ||||
| 205 | $stash->{$_}{$which} = IO::Handle->new for qw/tee reader/; | ||||
| 206 | pipe $stash->{reader}{$which}, $stash->{tee}{$which}; | ||||
| 207 | # _debug( "# pipe for $which\: " . _name($stash->{tee}{$which}) . " " . fileno( $stash->{tee}{$which} ) . " => " . _name($stash->{reader}{$which}) . " " . fileno( $stash->{reader}{$which}) . "\n" ); | ||||
| 208 | select((select($stash->{tee}{$which}), $|=1)[0]); # autoflush | ||||
| 209 | # setup desired redirection for parent and child | ||||
| 210 | $stash->{new}{$which} = $stash->{tee}{$which}; | ||||
| 211 | $stash->{child}{$which} = { | ||||
| 212 | stdin => $stash->{reader}{$which}, | ||||
| 213 | stdout => $stash->{old}{$which}, | ||||
| 214 | stderr => $stash->{capture}{$which}, | ||||
| 215 | }; | ||||
| 216 | # flag file is used to signal the child is ready | ||||
| 217 | $stash->{flag_files}{$which} = scalar( tmpnam() ) . $$; | ||||
| 218 | # execute @cmd as a separate process | ||||
| 219 | if ( $IS_WIN32 ) { | ||||
| 220 | my $old_eval_err=$@; | ||||
| 221 | undef $@; | ||||
| 222 | |||||
| 223 | eval "use Win32API::File qw/GetOsFHandle SetHandleInformation fileLastError HANDLE_FLAG_INHERIT INVALID_HANDLE_VALUE/ "; | ||||
| 224 | # _debug( "# Win32API::File loaded\n") unless $@; | ||||
| 225 | my $os_fhandle = GetOsFHandle( $stash->{tee}{$which} ); | ||||
| 226 | # _debug( "# Couldn't get OS handle: " . fileLastError() . "\n") if ! defined $os_fhandle || $os_fhandle == INVALID_HANDLE_VALUE(); | ||||
| 227 | my $result = SetHandleInformation( $os_fhandle, HANDLE_FLAG_INHERIT(), 0); | ||||
| 228 | # _debug( $result ? "# set no-inherit flag on $which tee\n" : ("# can't disable tee handle flag inherit: " . fileLastError() . "\n")); | ||||
| 229 | _open_std( $stash->{child}{$which} ); | ||||
| 230 | $stash->{pid}{$which} = system(1, @cmd, $stash->{flag_files}{$which}); | ||||
| 231 | # not restoring std here as it all gets redirected again shortly anyway | ||||
| 232 | $@=$old_eval_err; | ||||
| 233 | } | ||||
| 234 | else { # use fork | ||||
| 235 | _fork_exec( $which, $stash ); | ||||
| 236 | } | ||||
| 237 | } | ||||
| 238 | |||||
| 239 | sub _fork_exec { | ||||
| 240 | my ($which, $stash) = @_; # $which is "stdout" or "stderr" | ||||
| 241 | my $pid = fork; | ||||
| 242 | if ( not defined $pid ) { | ||||
| 243 | Carp::confess "Couldn't fork(): $!"; | ||||
| 244 | } | ||||
| 245 | elsif ($pid == 0) { # child | ||||
| 246 | # _debug( "# in child process ...\n" ); | ||||
| 247 | untie *STDIN; untie *STDOUT; untie *STDERR; | ||||
| 248 | _close $stash->{tee}{$which}; | ||||
| 249 | # _debug( "# redirecting handles in child ...\n" ); | ||||
| 250 | _open_std( $stash->{child}{$which} ); | ||||
| 251 | # _debug( "# calling exec on command ...\n" ); | ||||
| 252 | exec @cmd, $stash->{flag_files}{$which}; | ||||
| 253 | } | ||||
| 254 | $stash->{pid}{$which} = $pid | ||||
| 255 | } | ||||
| 256 | |||||
| 257 | 1 | 22µs | my $have_usleep = eval "use Time::HiRes 'usleep'; 1"; # spent 104µs executing statements in string eval # includes 522µs spent executing 1 call to 1 sub defined therein. | ||
| 258 | sub _files_exist { | ||||
| 259 | return 1 if @_ == grep { -f } @_; | ||||
| 260 | Time::HiRes::usleep(1000) if $have_usleep; | ||||
| 261 | return 0; | ||||
| 262 | } | ||||
| 263 | |||||
| 264 | sub _wait_for_tees { | ||||
| 265 | my ($stash) = @_; | ||||
| 266 | my $start = time; | ||||
| 267 | my @files = values %{$stash->{flag_files}}; | ||||
| 268 | my $timeout = defined $ENV{PERL_CAPTURE_TINY_TIMEOUT} | ||||
| 269 | ? $ENV{PERL_CAPTURE_TINY_TIMEOUT} : $TIMEOUT; | ||||
| 270 | 1 until _files_exist(@files) || ($timeout && (time - $start > $timeout)); | ||||
| 271 | Carp::confess "Timed out waiting for subprocesses to start" if ! _files_exist(@files); | ||||
| 272 | unlink $_ for @files; | ||||
| 273 | } | ||||
| 274 | |||||
| 275 | sub _kill_tees { | ||||
| 276 | my ($stash) = @_; | ||||
| 277 | if ( $IS_WIN32 ) { | ||||
| 278 | # _debug( "# closing handles\n"); | ||||
| 279 | close($_) for values %{ $stash->{tee} }; | ||||
| 280 | # _debug( "# waiting for subprocesses to finish\n"); | ||||
| 281 | my $start = time; | ||||
| 282 | 1 until wait == -1 || (time - $start > 30); | ||||
| 283 | } | ||||
| 284 | else { | ||||
| 285 | _close $_ for values %{ $stash->{tee} }; | ||||
| 286 | waitpid $_, 0 for values %{ $stash->{pid} }; | ||||
| 287 | } | ||||
| 288 | } | ||||
| 289 | |||||
| 290 | sub _slurp { | ||||
| 291 | my ($name, $stash) = @_; | ||||
| 292 | my ($fh, $pos) = map { $stash->{$_}{$name} } qw/capture pos/; | ||||
| 293 | # _debug( "# slurping captured $name from " . fileno($fh) . " at pos $pos with layers: @{[PerlIO::get_layers($fh)]}\n"); | ||||
| 294 | seek( $fh, $pos, 0 ) or die "Couldn't seek on capture handle for $name\n"; | ||||
| 295 | my $text = do { local $/; scalar readline $fh }; | ||||
| 296 | return defined($text) ? $text : ""; | ||||
| 297 | } | ||||
| 298 | |||||
| 299 | #--------------------------------------------------------------------------# | ||||
| 300 | # _capture_tee() -- generic main sub for capturing or teeing | ||||
| 301 | #--------------------------------------------------------------------------# | ||||
| 302 | |||||
| 303 | # spent 53695s (20.9+53674) within Capture::Tiny::_capture_tee which was called 133035 times, avg 404ms/call:
# 133035 times (20.9s+53674s) by main::__ANON__[split.pl:90] at line 1 of (eval 16)[Capture/Tiny.pm:38], avg 404ms/call | ||||
| 304 | # _debug( "# starting _capture_tee with (@_)...\n" ); | ||||
| 305 | 133035 | 66.7ms | my ($do_stdout, $do_stderr, $do_merge, $do_tee, $code, @opts) = @_; | ||
| 306 | 133035 | 153ms | my %do = ($do_stdout ? (stdout => 1) : (), $do_stderr ? (stderr => 1) : ()); | ||
| 307 | 133035 | 93.6ms | Carp::confess("Custom capture options must be given as key/value pairs\n") | ||
| 308 | unless @opts % 2 == 0; | ||||
| 309 | 133035 | 153ms | my $stash = { capture => { @opts } }; | ||
| 310 | 133035 | 175ms | for ( keys %{$stash->{capture}} ) { | ||
| 311 | my $fh = $stash->{capture}{$_}; | ||||
| 312 | Carp::confess "Custom handle for $_ must be seekable\n" | ||||
| 313 | unless ref($fh) eq 'GLOB' || (blessed($fh) && $fh->isa("IO::Seekable")); | ||||
| 314 | } | ||||
| 315 | # save existing filehandles and setup captures | ||||
| 316 | 133035 | 320ms | local *CT_ORIG_STDIN = *STDIN ; | ||
| 317 | 133035 | 51.8ms | local *CT_ORIG_STDOUT = *STDOUT; | ||
| 318 | 133035 | 220ms | local *CT_ORIG_STDERR = *STDERR; | ||
| 319 | # find initial layers | ||||
| 320 | 133035 | 1.80s | 399105 | 471ms | my %layers = ( # spent 471ms making 399105 calls to PerlIO::get_layers, avg 1µs/call |
| 321 | stdin => [PerlIO::get_layers(\*STDIN) ], | ||||
| 322 | stdout => [PerlIO::get_layers(\*STDOUT, output => 1)], | ||||
| 323 | stderr => [PerlIO::get_layers(\*STDERR, output => 1)], | ||||
| 324 | ); | ||||
| 325 | # _debug( "# existing layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; | ||||
| 326 | # get layers from underlying glob of tied filehandles if we can | ||||
| 327 | # (this only works for things that work like Tie::StdHandle) | ||||
| 328 | 133035 | 51.5ms | $layers{stdout} = [PerlIO::get_layers(tied *STDOUT)] | ||
| 329 | if tied(*STDOUT) && (reftype tied *STDOUT eq 'GLOB'); | ||||
| 330 | 133035 | 34.2ms | $layers{stderr} = [PerlIO::get_layers(tied *STDERR)] | ||
| 331 | if tied(*STDERR) && (reftype tied *STDERR eq 'GLOB'); | ||||
| 332 | # _debug( "# tied object corrected layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; | ||||
| 333 | # bypass scalar filehandles and tied handles | ||||
| 334 | # localize scalar STDIN to get a proxy to pick up FD0, then restore later to CT_ORIG_STDIN | ||||
| 335 | 133035 | 37.2ms | my %localize; | ||
| 336 | $localize{stdin}++, local(*STDIN) | ||||
| 337 | 133035 | 180ms | if grep { $_ eq 'scalar' } @{$layers{stdin}}; | ||
| 338 | $localize{stdout}++, local(*STDOUT) | ||||
| 339 | 133035 | 118ms | if $do_stdout && grep { $_ eq 'scalar' } @{$layers{stdout}}; | ||
| 340 | $localize{stderr}++, local(*STDERR) | ||||
| 341 | 133035 | 47.1ms | if ($do_stderr || $do_merge) && grep { $_ eq 'scalar' } @{$layers{stderr}}; | ||
| 342 | 133035 | 40.3ms | $localize{stdin}++, local(*STDIN), _open( \*STDIN, "<&=0") | ||
| 343 | if tied *STDIN && $] >= 5.008; | ||||
| 344 | 133035 | 45.9ms | $localize{stdout}++, local(*STDOUT), _open( \*STDOUT, ">&=1") | ||
| 345 | if $do_stdout && tied *STDOUT && $] >= 5.008; | ||||
| 346 | 133035 | 43.5ms | $localize{stderr}++, local(*STDERR), _open( \*STDERR, ">&=2") | ||
| 347 | if ($do_stderr || $do_merge) && tied *STDERR && $] >= 5.008; | ||||
| 348 | # _debug( "# localized $_\n" ) for keys %localize; | ||||
| 349 | # proxy any closed/localized handles so we don't use fds 0, 1 or 2 | ||||
| 350 | 133035 | 206ms | 133035 | 553ms | my %proxy_std = _proxy_std(); # spent 553ms making 133035 calls to Capture::Tiny::_proxy_std, avg 4µs/call |
| 351 | # _debug( "# proxy std: @{ [%proxy_std] }\n" ); | ||||
| 352 | # update layers after any proxying | ||||
| 353 | 133035 | 58.6ms | $layers{stdout} = [PerlIO::get_layers(\*STDOUT, output => 1)] if $proxy_std{stdout}; | ||
| 354 | 133035 | 31.1ms | $layers{stderr} = [PerlIO::get_layers(\*STDERR, output => 1)] if $proxy_std{stderr}; | ||
| 355 | # _debug( "# post-proxy layers for $_\: @{$layers{$_}}\n" ) for qw/stdin stdout stderr/; | ||||
| 356 | # store old handles and setup handles for capture | ||||
| 357 | 133035 | 217ms | 133035 | 12.4s | $stash->{old} = _copy_std(); # spent 12.4s making 133035 calls to Capture::Tiny::_copy_std, avg 93µs/call |
| 358 | 133035 | 237ms | $stash->{new} = { %{$stash->{old}} }; # default to originals | ||
| 359 | 133035 | 132ms | for ( keys %do ) { | ||
| 360 | 133035 | 1.15s | 133035 | 42.8s | $stash->{new}{$_} = ($stash->{capture}{$_} ||= File::Temp->new); # spent 42.8s making 133035 calls to File::Temp::new, avg 321µs/call |
| 361 | 133035 | 718ms | 133035 | 305ms | seek( $stash->{capture}{$_}, 0, 2 ) or die "Could not seek on capture handle for $_\n"; # spent 305ms making 133035 calls to Capture::Tiny::CORE:seek, avg 2µs/call |
| 362 | 133035 | 462ms | 133035 | 36.9ms | $stash->{pos}{$_} = tell $stash->{capture}{$_}; # spent 36.9ms making 133035 calls to Capture::Tiny::CORE:tell, avg 277ns/call |
| 363 | # _debug("# will capture $_ on " . fileno($stash->{capture}{$_})."\n" ); | ||||
| 364 | 133035 | 71.3ms | _start_tee( $_ => $stash ) if $do_tee; # tees may change $stash->{new} | ||
| 365 | } | ||||
| 366 | 133035 | 22.8ms | _wait_for_tees( $stash ) if $do_tee; | ||
| 367 | # finalize redirection | ||||
| 368 | 133035 | 17.6ms | $stash->{new}{stderr} = $stash->{new}{stdout} if $do_merge; | ||
| 369 | # _debug( "# redirecting in parent ...\n" ); | ||||
| 370 | 133035 | 196ms | 133035 | 4.95s | _open_std( $stash->{new} ); # spent 4.95s making 133035 calls to Capture::Tiny::_open_std, avg 37µs/call |
| 371 | # execute user provided code | ||||
| 372 | 133035 | 33.5ms | my ($exit_code, $inner_error, $outer_error, $orig_pid, @result); | ||
| 373 | { | ||||
| 374 | 266070 | 198ms | $orig_pid = $$; | ||
| 375 | 133035 | 35.7ms | local *STDIN = *CT_ORIG_STDIN if $localize{stdin}; # get original, not proxy STDIN | ||
| 376 | # _debug( "# finalizing layers ...\n" ); | ||||
| 377 | 133035 | 231ms | 133035 | 3.15s | _relayer(\*STDOUT, $layers{stdout}) if $do_stdout; # spent 3.15s making 133035 calls to Capture::Tiny::_relayer, avg 24µs/call |
| 378 | 133035 | 23.3ms | _relayer(\*STDERR, $layers{stderr}) if $do_stderr; | ||
| 379 | # _debug( "# running code $code ...\n" ); | ||||
| 380 | 133035 | 42.0ms | my $old_eval_err=$@; | ||
| 381 | 133035 | 43.4ms | undef $@; | ||
| 382 | 399105 | 1.20s | 133035 | 53563s | eval { @result = $code->(); $inner_error = $@ }; # spent 53563s making 133035 calls to main::__ANON__[split.pl:88], avg 403ms/call |
| 383 | 133035 | 63.6ms | $exit_code = $?; # save this for later | ||
| 384 | 133035 | 26.0ms | $outer_error = $@; # save this for later | ||
| 385 | 133035 | 1.25s | 133035 | 357ms | STDOUT->flush if $do_stdout; # spent 357ms making 133035 calls to IO::Handle::flush, avg 3µs/call |
| 386 | 133035 | 27.8ms | STDERR->flush if $do_stderr; | ||
| 387 | 133035 | 66.5ms | $@ = $old_eval_err; | ||
| 388 | } | ||||
| 389 | # restore prior filehandles and shut down tees | ||||
| 390 | # _debug( "# restoring filehandles ...\n" ); | ||||
| 391 | 133035 | 687ms | 133035 | 8.85s | _open_std( $stash->{old} ); # spent 8.85s making 133035 calls to Capture::Tiny::_open_std, avg 67µs/call |
| 392 | 133035 | 509ms | 266070 | 2.18s | _close( $_ ) for values %{$stash->{old}}; # don't leak fds # spent 2.18s making 266070 calls to Capture::Tiny::_close, avg 8µs/call |
| 393 | # shouldn't need relayering originals, but see rt.perl.org #114404 | ||||
| 394 | 133035 | 469ms | 133035 | 4.12s | _relayer(\*STDOUT, $layers{stdout}) if $do_stdout; # spent 4.12s making 133035 calls to Capture::Tiny::_relayer, avg 31µs/call |
| 395 | 133035 | 28.0ms | _relayer(\*STDERR, $layers{stderr}) if $do_stderr; | ||
| 396 | 133035 | 357ms | 133035 | 479ms | _unproxy( %proxy_std ); # spent 479ms making 133035 calls to Capture::Tiny::_unproxy, avg 4µs/call |
| 397 | # _debug( "# killing tee subprocesses ...\n" ) if $do_tee; | ||||
| 398 | 133035 | 25.7ms | _kill_tees( $stash ) if $do_tee; | ||
| 399 | # return captured output, but shortcut in void context | ||||
| 400 | # unless we have to echo output to tied/scalar handles; | ||||
| 401 | 133035 | 202ms | my %got; | ||
| 402 | 133035 | 256ms | if ( $orig_pid == $$ and ( defined wantarray or ($do_tee && keys %localize) ) ) { | ||
| 403 | for ( keys %do ) { | ||||
| 404 | _relayer($stash->{capture}{$_}, $layers{$_}); | ||||
| 405 | $got{$_} = _slurp($_, $stash); | ||||
| 406 | # _debug("# slurped " . length($got{$_}) . " bytes from $_\n"); | ||||
| 407 | } | ||||
| 408 | print CT_ORIG_STDOUT $got{stdout} | ||||
| 409 | if $do_stdout && $do_tee && $localize{stdout}; | ||||
| 410 | print CT_ORIG_STDERR $got{stderr} | ||||
| 411 | if $do_stderr && $do_tee && $localize{stderr}; | ||||
| 412 | } | ||||
| 413 | 133035 | 96.4ms | $? = $exit_code; | ||
| 414 | 133035 | 32.7ms | $@ = $inner_error if $inner_error; | ||
| 415 | 133035 | 16.2ms | die $outer_error if $outer_error; | ||
| 416 | # _debug( "# ending _capture_tee with (@_)...\n" ); | ||||
| 417 | 133035 | 6.95s | 133035 | 30.3s | return unless defined wantarray; # spent 30.3s making 133035 calls to File::Temp::DESTROY, avg 228µs/call |
| 418 | my @return; | ||||
| 419 | push @return, $got{stdout} if $do_stdout; | ||||
| 420 | push @return, $got{stderr} if $do_stderr && ! $do_merge; | ||||
| 421 | push @return, @result; | ||||
| 422 | return wantarray ? @return : $return[0]; | ||||
| 423 | } | ||||
| 424 | |||||
| 425 | 1 | 14µs | 1; | ||
| 426 | |||||
| 427 | __END__ | ||||
# spent 1.23s within Capture::Tiny::CORE:binmode which was called 798210 times, avg 2µs/call:
# 266070 times (604ms+0s) by Capture::Tiny::_relayer at line 97, avg 2µs/call
# 266070 times (389ms+0s) by Capture::Tiny::_relayer at line 88, avg 1µs/call
# 266070 times (234ms+0s) by Capture::Tiny::_relayer at line 90, avg 878ns/call | |||||
# spent 1.19s within Capture::Tiny::CORE:close which was called 266070 times, avg 4µs/call:
# 266070 times (1.19s+0s) by Capture::Tiny::_close at line 113, avg 4µs/call | |||||
# spent 10.0s within Capture::Tiny::CORE:open which was called 798210 times, avg 13µs/call:
# 798210 times (10.0s+0s) by Capture::Tiny::_open at line 107, avg 13µs/call | |||||
# spent 305ms within Capture::Tiny::CORE:seek which was called 133035 times, avg 2µs/call:
# 133035 times (305ms+0s) by Capture::Tiny::_capture_tee at line 361, avg 2µs/call | |||||
# spent 36.9ms within Capture::Tiny::CORE:tell which was called 133035 times, avg 277ns/call:
# 133035 times (36.9ms+0s) by Capture::Tiny::_capture_tee at line 362, avg 277ns/call |