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_tee | Capture::Tiny::
798210 | 1 | 1 | 10.0s | 10.0s | CORE:open (opcode) | Capture::Tiny::
266070 | 2 | 1 | 5.29s | 7.28s | _relayer | Capture::Tiny::
798210 | 3 | 1 | 3.70s | 13.7s | _open | Capture::Tiny::
133035 | 1 | 1 | 3.59s | 12.4s | _copy_std | Capture::Tiny::
266070 | 2 | 1 | 3.48s | 13.8s | _open_std | Capture::Tiny::
798210 | 3 | 1 | 1.23s | 1.23s | CORE:binmode (opcode) | Capture::Tiny::
266070 | 1 | 1 | 1.19s | 1.19s | CORE:close (opcode) | Capture::Tiny::
266070 | 1 | 1 | 989ms | 2.18s | _close | Capture::Tiny::
133035 | 1 | 1 | 553ms | 553ms | _proxy_std | Capture::Tiny::
133035 | 1 | 1 | 479ms | 479ms | _unproxy | Capture::Tiny::
133035 | 1 | 1 | 305ms | 305ms | CORE:seek (opcode) | Capture::Tiny::
133035 | 1 | 1 | 36.9ms | 36.9ms | CORE:tell (opcode) | Capture::Tiny::
1 | 1 | 1 | 165µs | 166µs | BEGIN@14 | Capture::Tiny::
1 | 1 | 1 | 13µs | 13µs | BEGIN@1.5 | main::
1 | 1 | 1 | 5µs | 10µs | BEGIN@102 | Capture::Tiny::
1 | 1 | 1 | 5µs | 24µs | BEGIN@11 | Capture::Tiny::
1 | 1 | 1 | 5µs | 15µs | BEGIN@12 | Capture::Tiny::
1 | 1 | 1 | 4µs | 6µs | BEGIN@2 | main::
1 | 1 | 1 | 4µs | 22µs | BEGIN@3.6 | main::
1 | 1 | 1 | 2µs | 2µs | BEGIN@9 | Capture::Tiny::
1 | 1 | 1 | 2µs | 2µs | BEGIN@7 | Capture::Tiny::
1 | 1 | 1 | 2µs | 2µs | BEGIN@10 | Capture::Tiny::
1 | 1 | 1 | 2µs | 2µs | BEGIN@8 | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | __ANON__[:17] | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | _files_exist | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | _fork_exec | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | _kill_tees | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | _name | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | _slurp | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | _start_tee | Capture::Tiny::
0 | 0 | 0 | 0s | 0s | _wait_for_tees | Capture::Tiny::
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 |