File Coverage

blib/lib/Devel/NYTProf/Data.pm
Criterion Covered Total %
statement 287 299 95.9
branch 126 150 84.0
condition 66 82 80.4
subroutine 38 38 100.0
pod 6 20 30.0
total 523 589 88.7


line stmt bran cond sub pod time code
1             # vim: ts=8 sw=4 expandtab:
2             ##########################################################
3             # This script is part of the Devel::NYTProf distribution
4             #
5             # Copyright, contact and other information can be found
6             # at the bottom of this file, or by going to:
7             # http://metacpan.org/release/Devel-NYTProf/
8             #
9             ###########################################################
10             package Devel::NYTProf::Data;
11              
12             =head1 NAME
13              
14             Devel::NYTProf::Data - L data loading and manipulation
15              
16             =head1 SYNOPSIS
17              
18             use Devel::NYTProf::Data;
19              
20             $profile = Devel::NYTProf::Data->new( { filename => 'nytprof.out' } );
21              
22             $profile->dump_profile_data();
23              
24             =head1 DESCRIPTION
25              
26             Reads a profile data file written by L, aggregates the
27             contents, and returns the results as a blessed data structure.
28              
29             Access to the data should be via methods in this class to avoid breaking
30             encapsulation (and thus breaking your code when the data structures change in
31             future versions).
32              
33             B the documentation is out of date and may not be updated soon.
34             It's also likely that the API will change drastically in future.
35             It's possible, for example, that the data model will switch to use SQLite
36             and the http://metacpan.org/pod/ORLite ORM.
37              
38             Let me know if you come to depend on a particular API and I'll try to preserve
39             it if practical.
40              
41             =head1 METHODS
42              
43             =cut
44              
45              
46 48     48   4987376 use warnings;
  48         598  
  48         1623  
47 48     48   303 use strict;
  48         122  
  48         1200  
48              
49 48     48   234 use Carp qw(carp croak cluck);
  48         109  
  48         3026  
50 48     48   393 use Cwd qw(getcwd);
  48         118  
  48         2087  
51 48     48   275 use Scalar::Util qw(blessed);
  48         146  
  48         2686  
52              
53 48     48   20500 use Devel::NYTProf::Core;
  48         135  
  48         1819  
54 48     48   22919 use Devel::NYTProf::FileInfo;
  48         143  
  48         1498  
55 48     48   21267 use Devel::NYTProf::SubInfo;
  48         156  
  48         1720  
56 48     48   354 use Devel::NYTProf::Util qw( trace_level _dumper );
  48         133  
  48         122510  
