File Coverage

blib/lib/Devel/NYTProf/SubInfo.pm
Criterion Covered Total %
statement 204 215 94.8
branch 61 92 66.3
condition 32 47 68.0
subroutine 41 42 97.6
pod 0 28 0.0
total 338 424 79.7


line stmt bran cond sub pod time code
1             package Devel::NYTProf::SubInfo; # sub_subinfo
2              
3 48     48   367 use strict;
  48         122  
  48         1482  
4 48     48   271 use warnings;
  48         97  
  48         1217  
5 48     48   275 use Carp;
  48         96  
  48         2738  
6              
7 48     48   293 use List::Util qw(min max);
  48         107  
  48         2875  
8 48     48   936 use Data::Dumper;
  48         8747  
  48         2749  
9              
10 48         3176 use Devel::NYTProf::Util qw(
11             trace_level
12 48     48   353 );
  48         153  
13 48         6767 use Devel::NYTProf::Constants qw(
14             NYTP_SIi_FID NYTP_SIi_FIRST_LINE NYTP_SIi_LAST_LINE
15             NYTP_SIi_CALL_COUNT NYTP_SIi_INCL_RTIME NYTP_SIi_EXCL_RTIME
16             NYTP_SIi_SUB_NAME NYTP_SIi_PROFILE
17             NYTP_SIi_REC_DEPTH NYTP_SIi_RECI_RTIME NYTP_SIi_CALLED_BY
18             NYTP_SIi_elements
19              
20             NYTP_SCi_CALL_COUNT
21             NYTP_SCi_INCL_RTIME NYTP_SCi_EXCL_RTIME NYTP_SCi_RECI_RTIME
22             NYTP_SCi_REC_DEPTH NYTP_SCi_CALLING_SUB
23             NYTP_SCi_elements
24 48     48   340 );
  48         100  
25              
26             # extra constants for private elements
27             use constant {
28 48         148964 NYTP_SIi_meta => NYTP_SIi_elements + 1,
29             NYTP_SIi_cache => NYTP_SIi_elements + 2,
30 48     48   398 };
  48         166  
31              
32              
33 3346 100   3346 0 22382 sub fid { shift->[NYTP_SIi_FID] || 0 }
34              
35 2376     2376 0 7271 sub first_line { shift->[NYTP_SIi_FIRST_LINE] }
36              
37 2270     2270 0 6327 sub last_line { shift->[NYTP_SIi_LAST_LINE] }
38              
39 6899     6899 0 85309 sub calls { shift->[NYTP_SIi_CALL_COUNT] }
40              
41 7     7 0 2216 sub incl_time { shift->[NYTP_SIi_INCL_RTIME] }
42              
43 1     1 0 5 sub excl_time { shift->[NYTP_SIi_EXCL_RTIME] }
44              
45 11470     11470 0 111929 sub subname { shift->[NYTP_SIi_SUB_NAME] }
46              
47             sub subname_without_package {
48 1     1 0 528 my $subname = shift->[NYTP_SIi_SUB_NAME];
49 1         8 $subname =~ s/.*:://;
50 1         6 return $subname;
51             }
52              
53 2452     2452 0 5772 sub profile { shift->[NYTP_SIi_PROFILE] }
54              
55 1     1 0 3 sub package { (my $pkg = shift->subname) =~ s/^(.*)::.*/$1/; return $pkg }
  1         5  
