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   5029782 use warnings;
  48         585  
  48         1634  
47 48     48   300 use strict;
  48         102  
  48         1223  
48              
49 48     48   243 use Carp qw(carp croak cluck);
  48         101  
  48         3170  
50 48     48   339 use Cwd qw(getcwd);
  48         107  
  48         2223  
51 48     48   282 use Scalar::Util qw(blessed);
  48         93  
  48         2958  
52              
53 48     48   21026 use Devel::NYTProf::Core;
  48         141  
  48         1773  
54 48     48   23386 use Devel::NYTProf::FileInfo;
  48         186  
  48         1567  
55 48     48   22599 use Devel::NYTProf::SubInfo;
  48         157  
  48         1766  
56 48     48   383 use Devel::NYTProf::Util qw( trace_level _dumper );
  48         105  
  48         123389  
57              
58             our $VERSION = '6.13_003';
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 60299764 my $class = shift;
78 676   100     8024 my $args = shift || { };
79              
80 676   100     6939 my $file = $args->{filename} ||= 'nytprof.out';
81 676 100       20064 croak "Devel::NYTProf::new() could not locate file for processing"
82             unless -f $file;
83              
84 675 100       64493 print "Reading $file\n" unless $args->{quiet};
85              
86             my $profile = load_profile_data_from_file(
87             $file,
88             $args->{callback},
89 675         883440 );
90              
91 675 100       11423 return undef if $args->{callback};
92              
93 674 100       57405 print "Processing $file data\n" unless $args->{quiet};
94              
95 674         7976 bless $profile => $class;
96              
97 674         4488 my $fid_fileinfo = $profile->{fid_fileinfo};
98 674         2358 my $sub_subinfo = $profile->{sub_subinfo};
99              
100             # add profile ref so fidinfo & subinfo objects
101             # XXX circular ref, add weaken
102 674 100       5129 for (@$fid_fileinfo) { $_ and $_->[7] = $profile; }
  2424         12100  
103 674         7097 $_->[7] = $profile for values %$sub_subinfo;
104              
105             # bless sub_subinfo data
106 674         19720 (my $sub_class = $class) =~ s/\w+$/SubInfo/;
107 674   50     22235 $_ 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         3432 my $attribute = $profile->{attribute};
112 674         7674 my $overhead_time = $attribute->{cumulative_overhead_ticks} / $attribute->{ticks_per_sec};
113 674         3849 $attribute->{profiler_active} = $attribute->{profiler_duration} - $overhead_time;
114              
115             # find subs that have calls but no fid
116 674 100       4200 my @homeless_subs = grep { $_->calls and not $_->fid } values %$sub_subinfo;
  6164         25265  