57              
58             our $VERSION = '6.13';
59              
60              
61             =head2 new
62              
63             $profile = Devel::NYTProf::Data->new( );
64              
65             $profile = Devel::NYTProf::Data->new( {
66             filename => 'nytprof.out', # default
67             quiet => 0, # default, 1 to silence message
68             } );
69              
70             Reads the specified file containing profile data written by L,
71             aggregates the contents, and returns the results as a blessed data structure.
72              
73             =cut
74              
75              
76             sub new {
77 676     676 1 60170615 my $class = shift;
78 676   100     8288 my $args = shift || { };
79              
80 676   100     6641 my $file = $args->{filename} ||= 'nytprof.out';
81 676 100       18223 croak "Devel::NYTProf::new() could not locate file for processing"
82             unless -f $file;
83              
84 675 100       74780 print "Reading $file\n" unless $args->{quiet};
85              
86             my $profile = load_profile_data_from_file(
87             $file,
88             $args->{callback},
89 675         928167 );
90              
91 675 100       11654 return undef if $args->{callback};
92              
93 674 100       63957 print "Processing $file data\n" unless $args->{quiet};
94              
95 674         8851 bless $profile => $class;
96              
97 674         4541 my $fid_fileinfo = $profile->{fid_fileinfo};
98 674         2461 my $sub_subinfo = $profile->{sub_subinfo};
99              
100             # add profile ref so fidinfo & subinfo objects
101             # XXX circular ref, add weaken
102 674 100       5431 for (@$fid_fileinfo) { $_ and $_->[7] = $profile; }
  2424         12109  
103 674         7661 $_->[7] = $profile for values %$sub_subinfo;
104              
105             # bless sub_subinfo data
106 674         19926 (my $sub_class = $class) =~ s/\w+$/SubInfo/;
107 674   50     22619 $_ and bless $_ => $sub_class for values %$sub_subinfo;
108              
109             # create profiler_active attribute by subtracting from profiler_duration
110             # currently we only subtract cumulative_overhead_ticks
111 674         3979 my $attribute = $profile->{attribute};
112 674         8382 my $overhead_time = $attribute->{cumulative_overhead_ticks} / $attribute->{ticks_per_sec};
113 674         4014 $attribute->{profiler_active} = $attribute->{profiler_duration} - $overhead_time;
114              
115             # find subs that have calls but no fid
116 674 100       4161 my @homeless_subs = grep { $_->calls and not $_->fid } values %$sub_subinfo;
  6164         26686  
117 674 100       3956 if (@homeless_subs) { # give them a home...
118             # currently just the first existing fileinfo
119             # XXX ought to create a new dummy fileinfo for them
120 112         1344 my $new_fi = $profile->fileinfo_of(1);
121 112         1796 $_->_alter_fileinfo(undef, $new_fi) for @homeless_subs;
122             }
123              
124              
125             # Where a given eval() has been invoked more than once
126             # rollup the corresponding fids if they're "uninteresting".
127 674 100       3461 if (not $args->{skip_collapse_evals}) {
128 673         8731 for my $fi ($profile->noneval_fileinfos) {
129 925         8177 $profile->collapse_evals_in($fi);
130             }
131             }
132              
133 674         5740 $profile->_clear_caches;
134              
135             # a hack for testing/debugging
136             # $ENV{NYTPROF_ONLOAD} must be a colon-delimited string of
137             # equal-sign-delimited substrings, e.g.,
138             # 'alpha=beta:gamma=delta:dump=1:exit=1';
139              
140 674 100       3709 if (my $env = $ENV{NYTPROF_ONLOAD}) {
141 1         6 my %onload = map { split /=/, $_, 2 } split /:/, $env, -1;
  3         12  
142 1 50       10 warn _dumper($profile) if $onload{dump};
143 1 50       1591 exit $onload{exit} if defined $onload{exit};
144             }
145              
146 674         4137 return $profile;
147             }
148              
149              
150             sub collapse_evals_in {
151 1749     1749 0 6116 my ($profile, $parent_fi) = @_;
152 1749         5554 my $parent_fid = $parent_fi->fid;
153              
154 1749         3815 my %evals_on_line;
155 1749         9605 for my $fi ($parent_fi->has_evals) {
156 824         4923 $profile->collapse_evals_in($fi); # recurse first
157 824         1487 push @{ $evals_on_line{$fi->eval_line} }, $fi;
  824         2466  
158             }
159              
160 1749         11291 while ( my ($line, $siblings) = each %evals_on_line) {
161              
162 552 100       3216 next if @$siblings == 1;
163              
164             # compare src code of evals and collapse identical ones
165 176         539 my %src_keyed;
166 176         738 for my $fi (@$siblings) {
167 448         2501 my $key = $fi->src_digest;
168 448 100       1453 if (!$key) { # include extra info to segregate when there's no src
169 164 100       624 $key .= ',evals' if $fi->has_evals;
170 164 100       814 $key .= ',subs' if $fi->subs_defined;
171             }
172 448         822 push @{$src_keyed{$key}}, $fi;
  448         2360  
173             }
174              
175 176 50       1382 if (trace_level() >= 2) {
176 0         0 my @subs = map { $_->subs_defined } @$siblings;
  0         0  
177 0         0 my @evals = map { $_->has_evals(0) } @$siblings;
  0         0  
178             warn sprintf "%d:%d: has %d sibling evals (subs %d, evals %d, keys %d) in %s; fids: %s\n",
179             $parent_fid, $line, scalar @$siblings, scalar @subs, scalar @evals,
180             scalar keys %src_keyed,
181             $parent_fi->filename,
182 0         0 join(" ", map { $_->fid } @$siblings);
  0         0  
183              
184 0         0 for my $si (@subs) {
185 0         0 warn sprintf "%d:%d evals: define sub %s in fid %s\n",
186             $parent_fid, $line, $si->subname, $si->fid;
187             }
188 0         0 for my $fi (@evals) {
189 0         0 warn sprintf "%d:%d evals: execute eval %s\n",
190             $parent_fid, $line, $fi->filename;
191             }
192              
193             }
194              
195             # if 'too many' distinct eval source keys then simply collapse all
196 176   50     1802 my $max_evals_siblings = $ENV{NYTPROF_MAX_EVAL_SIBLINGS} || 200;
197 176 50       798 if (values %src_keyed > $max_evals_siblings) {
198 0         0 $parent_fi->collapse_sibling_evals(@$siblings);
199             }
200             else {
201             # finesse: consider each distinct src in turn
202              
203 176         1068 while ( my ($key, $src_same_fis) = each %src_keyed ) {
204 208 50       674 next if @$src_same_fis == 1; # unique src key
205 208         541 my @fids = map { $_->fid } @$src_same_fis;
  448         1236  
206              
207 208 100       662 if (grep { $_->has_evals(0) } @$src_same_fis) {
  448         1196  
208 16 50       175 warn "evals($key): collapsing skipped due to evals in @fids\n" if trace_level() >= 3;
209             }
210             else {
211 192 50       986 warn "evals($key): collapsing identical: @fids\n" if trace_level() >= 3;
212 192         1491 my $fi = $parent_fi->collapse_sibling_evals(@$src_same_fis);
213 192         1421 @$src_same_fis = ( $fi ); # update list in-place
214             }
215             }
216             }
217             }
218 1749         5728 return 1;
219             }
220              
221 7735   100 7735   31465 sub _caches { return shift->{caches} ||= {} }
222 1124     1124   5299 sub _clear_caches { return delete shift->{caches} }
223              
224             sub attributes {
225 644   50 644 0 5012 return shift->{attribute} || {};
226             }
227              
228             sub options {
229 450   50 450 0 6105 return shift->{option} || {};
230             }
231              
232             sub subname_subinfo_map {
233 128     128 0 67560 return { %{ shift->{sub_subinfo} } }; # shallow copy
  128         1789  
234             }
235              
236             sub _disconnect_subinfo {
237 128     128   368 my ($self, $si) = @_;
238 128         317 my $subname = $si->subname;
239 128         367 my $si2 = delete $self->{sub_subinfo}{$subname};
240             # sanity check
241 128 0 33     1066 carp sprintf "disconnect_subinfo: deleted entry %s %s doesn't match argument %s %s",
    50          
242             ($si2) ? ($si2, $si2->subname) : ('undef', 'undef'),
243             $si, $subname
244             if $si2 != $si or $si2->subname ne $subname;
245             # do more?
246             }
247              
248              
249             # package_tree_subinfo_map is like package_subinfo_map but returns
250             # nested data instead of flattened.
251             # for "Foo::Bar::Baz" package:
252             # { Foo => { '' => [...], '::Bar' => { ''=>[...], '::Baz'=>[...] } } }
253             # if merged is true then array contains a single 'merged' subinfo
254             sub package_subinfo_map {
255 6     6 0 10743 my $self = shift;
256 6         16 my ($merge_subs, $nested_pkgs) = @_;
257              
258 6         11 my %pkg;
259             my %to_merge;
260              
261 6         28 my $all_subs = $self->subname_subinfo_map;
262 6         31 while ( my ($name, $subinfo) = each %$all_subs ) {
263 36         155 $name =~ s/^(.*::).*/$1/; # XXX $subinfo->package
264 36         63 my $subinfos;
265 36 100       59 if ($nested_pkgs) {
266 24         49 my @parts = split /::/, $name;
267 24   100     84 my $node = $pkg{ shift @parts } ||= {};
268             # TODO: Need to figure out how to provide a multi-part name, e.g., 'alpha::beta'
269             # Otherwise @parts is now empty and so next line is not exercised
270             # during testing.
271 24   0     56 $node = $node->{ shift @parts } ||= {} while @parts;
272 24   100     68 $subinfos = $node->{''} ||= [];
273             }
274             else {
275 12   100     31 $subinfos = $pkg{$name} ||= [];
276             }
277 36         56 push @$subinfos, $subinfo;
278 36 100       128 $to_merge{$subinfos} = $subinfos if $merge_subs;
279             }
280              
281 6         18 for my $subinfos (values %to_merge) {
282 2         8 my $subinfo = shift(@$subinfos)->clone;
283             $subinfo->merge_in($_, src_keep => 1)
284 2         9 for @$subinfos;
285             # replace the many with the one
286 2         6 @$subinfos = ($subinfo);
287             }
288              
289 6         32 return \%pkg;
290             }
291              
292             sub all_fileinfos {
293 2323     2323 0 114758 my @all = @{shift->{fid_fileinfo}};
  2323         8981  
294 2323         5203 shift @all; # drop fid 0
295             # return all non-nullified fileinfos
296 2323         7604 return grep { $_->fid } @all;
  6263         24871  
297             }
298              
299             sub eval_fileinfos {
300 1     1 0 502 return grep { $_->eval_line } shift->all_fileinfos;
  1         4  
301             }
302              
303             sub noneval_fileinfos {
304 675     675 0 6329 return grep { !$_->eval_line } shift->all_fileinfos;
  1751         6644  
305             }
306              
307              
308             sub fileinfo_of {
309 7702     7702 0 124806 my ($self, $arg, $silent_if_undef) = @_;
310              
311 7702 100       17740 if (not defined $arg) {
312 2 100       184 carp "Can't resolve fid of undef value" unless $silent_if_undef;
313 2         87 return undef;
314             }
315              
316             # check if already a file info object
317 7700 100 100     18645 return $arg if ref $arg and UNIVERSAL::can($arg,'fid') and $arg->isa('Devel::NYTProf::FileInfo');
      100        
318              
319 7696         22870 my $fid = $self->resolve_fid($arg);
320 7696 100       19318 if (not $fid) {
321 4         328 carp "Can't resolve fid of '$arg'";
322 4         351 return undef;
323             }
324              
325 7692         14931 my $fi = $self->{fid_fileinfo}[$fid];
326 7692 100       21557 return undef unless defined $fi->fid; # nullified?
327 7212         18742 return $fi;
328             }
329              
330              
331             sub subinfo_of {
332 258     258 0 4408 my ($self, $subname) = @_;
333              
334 258 100       955 if (not defined $subname) {
335 1         198 cluck "Can't resolve subinfo of undef value";
336 1         226 return undef;
337             }
338              
339 257 100       1339 my $si = $self->{sub_subinfo}{$subname}
340             or cluck "Can't resolve subinfo of '$subname'";
341              
342 257         1969 return $si;
343             }
344              
345              
346             sub inc {
347              
348             # XXX should return inc from profile data, when it's there
349 1329     1329 0 40744 return @INC;
350             }
351              
352             =head2 dump_profile_data
353              
354             $profile->dump_profile_data;
355             $profile->dump_profile_data( {
356             filehandle => \*STDOUT,
357             separator => "",
358             } );
359              
360             Writes the profile data in a reasonably human friendly format to the specified
361             C (default STDOUT).
362              
363             For non-trivial profiles the output can be very large. As a guide, there'll be
364             at least one line of output for each line of code executed, plus one for each
365             place a subroutine was called from, plus one per subroutine.
366              
367             The default format is a Data::Dumper style whitespace-indented tree.
368             The types of data present can depend on the options used when profiling.
369              
370             If C is true then instead of whitespace, each item of data is
371             indented with the I through the structure with C used to
372             separate the elements of the path.
373             This format is especially useful for grep'ing and diff'ing.
374              
375             =cut
376              
377              
378             sub dump_profile_data {
379 450     450 1 108015 my $self = shift;
380 450   100     2795 my $args = shift || {};
381 450   100     2791 my $separator = $args->{separator} || '';
382 450   100     2437 my $filehandle = $args->{filehandle} || \*STDOUT;
383              
384             # shallow clone and add sub_caller for migration of tests
385 450         1476 my $startnode = $self;
386              
387 450         1954 $self->_clear_caches;
388              
389             my $callback = sub {
390 28092     28092   54571 my ($path, $value) = @_;
391              
392             # not needed currently
393             #if ($path->[0] eq 'attribute' && @$path == 1) { my %v = %$value; return ({}, \%v); }
394              
395 28092 100       62960 if (my $hook = $args->{skip_fileinfo_hook}) {
396              
397             # for fid_fileinfo elements...
398 27936 100 100     68558 if ($path->[0] eq 'fid_fileinfo' && @$path==2) {
399 1120         2497 my $fi = $value;
400              
401             # skip nullified fileinfo
402 1120 100       3647 return undef unless $fi->fid;
403              
404             # don't dump internal details of lib modules
405 960         4032 return ({ skip_internal_details => scalar $hook->($fi, $path, $value) }, $value);
406             }
407              
408             # skip sub_subinfo data for 'library modules'
409 26816 50 100     71154 if ($path->[0] eq 'sub_subinfo' && @$path==2 && $value->[0]) {
      66        
410 2432         6354 my $fi = $self->fileinfo_of($value->[0]);
411 2432 100 66     9819 return undef if !$fi or $hook->($fi, $path, $value);
412             }
413              
414             # skip fid_*_time data for 'library modules'
415 26624 100 100     123280 if ($path->[0] =~ /^fid_\w+_time$/ && @$path==2) {
416 3264         9883 my $fi = $self->fileinfo_of($path->[1]);
417 3264 100 100     14704 return undef if !$fi or $hook->($fi, $path, $value);
418             }
419             }
420 26252         79149 return ({}, $value);
421 450         13469 };
422              
423 450         4688 _dump_elements($startnode, $separator, $filehandle, [], $callback);
424             }
425              
426              
427             sub _dump_elements {
428 6792     6792   18194 my ($r, $separator, $fh, $path, $callback) = @_;
429 6792         12641 my $pad = " ";
430 6792         10510 my $padN;
431              
432 6792         19791 my $is_hash = (UNIVERSAL::isa($r, 'HASH'));
433 6792 100       51102 my ($start, $end, $colon, $keys) =
434             ($is_hash)
435             ? ('{', '}', ' => ', [sort keys %$r])
436             : ('[', ']', ': ', [0 .. @$r - 1]);
437              
438 6792 100       18446 if ($separator) {
439 6780         14513 ($start, $end, $colon) = (undef, undef, $separator);
440 6780         19782 $padN = join $separator, @$path, '';
441             }
442             else {
443 12         40 $padN = $pad x (@$path + 1);
444             }
445              
446 6792         24641 my $format = {sub_subinfo => {compact => 1},};
447              
448 6792 100       16487 print $fh "$start\n" if $start;
449 6792   66     21437 my $key1 = $path->[0] || $keys->[0];
450 6792         17388 for my $key (@$keys) {
451              
452 49806 100       112053 next if $key eq 'fid_srclines';
453              
454 49356 100       98234 my $value = ($is_hash) ? $r->{$key} : $r->[$key];
455              
456             # skip undef elements in array
457 49356 100 100     138729 next if !$is_hash && !defined($value);
458             # skip refs to empty arrays in array
459 28092 50 100     91514 next if !$is_hash && ref $value eq 'ARRAY' && !@$value;
      66        
460              
461 28092         47861 my $dump_opts = {};
462 28092 50       58553 if ($callback) {
463 28092         83706 ($dump_opts, $value) = $callback->([ @$path, $key ], $value);
464 28092 100       89798 next if not $dump_opts;
465             }
466              
467 27212         65998 my $prefix = "$padN$key$colon";
468              
469 27212 100       92191 if (UNIVERSAL::can($value,'dump')) {
470 3214         17667 $value->dump($separator, $fh, [ @$path, $key ], $prefix, $dump_opts);
471             }
472             else {
473              
474             # special case some common cases to be more compact:
475             # fid_*_time [fid][line] = [N,N]
476             # sub_subinfo {subname} = [fid,startline,endline,calls,incl_time]
477 23998         48082 my $as_compact = $format->{$key1}{compact};
478 23998 50       49643 if (not defined $as_compact) { # so guess...
479             $as_compact =
480 23998   100     99931 (UNIVERSAL::isa($value, 'ARRAY') && @$value <= 9 && !grep { ref or !defined }
481             @$value);
482             }
483 23998 100       58726 $as_compact = 0 if not ref $value eq 'ARRAY';
484              
485 23998 100       52138 if ($as_compact) {
    100          
486 48     48   418 no warnings qw(uninitialized);
  48         132  
  48         6352  
487 9066 50       18144 printf $fh "%s[ %s ]\n", $prefix, join(" ", map { defined($_) ? $_ : 'undef' } @$value);
  18132         75474  
488             }
489             elsif (ref $value) {
490 6342         23815 _dump_elements($value, $separator, $fh, [ @$path, $key ], $callback);
491             }
492             else {
493 8590         34232 print $fh "$prefix$value\n";
494             }
495             }
496             }
497 48     48   383 no warnings 'numeric'; # @$path can be non-positive
  48         107  
  48         61014  
498 6792 100       15899 printf $fh "%s$end\n", ($pad x (@$path - 1)) if $end;
499 6792         30723 return 1;
500             }
501              
502              
503             sub get_profile_levels {
504 2     2 0 12 return shift->{profile_modes};
505             }
506              
507             sub get_fid_line_data {
508 1825     1825 0 14469 my ($self, $level) = @_;
509 1825   100     5802 $level ||= 'line';
510 1825         6517 my $fid_line_data = $self->{"fid_${level}_time"};
511 1825         6648 return $fid_line_data;
512             }
513              
514              
515             =head2 normalize_variables
516              
517             $profile->normalize_variables;
518              
519             Traverses the profile data structure and normalizes highly variable data, such
520             as the time, in order that the data can more easily be compared. This is mainly of
521             use to the test suite.
522              
523             The data normalized is:
524              
525             =over
526              
527             =item *
528              
529             profile timing data: set to 0
530              
531             =item *
532              
533             subroutines: timings are set to 0
534              
535             =item *
536              
537             attributes, like basetime, xs_version, etc., are set to 0
538              
539             =item *
540              
541             filenames: path prefixes matching absolute paths in @INC are changed to "/.../"
542              
543             =item *
544              
545             filenames: eval sequence numbers, like "(re_eval 2)" are changed to 0
546              
547             =back
548              
549             =cut
550              
551              
552             sub normalize_variables {
553 451     451 1 19784 my ($self, $normalize_options) = @_;
554              
555 451 100       2835 if ($normalize_options) {
556 449         1296 %{ $self->options } = ();
  449         2962  
557             }
558              
559 451         3039 my $attributes = $self->attributes;
560              
561 451         4816 for my $attr (qw(
562             basetime xs_version perl_version clock_id ticks_per_sec nv_size
563             profiler_duration profiler_end_time profiler_start_time
564             cumulative_overhead_ticks profiler_active
565             total_stmts_duration total_stmts_measured total_stmts_discounted
566             total_sub_calls sawampersand_line
567             )) {
568 7216 100       24137 $attributes->{$attr} = 0 if exists $attributes->{$attr};
569             }
570              
571 451         2483 for my $attr (qw(PL_perldb cumulative_overhead_ticks)) {
572 902         3684 delete $attributes->{$attr};
573             }
574              
575             # normalize line data
576 451         3280 for my $level (qw(line block sub)) {
577 1353   50     5621 my $fid_line_data = $self->get_fid_line_data($level) || [];
578              
579             # zero the statement timing data
580 1353         5155 for my $of_fid (@$fid_line_data) {
581 4626 100       15443 _zero_array_elem($of_fid, 0) if $of_fid;
582             }
583             }
584              
585 451         1968 my $sub_subinfo = $self->{sub_subinfo};
586 451         2551 for my $subname (keys %$sub_subinfo) {
587 2450         5671 my $si = $self->{sub_subinfo}{$subname};
588             # zero sub info and sub caller times etc.
589 2450         11979 my $newname = $si->normalize_for_test;
590 2450 100       9049 if ($newname ne $subname) {
591             warn "Normalizing $subname to $newname overwrote other data\n"
592 32 50       344 if $sub_subinfo->{$newname};
593 32         204 $sub_subinfo->{$newname} = delete $sub_subinfo->{$subname};
594             }
595             }
596              
597 451         2959 $_->normalize_for_test for $self->all_fileinfos;
598              
599 451         2358 return 1;
600             }
601              
602              
603             sub _zero_array_elem {
604 3273     3273   7670 my ($ary_of_line_data, $index) = @_;
605 3273         9499 for my $line_data (@$ary_of_line_data) {
606 34491 100       85434 next unless $line_data;
607 10287         20643 $line_data->[$index] = 0;
608              
609             # if line was a string eval
610             # then recurse to zero the times within the eval lines
611 10287 50       27965 if (my $eval_lines = $line_data->[2]) {
612 0         0 _zero_array_elem($eval_lines, $index); # recurse
613             }
614             }
615             }
616              
617             sub _filename_to_fid {
618 7697     7697   13263 my $self = shift;
619 7697         17182 my $caches = $self->_caches;
620 7697   66     28466 return $caches->{_filename_to_fid_cache} ||= do {
621 1126         3688 my $filename_to_fid = {};
622 1126         4476 $filename_to_fid->{$_->filename} = $_->fid for $self->all_fileinfos;
623 1126         5704 $filename_to_fid;
624             };
625             }
626              
627              
628             =head2 subs_defined_in_file
629              
630             $subs_defined_hash = $profile->subs_defined_in_file( $file, $include_lines );
631              
632             Returns a reference to a hash containing information about subroutines defined
633             in a source file. The $file argument can be an integer file id (fid) or a file
634             path.
635              
636             Returns undef if the profile contains no C data for the $file.
637              
638             The keys of the returned hash are fully qualified subroutine names and the
639             corresponding value is a hash reference containing L
640             objects.
641              
642             If $include_lines is true then the hash also contains integer keys
643             corresponding to the first line of the subroutine. The corresponding value is a
644             reference to an array. The array contains a hash ref for each of the
645             subroutines defined on that line, typically just one.
646              
647             =cut
648              
649             sub subs_defined_in_file {
650 38     38 1 15543 my ($self, $fid, $incl_lines) = @_;
651 38 50       148 croak "incl_lines is deprecated in subs_defined_in_file, use subs_defined_in_file_by_line instead" if $incl_lines;
652              
653 38 50       133 my $fi = $self->fileinfo_of($fid)
654             or return;
655              
656 38         114 $fid = $fi->fid;
657 38         119 my $caches = $self->_caches;
658              
659 38         261 my $cache_key = "subs_defined_in_file:$fid";
660 38 100       176 return $caches->{$cache_key} if $caches->{$cache_key};
661              
662 33         323 my %subs = map { $_->subname => $_ } $fi->subs_defined;
  150         501  
663              
664 33         154 $caches->{$cache_key} = \%subs;
665 33         317 return $caches->{$cache_key};
666             }
667              
668              
669             sub subs_defined_in_file_by_line {
670 19     19 0 88 my $subs = shift->subs_defined_in_file(@_);
671 19         107 my %line2subs;
672 19         88 for (values %$subs) {
673 82   100     205 my $first_line = $_->first_line || 0; # 0 = xsub?
674 82         141 push @{$line2subs{$first_line}}, $_;
  82         233  
675             }
676 19         216 return \%line2subs;
677             }
678              
679              
680             =head2 file_line_range_of_sub
681              
682             ($file, $fid, $first, $last, $fi) = $profile->file_line_range_of_sub("main::foo");
683              
684             Returns the filename, fid, and first and last line numbers, and fileinfo object
685             for the specified subroutine (which must be fully qualified with a package name).
686              
687             Returns an empty list if the subroutine name is not in the profile data.
688              
689             The $fid return is the 'original' fid associated with the file the subroutine was created in.
690              
691             The $file returned is the source file that defined the subroutine.
692              
693             Subroutines that are implemented in XS have a line range of 0,0 and a possibly
694             unknown file (if NYTProf couldn't find a good match based on the package name).
695              
696             Subroutines that were called but only returned via an exception may have a line
697             range of undef,undef if they're xsubs or were defined before NYTProf was enabled.
698              
699             =cut
700              
701              
702             sub file_line_range_of_sub {
703 74     74 1 37499 my ($self, $sub) = @_;
704              
705 74 100       358 my $sub_subinfo = $self->subinfo_of($sub)
706             or return; # no such sub; warning supplied by subinfo_of()
707 73         314 my ($fid, $first, $last) = @$sub_subinfo;
708              
709 73 50       296 return if not $fid; # sub has no known file
710              
711 73 50 33     614 my $fileinfo = $fid && $self->fileinfo_of($fid)
712             or croak "No fid_fileinfo for sub $sub fid '$fid'";
713              
714 73         331 return ($fileinfo->filename, $fid, $first, $last, $fileinfo);
715             }
716              
717              
718             =head2 resolve_fid
719              
720             $fid = $profile->resolve_fid( $file );
721              
722             Returns the integer I that corresponds to $file.
723              
724             If $file can't be found and $file looks like a positive integer then it's
725             presumed to already be a fid and is returned. This is used to enable other
726             methods to work with fid or file arguments.
727              
728             If $file can't be found but it uniquely matches the suffix of one of the files
729             then that corresponding fid is returned.
730              
731             =cut
732              
733              
734             sub resolve_fid {
735 7698     7698 1 17683 my ($self, $file) = @_;
736 7698 100       16270 Carp::confess("No file specified") unless defined $file;
737 7697         17635 my $resolve_fid_cache = $self->_filename_to_fid;
738              
739             # exact match
740             return $resolve_fid_cache->{$file}
741 7697 100       19951 if exists $resolve_fid_cache->{$file};
742              
743             # looks like a fid already
744 7643 100       43803 return $file
745             if $file =~ m/^\d+$/;
746              
747             # XXX hack needed to because of how _map_new_to_old deals
748             # with .pmc files because of how ::Reporter works
749 5 50       19 return $self->resolve_fid($file) if $file =~ s/\.pmc$/.pm/;
750              
751             # unfound absolute path, so we're sure we won't find it
752             return undef # XXX carp?
753 5 100       25 if $file =~ m/^\//;
754              
755             # prepend '/' and grep for trailing matches - if just one then use that
756 4         76 my $match = qr{/\Q$file\E$};
757 4         13 my @matches = grep {m/$match/} keys %$resolve_fid_cache;
  4         20  
758             # XXX: Not clear how to exercise either of the following conditions
759 4 50       13 return $self->resolve_fid($matches[0])
760             if @matches == 1;
761 4 50       10 carp "Can't resolve '$file' to a unique file id (matches @matches)"
762             if @matches >= 2;
763              
764 4         12 return undef;
765             }
766              
767             1;
768              
769             __END__