56              
57 1     1 0 5 sub recur_max_depth { shift->[NYTP_SIi_REC_DEPTH] }
58              
59 1     1 0 5 sub recur_incl_time { shift->[NYTP_SIi_RECI_RTIME] }
60              
61              
62             # general purpose hash - mainly a hack to help kill off Reader.pm
63 2390   100 2390 0 21203 sub meta { shift->[NYTP_SIi_meta()] ||= {} }
64             # general purpose cache
65 1   50 1 0 11 sub cache { shift->[NYTP_SIi_cache()] ||= {} }
66              
67              
68             # { fid => { line => [ count, incl_time ] } }
69             sub caller_fid_line_places {
70 2558     2558 0 61582 my ($self, $merge_evals) = @_;
71 2558 50       6185 carp "caller_fid_line_places doesn't merge evals yet" if $merge_evals;
72             # shallow clone to remove fid 0 is_sub hack
73 2558 100       4140 my %tmp = %{ $self->[NYTP_SIi_CALLED_BY] || {} };
  2558         15061  
74 2558         6479 delete $tmp{0};
75 2558         9181 return \%tmp;
76             }
77              
78             sub called_by_subnames {
79 96     96 0 45424 my ($self) = @_;
80 96   50     442 my $callers = $self->caller_fid_line_places || {};
81              
82 96         250 my %subnames;
83 96         358 for my $sc (map { values %$_ } values %$callers) {
  128         917  
84 144         407 my $caller_subnames = $sc->[NYTP_SCi_CALLING_SUB];
85 144         661 @subnames{ keys %$caller_subnames } = (); # viv keys
86             }
87              
88 96         454 return \%subnames;
89             }
90              
91             sub is_xsub {
92 15     15 0 21 my $self = shift;
93              
94             # XXX should test == 0 but some xsubs still have undef first_line etc
95             # XXX shouldn't include opcode
96 15         24 my $first = $self->first_line;
97 15 50       28 return undef if not defined $first;
98 15 100 66     30 return 1 if $first == 0 && $self->last_line == 0;
99 12         22 return 0;
100             }
101              
102             sub is_opcode {
103 18     18 0 26 my $self = shift;
104 18 100 66     28 return 0 if $self->first_line or $self->last_line;
105 6 100       12 return 1 if $self->subname =~ m/(?:^CORE::|::CORE:)\w+$/;
106 3         9 return 0;
107             }
108              
109             sub is_anon {
110 336     336 0 1141 shift->subname =~ m/::__ANON__\b/;
111             }
112              
113             sub kind {
114 18     18 0 28 my $self = shift;
115 18 100       29 return 'opcode' if $self->is_opcode;
116 15 100       39 return 'xsub' if $self->is_xsub;
117 12         23 return 'perl';
118             }
119              
120             sub fileinfo {
121 1     1 0 2 my $self = shift;
122 1         4 my $fid = $self->fid;
123 1 50       4 if (!$fid) {
124 0         0 return undef; # sub not have a known fid
125             }
126 1         3 $self->profile->fileinfo_of($fid);
127             }
128              
129             sub clone { # shallow
130 2     2 0 3 my $self = shift;
131 2         9 return bless [ @$self ] => ref $self;
132             }
133              
134             sub _min {
135 138     138   415 my ($a, $b) = @_;
136 138 50       445 $a = $b if not defined $a;
137 138 50       383 $b = $a if not defined $b;
138             # either both are defined or both are undefined here
139 138 50       352 return undef unless defined $a;
140 138         717 return min($a, $b);
141             }
142              
143             sub _max {
144 138     138   433 my ($a, $b) = @_;
145 138 50       431 $a = $b if not defined $a;
146 138 50       389 $b = $a if not defined $b;
147             # either both are defined or both are undefined here
148 138 50       311 return undef unless defined $a;
149 138         384 return max($a, $b);
150             }
151              
152              
153             sub _alter_fileinfo {
154 240     240   1158 my ($self, $remove_fi, $new_fi) = @_;
155 240 100       1065 my $remove_fid = ($remove_fi) ? $remove_fi->fid : 0;
156 240 50       1131 my $new_fid = ( $new_fi) ? $new_fi->fid : 0;
157              
158 240 50       680 if ($self->fid == $remove_fid) {
159 240         641 $self->[NYTP_SIi_FID] = $new_fid;
160              
161 240 100       1312 $remove_fi->_remove_sub_defined($self) if $remove_fi;
162 240 50       2289 $new_fi->_add_new_sub_defined($self) if $new_fi;
163             }
164             }
165              
166              
167             sub _alter_called_by_fileinfo {
168 176     176   614 my ($self, $remove_fi, $new_fi) = @_;
169 176 50       874 my $remove_fid = ($remove_fi) ? $remove_fi->fid : 0;
170 176 50       714 my $new_fid = ( $new_fi) ? $new_fi->fid : 0;
171              
172             # remove mentions of $remove_fid from called-by details
173             # { fid => { line => [ count, incl, excl, ... ] } }
174 176 50       642 if (my $called_by = $self->[NYTP_SIi_CALLED_BY]) {
175 176         837 my $cb = delete $called_by->{$remove_fid};
176              
177 176 50 33     2042 if ($cb && $new_fid) {
178 176   100     1023 my $new_cb = $called_by->{$new_fid} ||= {};
179              
180 176 50       820 warn sprintf "_alter_called_by_fileinfo: %s from fid %d to fid %d\n",
181             $self->subname, $remove_fid, $new_fid
182             if trace_level() >= 4;
183              
184             # merge $cb into $new_cb
185 176         1051 while ( my ($line, $cb_li) = each %$cb ) {
186 176   100     886 my $dst_line_info = $new_cb->{$line} ||= [];
187 176         1213 _merge_in_caller_info($dst_line_info, delete $cb->{$line},
188             tag => "$line:".$self->subname,
189             );
190             }
191              
192             }
193             }
194              
195             }
196              
197              
198              
199              
200             # merge details of another sub into this one
201             # there are very few cases where this is sane thing to do
202             # it's meant for merging things like anon-subs in evals
203             # e.g., "PPI::Node::__ANON__[(eval 286)[PPI/Node.pm:642]:4]"
204             sub merge_in {
205 138     138 0 433 my ($self, $donor, %opts) = @_;
206 138         380 my $self_subname = $self->subname;
207 138         366 my $donor_subname = $donor->subname;
208              
209 138 50       527 warn sprintf "Merging sub %s into %s (%s)\n",
210             $donor_subname, $self_subname, join(" ", %opts)
211             if trace_level() >= 4;
212              
213             # see also "case NYTP_TAG_SUB_CALLERS:" in load_profile_data_from_stream()
214 138         252 push @{ $self->meta->{merged_sub_names} }, $donor->subname;
  138         616  
215              
216 138         767 $self->[NYTP_SIi_FIRST_LINE] = _min($self->[NYTP_SIi_FIRST_LINE], $donor->[NYTP_SIi_FIRST_LINE]);
217 138         673 $self->[NYTP_SIi_LAST_LINE] = _max($self->[NYTP_SIi_LAST_LINE], $donor->[NYTP_SIi_LAST_LINE]);
218 138         398 $self->[NYTP_SIi_CALL_COUNT] += $donor->[NYTP_SIi_CALL_COUNT];
219 138         298 $self->[NYTP_SIi_INCL_RTIME] += $donor->[NYTP_SIi_INCL_RTIME];
220 138         267 $self->[NYTP_SIi_EXCL_RTIME] += $donor->[NYTP_SIi_EXCL_RTIME];
221 138         340 $self->[NYTP_SIi_REC_DEPTH] = max($self->[NYTP_SIi_REC_DEPTH], $donor->[NYTP_SIi_REC_DEPTH]);
222             # adding reci_rtime is correct only if one sub doesn't call the other
223 138         302 $self->[NYTP_SIi_RECI_RTIME] += $donor->[NYTP_SIi_RECI_RTIME]; # XXX
224              
225             # { fid => { line => [ count, incl_time, ... ] } }
226 138   50     370 my $dst_called_by = $self ->[NYTP_SIi_CALLED_BY] ||= {};
227 138   100     370 my $src_called_by = $donor->[NYTP_SIi_CALLED_BY] || {};
228              
229 138   33     1690 $opts{opts} ||= "merge in $donor_subname";
230              
231             # iterate over src and merge into dst
232 138         710 while (my ($fid, $src_line_hash) = each %$src_called_by) {
233              
234 135         322 my $dst_line_hash = $dst_called_by->{$fid};
235              
236             # merge lines in %$src_line_hash into %$dst_line_hash
237 135         821 for my $line (keys %$src_line_hash) {
238 148   100     593 my $dst_line_info = $dst_line_hash->{$line} ||= [];
239 148         267 my $src_line_info = $src_line_hash->{$line};
240 148 100       503 delete $src_line_hash->{$line} unless $opts{src_keep};
241 148         522 _merge_in_caller_info($dst_line_info, $src_line_info, %opts);
242             }
243             }
244              
245 138         510 return;
246             }
247              
248              
249             sub _merge_in_caller_info {
250 628     628   2824 my ($dst_line_info, $src_line_info, %opts) = @_;
251 628 100       3313 my $tag = ($opts{tag}) ? " $opts{tag}" : "";
252              
253 628 50       1980 if (!@$src_line_info) {
254 0 0       0 carp sprintf "_merge_in_caller_info%s skipped (empty donor)", $tag
255             if trace_level();
256 0         0 return;
257             }
258              
259 628 50       2435 if (trace_level() >= 5) {
260 0         0 carp sprintf "_merge_in_caller_info%s merging from $src_line_info -> $dst_line_info:", $tag;
261 0         0 warn sprintf " . %s\n", _fmt_sc($src_line_info);
262 0         0 warn sprintf " + %s\n", _fmt_sc($dst_line_info);
263             }
264 628 100       1782 if (!@$dst_line_info) {
265 78         315 @$dst_line_info = (0) x NYTP_SCi_elements;
266 78         168 $dst_line_info->[NYTP_SCi_CALLING_SUB] = undef;
267             }
268              
269             # merge @$src_line_info into @$dst_line_info
270 628         2990 $dst_line_info->[$_] += $src_line_info->[$_] for (
271             NYTP_SCi_CALL_COUNT, NYTP_SCi_INCL_RTIME, NYTP_SCi_EXCL_RTIME,
272             );
273 628         2192 $dst_line_info->[NYTP_SCi_REC_DEPTH] = max($dst_line_info->[NYTP_SCi_REC_DEPTH],
274             $src_line_info->[NYTP_SCi_REC_DEPTH]);
275             # ug, we can't really combine recursive incl_time, but this is better than undef
276 628         1616 $dst_line_info->[NYTP_SCi_RECI_RTIME] = max($dst_line_info->[NYTP_SCi_RECI_RTIME],
277             $src_line_info->[NYTP_SCi_RECI_RTIME]);
278              
279 628   50     2123 my $src_cs = $src_line_info->[NYTP_SCi_CALLING_SUB]|| {};
280 628   100     2046 my $dst_cs = $dst_line_info->[NYTP_SCi_CALLING_SUB]||={};
281 628         2395 $dst_cs->{$_} = $src_cs->{$_} for keys %$src_cs;
282              
283 628 50       2300 warn sprintf " = %s\n", _fmt_sc($dst_line_info)
284             if trace_level() >= 5;
285              
286 628         3305 return;
287             }
288              
289             sub _fmt_sc {
290 0     0   0 my ($sc) = @_;
291 0 0       0 return "(empty)" if !@$sc;
292 0   0     0 my $dst_cs = $sc->[NYTP_SCi_CALLING_SUB]||{};
293 0         0 my $by = join " & ", sort keys %$dst_cs;
294 0 0       0 sprintf "calls %d%s",
295             $sc->[NYTP_SCi_CALL_COUNT], ($by) ? ", by $by" : "";
296             }
297              
298              
299             sub caller_fids {
300 128     128 0 414 my ($self, $merge_evals) = @_;
301 128   50     877 my $callers = $self->caller_fid_line_places($merge_evals) || {};
302 128         596 my @fids = keys %$callers;
303 128         746 return @fids; # count in scalar context
304             }
305              
306 1     1 0 10 sub caller_count { return scalar shift->caller_places; } # XXX deprecate later
307              
308             # array of [ $fid, $line, $sub_call_info ], ...
309             sub caller_places {
310 2254     2254 0 5295 my ($self, $merge_evals) = @_;
311 2254   50     8401 my $callers = $self->caller_fid_line_places || {};
312              
313 2254         3975 my @callers;
314 2254         10509 for my $fid (sort { $a <=> $b } keys %$callers) {
  201         794  
315 1416         3287 my $lines_hash = $callers->{$fid};
316 1416         6507 for my $line (sort { $a <=> $b } keys %$lines_hash) {
  1188         4949  
317 2210         7501 push @callers, [ $fid, $line, $lines_hash->{$line} ];
318             }
319             }
320              
321 2254         8021 return @callers; # scalar: number of distinct calling locations
322             }
323              
324             sub normalize_for_test {
325 2450     2450 0 5694 my $self = shift;
326 2450         8300 my $profile = $self->profile;
327              
328             # normalize eval sequence numbers in anon sub names to 0
329             $self->[NYTP_SIi_SUB_NAME] =~ s/ \( ((?:re_)?) eval \s \d+ \) /(${1}eval 0)/xg
330             if $self->[NYTP_SIi_SUB_NAME] =~ m/__ANON__/
331 2450 100 100     13260 && not $ENV{NYTPROF_TEST_SKIP_EVAL_NORM};
332              
333             # zero subroutine inclusive time
334 2450         6228 $self->[NYTP_SIi_INCL_RTIME] = 0;
335 2450         5542 $self->[NYTP_SIi_EXCL_RTIME] = 0;
336 2450         5245 $self->[NYTP_SIi_RECI_RTIME] = 0;
337              
338             # { fid => { line => [ count, incl, excl, ... ] } }
339 2450   100     13615 my $callers = $self->[NYTP_SIi_CALLED_BY] || {};
340              
341             # calls from modules shipped with perl cause problems for tests
342             # because the line numbers vary between perl versions, so here we
343             # edit the line number of calls from these modules
344 2450         11570 for my $fid (keys %$callers) {
345 2015 100       6586 next if not $fid;
346 1548 50       6395 my $fileinfo = $profile->fileinfo_of($fid) or next;
347 1548 100       4793 next if $fileinfo->filename !~ /(AutoLoader|Exporter)\.pm$/;
348              
349             # normalize the lines X,Y,Z to 1,2,3
350 144         321 my %lines = %{ delete $callers->{$fid} };
  144         687  
351 144         856 my @lines = @lines{sort { $a <=> $b } keys %lines};
  32         343  
352 144         411 $callers->{$fid} = { map { $_ => shift @lines } 1..@lines };
  176         1022  
353             }
354              
355 2450         8152 for my $sc (map { values %$_ } values %$callers) {
  2015         8580  
356             # zero per-call-location subroutine inclusive time
357 2395         7938 $sc->[NYTP_SCi_INCL_RTIME] =
358             $sc->[NYTP_SCi_EXCL_RTIME] =
359             $sc->[NYTP_SCi_RECI_RTIME] = 0;
360              
361 2395 100       7050 if (not $ENV{NYTPROF_TEST_SKIP_EVAL_NORM}) {
362             # normalize eval sequence numbers in anon sub names to 0
363 2235   50     6252 my $names = $sc->[NYTP_SCi_CALLING_SUB]||{};
364 2235         7278 for my $subname (keys %$names) {
365 2235         6429 (my $newname = $subname) =~ s/ \( ((?:re_)?) eval \s \d+ \) /(${1}eval 0)/xg;
366 2235 100       7991 next if $newname eq $subname;
367             warn "Normalizing $subname to $newname overwrote other calling-sub data\n"
368 16 50       103 if $names->{$newname};
369 16         107 $names->{$newname} = delete $names->{$subname};
370             }
371             }
372              
373             }
374 2450         9453 return $self->[NYTP_SIi_SUB_NAME];
375             }
376              
377             sub dump {
378 2252     2252 0 6783 my ($self, $separator, $fh, $path, $prefix) = @_;
379              
380 2252         4044 my ($fid, $l1, $l2, $calls) = @{$self}[
  2252         7174  
381             NYTP_SIi_FID, NYTP_SIi_FIRST_LINE, NYTP_SIi_LAST_LINE, NYTP_SIi_CALL_COUNT
382             ];
383 2252         4656 my @values = @{$self}[
  2252         5837  
384             NYTP_SIi_INCL_RTIME, NYTP_SIi_EXCL_RTIME,
385             NYTP_SIi_REC_DEPTH, NYTP_SIi_RECI_RTIME
386             ];
387             printf $fh "%s[ %s:%s-%s calls %s times %s ]\n",
388             $prefix,
389 9008 100       18947 map({ defined($_) ? $_ : 'undef' } $fid, $l1, $l2, $calls),
390 2252 50       5459 join(" ", map { defined($_) ? $_ : 'undef' } @values);
  9008         28284  
391              
392 2252         9662 my @caller_places = $self->caller_places;
393 2252         6566 for my $cp (@caller_places) {
394 2210         5672 my ($fid, $line, $sc) = @$cp;
395 2210         5184 my @sc = @$sc;
396 2210         3362 $sc[NYTP_SCi_CALLING_SUB] = join "|", sort keys %{ $sc[NYTP_SCi_CALLING_SUB] };
  2210         6681  
397             printf $fh "%s%s%s%d:%d%s[ %s ]\n",
398             $prefix,
399             'called_by', $separator,
400             $fid, $line, $separator,
401 2210 50       5488 join(" ", map { defined($_) ? $_ : 'undef' } @sc);
  17680         45575  
402             }
403              
404             # where a sub has had others merged into it, list them
405 2252   100     7305 my $merge_subs = $self->meta->{merged_sub_names} || [];
406 2252         13223 for my $ms (sort @$merge_subs) {
407 80         556 printf $fh "%s%s%s%s\n",
408             $prefix, 'merge_donor', $separator, $ms;
409             }
410             }
411              
412             # vim:ts=8:sw=4:et
413             1;