117 674 100       4040 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         1052 my $new_fi = $profile->fileinfo_of(1);
121 112         1615 $_->_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       3342 if (not $args->{skip_collapse_evals}) {
128 673         7126 for my $fi ($profile->noneval_fileinfos) {
129 925         8148 $profile->collapse_evals_in($fi);
130             }
131             }
132              
133 674         5368 $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       3921 if (my $env = $ENV{NYTPROF_ONLOAD}) {
141 1         6 my %onload = map { split /=/, $_, 2 } split /:/, $env, -1;
  3         10  
142 1 50       9 warn _dumper($profile) if $onload{dump};
143 1 50       1567 exit $onload{exit} if defined $onload{exit};
144             }
145              
146 674         4205 return $profile;
147             }
148              
149              
150             sub collapse_evals_in {
151 1749     1749 0 6219 my ($profile, $parent_fi) = @_;
152 1749         5604 my $parent_fid = $parent_fi->fid;
153              
154 1749         4047 my %evals_on_line;
155 1749         10071 for my $fi ($parent_fi->has_evals) {
156 824         5464 $profile->collapse_evals_in($fi); # recurse first
157 824         1616 push @{ $evals_on_line{$fi->eval_line} }, $fi;
  824         2859  
158             }
159              
160 1749         10670 while ( my ($line, $siblings) = each %evals_on_line) {
161              
162 552 100       3088 next if @$siblings == 1;
163              
164             # compare src code of evals and collapse identical ones
165 176         423 my %src_keyed;
166 176         547 for my $fi (@$siblings) {
167 448         2574 my $key = $fi->src_digest;
168 448 100       1684 if (!$key) { # include extra info to segregate when there's no src
169 164 100       729 $key .= ',evals' if $fi->has_evals;
170 164 100       888 $key .= ',subs' if $fi->subs_defined;
171             }
172 448         913 push @{$src_keyed{$key}}, $fi;
  448         2451  
173             }
174              
175 176 50       1349 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     1956 my $max_evals_siblings = $ENV{NYTPROF_MAX_EVAL_SIBLINGS} || 200;
197 176 50       895 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         1014 while ( my ($key, $src_same_fis) = each %src_keyed ) {
204 208 50       785 next if @$src_same_fis == 1; # unique src key
205 208         592 my @fids = map { $_->fid } @$src_same_fis;
  448         1300  
206              
207 208 100       623 if (grep { $_->has_evals(0) } @$src_same_fis) {
  448         1186  
208 16 50       301 warn "evals($key): collapsing skipped due to evals in @fids\n" if trace_level() >= 3;
209             }
210             else {
211 192 50       1117 warn "evals($key): collapsing identical: @fids\n" if trace_level() >= 3;
212 192         1470 my $fi = $parent_fi->collapse_sibling_evals(@$src_same_fis);
213 192         1517 @$src_same_fis = ( $fi ); # update list in-place
214             }
215             }
216             }
217             }
218 1749         5847 return 1;
219             }
220              
221 7735   100 7735   31862 sub _caches { return shift->{caches} ||= {} }
222 1124     1124   5017 sub _clear_caches { return delete shift->{caches} }
223              
224             sub attributes {
225 644   50 644 0 5033 return shift->{attribute} || {};
226             }
227              
228             sub options {
229 450   50 450 0 6325 return shift->{option} || {};
230             }
231              
232             sub subname_subinfo_map {
233 128     128 0 59117 return { %{ shift->{sub_subinfo} } }; # shallow copy
  128         1836  
234             }
235              
236             sub _disconnect_subinfo {
237 128     128   454 my ($self, $si) = @_;
238 128         359 my $subname = $si->subname;
239 128         388 my $si2 = delete $self->{sub_subinfo}{$subname};
240             # sanity check
241 128 0 33     1138 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 10593 my $self = shift;
256 6         15 my ($merge_subs, $nested_pkgs) = @_;
257              
258 6         10 my %pkg;
259             my %to_merge;
260              
261 6         17 my $all_subs = $self->subname_subinfo_map;
262 6         32 while ( my ($name, $subinfo) = each %$all_subs ) {
263 36         165 $name =~ s/^(.*::).*/$1/; # XXX $subinfo->package
264 36         57 my $subinfos;
265 36 100       61 if ($nested_pkgs) {
266 24         52 my @parts = split /::/, $name;
267 24   100     80 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     66 $subinfos = $node->{''} ||= [];
273             }
274             else {
275 12   100     32 $subinfos = $pkg{$name} ||= [];
276             }
277 36         58 push @$subinfos, $subinfo;
278 36 100       136 $to_merge{$subinfos} = $subinfos if $merge_subs;
279             }
280              
281 6         27 for my $subinfos (values %to_merge) {
282 2         9 my $subinfo = shift(@$subinfos)->clone;
283             $subinfo->merge_in($_, src_keep => 1)
284 2         11 for @$subinfos;
285             # replace the many with the one
286 2         6 @$subinfos = ($subinfo);
287             }
288              
289 6         31 return \%pkg;
290             }
291              
292             sub all_fileinfos {
293 2323     2323 0 84264 my @all = @{shift->{fid_fileinfo}};
  2323         8667  
294 2323         5125 shift @all; # drop fid 0
295             # return all non-nullified fileinfos
296 2323         8228 return grep { $_->fid } @all;
  6263         26084  
297             }
298              
299             sub eval_fileinfos {
300 1     1 0 560 return grep { $_->eval_line } shift->all_fileinfos;
  1         5  
301             }
302              
303             sub noneval_fileinfos {
304 675     675 0 6506 return grep { !$_->eval_line } shift->all_fileinfos;
  1751         6819  
305             }
306              
307              
308             sub fileinfo_of {
309 7702     7702 0 120603 my ($self, $arg, $silent_if_undef) = @_;
310              
311 7702 100       17837 if (not defined $arg) {
312 2 100       183 carp "Can't resolve fid of undef value" unless $silent_if_undef;
313 2         72 return undef;
314             }
315              
316             # check if already a file info object
317 7700 100 100     18796 return $arg if ref $arg and UNIVERSAL::can($arg,'fid') and $arg->isa('Devel::NYTProf::FileInfo');
      100        
318              
319 7696         23777 my $fid = $self->resolve_fid($arg);
320 7696 100       19223 if (not $fid) {
321 4         339 carp "Can't resolve fid of '$arg'";
322 4         338 return undef;
323             }
324              
325 7692         15361 my $fi = $self->{fid_fileinfo}[$fid];
326 7692 100       22037 return undef unless defined $fi->fid; # nullified?
327 7212         18610 return $fi;
328             }
329              
330              
331             sub subinfo_of {
332 258     258 0 4380 my ($self, $subname) = @_;
333              
334 258 100       899 if (not defined $subname) {
335 1         161 cluck "Can't resolve subinfo of undef value";
336 1         197 return undef;
337             }
338              
339 257 100       1190 my $si = $self->{sub_subinfo}{$subname}
340             or cluck "Can't resolve subinfo of '$subname'";
341              
342 257         1981 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 41281 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 110941 my $self = shift;
380 450   100     2758 my $args = shift || {};
381 450   100     2866 my $separator = $args->{separator} || '';
382 450   100     2228 my $filehandle = $args->{filehandle} || \*STDOUT;
383              
384             # shallow clone and add sub_caller for migration of tests
385 450         1356 my $startnode = $self;
386              
387 450         1953 $self->_clear_caches;
388              
389             my $callback = sub {
390 28092     28092   53835 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       61980 if (my $hook = $args->{skip_fileinfo_hook}) {
396              
397             # for fid_fileinfo elements...
398 27936 100 100     68629 if ($path->[0] eq 'fid_fileinfo' && @$path==2) {
399 1120         2528 my $fi = $value;
400              
401             # skip nullified fileinfo
402 1120 100       3785 return undef unless $fi->fid;
403              
404             # don't dump internal details of lib modules
405 960         3568 return ({ skip_internal_details => scalar $hook->($fi, $path, $value) }, $value);
406             }
407              
408             # skip sub_subinfo data for 'library modules'
409 26816 50 100     70716 if ($path->[0] eq 'sub_subinfo' && @$path==2 && $value->[0]) {
      66        
410 2432         6691 my $fi = $self->fileinfo_of($value->[0]);
411 2432 100 66     9855 return undef if !$fi or $hook->($fi, $path, $value);
412             }
413              
414             # skip fid_*_time data for 'library modules'
415 26624 100 100     123460 if ($path->[0] =~ /^fid_\w+_time$/ && @$path==2) {
416 3264         9770 my $fi = $self->fileinfo_of($path->[1]);
417 3264 100 100     14373 return undef if !$fi or $hook->($fi, $path, $value);
418             }
419             }
420 26252         79751 return ({}, $value);
421 450         13139 };
422              
423 450         4610 _dump_elements($startnode, $separator, $filehandle, [], $callback);
424             }
425              
426              
427             sub _dump_elements {
428 6792     6792   18346 my ($r, $separator, $fh, $path, $callback) = @_;
429 6792         13168 my $pad = " ";
430 6792         10604 my $padN;
431              
432 6792         20416 my $is_hash = (UNIVERSAL::isa($r, 'HASH'));
433 6792 100       51126 my ($start, $end, $colon, $keys) =
434             ($is_hash)
435             ? ('{', '}', ' => ', [sort keys %$r])
436             : ('[', ']', ': ', [0 .. @$r - 1]);
437              
438 6792 100       18500 if ($separator) {
439 6780         14683 ($start, $end, $colon) = (undef, undef, $separator);
440 6780         19604 $padN = join $separator, @$path, '';
441             }
442             else {
443 12         26 $padN = $pad x (@$path + 1);
444             }
445              
446 6792         24222 my $format = {sub_subinfo => {compact => 1},};
447              
448 6792 100       16662 print $fh "$start\n" if $start;
449 6792   66     21740 my $key1 = $path->[0] || $keys->[0];
450 6792         16907 for my $key (@$keys) {
451              
452 49806 100       112064 next if $key eq 'fid_srclines';
453              
454 49356 100       98637 my $value = ($is_hash) ? $r->{$key} : $r->[$key];
455              
456             # skip undef elements in array
457 49356 100 100     138762 next if !$is_hash && !defined($value);
458             # skip refs to empty arrays in array
459 28092 50 100     93239 next if !$is_hash && ref $value eq 'ARRAY' && !@$value;
      66        
460              
461 28092         49443 my $dump_opts = {};
462 28092 50       57740 if ($callback) {
463 28092         77502 ($dump_opts, $value) = $callback->([ @$path, $key ], $value);
464 28092 100       88774 next if not $dump_opts;
465             }
466              
467 27212         66478 my $prefix = "$padN$key$colon";
468              
469 27212 100       92815 if (UNIVERSAL::can($value,'dump')) {
470 3214         17730 $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         47995 my $as_compact = $format->{$key1}{compact};
478 23998 50       52544 if (not defined $as_compact) { # so guess...
479             $as_compact =
480 23998   100     99516 (UNIVERSAL::isa($value, 'ARRAY') && @$value <= 9 && !grep { ref or !defined }
481             @$value);
482             }
483 23998 100       57998 $as_compact = 0 if not ref $value eq 'ARRAY';
484              
485 23998 100       51285 if ($as_compact) {
    100          
486 48     48   439 no warnings qw(uninitialized);
  48         117  
  48         6408  
487 9066 50       18193 printf $fh "%s[ %s ]\n", $prefix, join(" ", map { defined($_) ? $_ : 'undef' } @$value);
  18132         75331  
488             }
489             elsif (ref $value) {
490 6342         24578 _dump_elements($value, $separator, $fh, [ @$path, $key ], $callback);
491             }
492             else {
493 8590         35005 print $fh "$prefix$value\n";
494             }
495             }
496             }
497 48     48   390 no warnings 'numeric'; # @$path can be non-positive
  48         148  
  48         61158  
498 6792 100       16152 printf $fh "%s$end\n", ($pad x (@$path - 1)) if $end;
499 6792         31069 return 1;
500             }
501              
502              
503             sub get_profile_levels {
504 2     2 0 31 return shift->{profile_modes};
505             }
506              
507             sub get_fid_line_data {
508 1825     1825 0 14782 my ($self, $level) = @_;
509 1825   100     6051 $level ||= 'line';
510 1825         6289 my $fid_line_data = $self->{"fid_${level}_time"};
511 1825         6578 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 18930 my ($self, $normalize_options) = @_;
554              
555 451 100       3075 if ($normalize_options) {
556 449         1411 %{ $self->options } = ();
  449         2801  
557             }
558              
559 451         2898 my $attributes = $self->attributes;
560              
561 451         4840 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       24521 $attributes->{$attr} = 0 if exists $attributes->{$attr};
569             }
570              
571 451         2496 for my $attr (qw(PL_perldb cumulative_overhead_ticks)) {
572 902         3653 delete $attributes->{$attr};
573             }
574              
575             # normalize line data
576 451         3460 for my $level (qw(line block sub)) {
577 1353   50     5818 my $fid_line_data = $self->get_fid_line_data($level) || [];
578              
579             # zero the statement timing data
580 1353         4903 for my $of_fid (@$fid_line_data) {
581 4626 100       15697 _zero_array_elem($of_fid, 0) if $of_fid;
582             }
583             }
584              
585 451         2004 my $sub_subinfo = $self->{sub_subinfo};
586 451         2358 for my $subname (keys %$sub_subinfo) {
587 2450         5637 my $si = $self->{sub_subinfo}{$subname};
588             # zero sub info and sub caller times etc.
589 2450         11165 my $newname = $si->normalize_for_test;
590 2450 100       8669 if ($newname ne $subname) {
591             warn "Normalizing $subname to $newname overwrote other data\n"
592 32 50       184 if $sub_subinfo->{$newname};
593 32         180 $sub_subinfo->{$newname} = delete $sub_subinfo->{$subname};
594             }
595             }
596              
597 451         2814 $_->normalize_for_test for $self->all_fileinfos;
598              
599 451         2308 return 1;
600             }
601              
602              
603             sub _zero_array_elem {
604 3273     3273   7395 my ($ary_of_line_data, $index) = @_;
605 3273         10247 for my $line_data (@$ary_of_line_data) {
606 34491 100       88915 next unless $line_data;
607 10287         21582 $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       29066 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   13481 my $self = shift;
619 7697         17325 my $caches = $self->_caches;
620 7697   66     27734 return $caches->{_filename_to_fid_cache} ||= do {
621 1126         3634 my $filename_to_fid = {};
622 1126         4101 $filename_to_fid->{$_->filename} = $_->fid for $self->all_fileinfos;
623 1126         5797 $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 12380 my ($self, $fid, $incl_lines) = @_;
651 38 50       186 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       131 my $fi = $self->fileinfo_of($fid)
654             or return;
655              
656 38         107 $fid = $fi->fid;
657 38         85 my $caches = $self->_caches;
658              
659 38         230 my $cache_key = "subs_defined_in_file:$fid";
660 38 100       195 return $caches->{$cache_key} if $caches->{$cache_key};
661              
662 33         152 my %subs = map { $_->subname => $_ } $fi->subs_defined;
  150         491  
663              
664 33         151 $caches->{$cache_key} = \%subs;
665 33         310 return $caches->{$cache_key};
666             }
667              
668              
669             sub subs_defined_in_file_by_line {
670 19     19 0 95 my $subs = shift->subs_defined_in_file(@_);
671 19         63 my %line2subs;
672 19         88 for (values %$subs) {
673 82   100     195 my $first_line = $_->first_line || 0; # 0 = xsub?
674 82         117 push @{$line2subs{$first_line}}, $_;
  82         219  
675             }
676 19         145 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 29277 my ($self, $sub) = @_;
704              
705 74 100       290 my $sub_subinfo = $self->subinfo_of($sub)
706             or return; # no such sub; warning supplied by subinfo_of()
707 73         222 my ($fid, $first, $last) = @$sub_subinfo;
708              
709 73 50       211 return if not $fid; # sub has no known file
710              
711 73 50 33     493 my $fileinfo = $fid && $self->fileinfo_of($fid)
712             or croak "No fid_fileinfo for sub $sub fid '$fid'";
713              
714 73         249 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 17724 my ($self, $file) = @_;
736 7698 100       16313 Carp::confess("No file specified") unless defined $file;
737 7697         18155 my $resolve_fid_cache = $self->_filename_to_fid;
738              
739             # exact match
740             return $resolve_fid_cache->{$file}
741 7697 100       20067 if exists $resolve_fid_cache->{$file};
742              
743             # looks like a fid already
744 7643 100       44326 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       21 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       16 if $file =~ m/^\//;
754              
755             # prepend '/' and grep for trailing matches - if just one then use that
756 4         69 my $match = qr{/\Q$file\E$};
757 4         15 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       9 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__