1package Devel::NYTProf::SubInfo; # sub_subinfo 2 3use strict; 4use warnings; 5use Carp; 6 7use List::Util qw(min max); 8use Data::Dumper; 9 10use Devel::NYTProf::Util qw( 11 trace_level 12); 13use 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); 25 26# extra constants for private elements 27use constant { 28 NYTP_SIi_meta => NYTP_SIi_elements + 1, 29 NYTP_SIi_cache => NYTP_SIi_elements + 2, 30}; 31 32 33sub fid { shift->[NYTP_SIi_FID] || 0 } 34 35sub first_line { shift->[NYTP_SIi_FIRST_LINE] } 36 37sub last_line { shift->[NYTP_SIi_LAST_LINE] } 38 39sub calls { shift->[NYTP_SIi_CALL_COUNT] } 40 41sub incl_time { shift->[NYTP_SIi_INCL_RTIME] } 42 43sub excl_time { shift->[NYTP_SIi_EXCL_RTIME] } 44 45sub subname { shift->[NYTP_SIi_SUB_NAME] } 46 47sub subname_without_package { 48 my $subname = shift->[NYTP_SIi_SUB_NAME]; 49 $subname =~ s/.*:://; 50 return $subname; 51} 52 53sub profile { shift->[NYTP_SIi_PROFILE] } 54 55sub package { (my $pkg = shift->subname) =~ s/^(.*)::.*/$1/; return $pkg } 56 57sub recur_max_depth { shift->[NYTP_SIi_REC_DEPTH] } 58 59sub recur_incl_time { shift->[NYTP_SIi_RECI_RTIME] } 60 61 62# general purpose hash - mainly a hack to help kill off Reader.pm 63sub meta { shift->[NYTP_SIi_meta()] ||= {} } 64# general purpose cache 65sub cache { shift->[NYTP_SIi_cache()] ||= {} } 66 67 68# { fid => { line => [ count, incl_time ] } } 69sub caller_fid_line_places { 70 my ($self, $merge_evals) = @_; 71 carp "caller_fid_line_places doesn't merge evals yet" if $merge_evals; 72 # shallow clone to remove fid 0 is_sub hack 73 my %tmp = %{ $self->[NYTP_SIi_CALLED_BY] || {} }; 74 delete $tmp{0}; 75 return \%tmp; 76} 77 78sub called_by_subnames { 79 my ($self) = @_; 80 my $callers = $self->caller_fid_line_places || {}; 81 82 my %subnames; 83 for my $sc (map { values %$_ } values %$callers) { 84 my $caller_subnames = $sc->[NYTP_SCi_CALLING_SUB]; 85 @subnames{ keys %$caller_subnames } = (); # viv keys 86 } 87 88 return \%subnames; 89} 90 91sub is_xsub { 92 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 my $first = $self->first_line; 97 return undef if not defined $first; 98 return 1 if $first == 0 && $self->last_line == 0; 99 return 0; 100} 101 102sub is_opcode { 103 my $self = shift; 104 return 0 if $self->first_line or $self->last_line; 105 return 1 if $self->subname =~ m/(?:^CORE::|::CORE:)\w+$/; 106 return 0; 107} 108 109sub is_anon { 110 shift->subname =~ m/::__ANON__\b/; 111} 112 113sub kind { 114 my $self = shift; 115 return 'opcode' if $self->is_opcode; 116 return 'xsub' if $self->is_xsub; 117 return 'perl'; 118} 119 120sub fileinfo { 121 my $self = shift; 122 my $fid = $self->fid; 123 if (!$fid) { 124 return undef; # sub not have a known fid 125 } 126 $self->profile->fileinfo_of($fid); 127} 128 129sub clone { # shallow 130 my $self = shift; 131 return bless [ @$self ] => ref $self; 132} 133 134sub _min { 135 my ($a, $b) = @_; 136 $a = $b if not defined $a; 137 $b = $a if not defined $b; 138 # either both are defined or both are undefined here 139 return undef unless defined $a; 140 return min($a, $b); 141} 142 143sub _max { 144 my ($a, $b) = @_; 145 $a = $b if not defined $a; 146 $b = $a if not defined $b; 147 # either both are defined or both are undefined here 148 return undef unless defined $a; 149 return max($a, $b); 150} 151 152 153sub _alter_fileinfo { 154 my ($self, $remove_fi, $new_fi) = @_; 155 my $remove_fid = ($remove_fi) ? $remove_fi->fid : 0; 156 my $new_fid = ( $new_fi) ? $new_fi->fid : 0; 157 158 if ($self->fid == $remove_fid) { 159 $self->[NYTP_SIi_FID] = $new_fid; 160 161 $remove_fi->_remove_sub_defined($self) if $remove_fi; 162 $new_fi->_add_new_sub_defined($self) if $new_fi; 163 } 164} 165 166 167sub _alter_called_by_fileinfo { 168 my ($self, $remove_fi, $new_fi) = @_; 169 my $remove_fid = ($remove_fi) ? $remove_fi->fid : 0; 170 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 if (my $called_by = $self->[NYTP_SIi_CALLED_BY]) { 175 my $cb = delete $called_by->{$remove_fid}; 176 177 if ($cb && $new_fid) { 178 my $new_cb = $called_by->{$new_fid} ||= {}; 179 180 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 while ( my ($line, $cb_li) = each %$cb ) { 186 my $dst_line_info = $new_cb->{$line} ||= []; 187 _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]" 204sub merge_in { 205 my ($self, $donor, %opts) = @_; 206 my $self_subname = $self->subname; 207 my $donor_subname = $donor->subname; 208 209 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 push @{ $self->meta->{merged_sub_names} }, $donor->subname; 215 216 $self->[NYTP_SIi_FIRST_LINE] = _min($self->[NYTP_SIi_FIRST_LINE], $donor->[NYTP_SIi_FIRST_LINE]); 217 $self->[NYTP_SIi_LAST_LINE] = _max($self->[NYTP_SIi_LAST_LINE], $donor->[NYTP_SIi_LAST_LINE]); 218 $self->[NYTP_SIi_CALL_COUNT] += $donor->[NYTP_SIi_CALL_COUNT]; 219 $self->[NYTP_SIi_INCL_RTIME] += $donor->[NYTP_SIi_INCL_RTIME]; 220 $self->[NYTP_SIi_EXCL_RTIME] += $donor->[NYTP_SIi_EXCL_RTIME]; 221 $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 $self->[NYTP_SIi_RECI_RTIME] += $donor->[NYTP_SIi_RECI_RTIME]; # XXX 224 225 # { fid => { line => [ count, incl_time, ... ] } } 226 my $dst_called_by = $self ->[NYTP_SIi_CALLED_BY] ||= {}; 227 my $src_called_by = $donor->[NYTP_SIi_CALLED_BY] || {}; 228 229 $opts{opts} ||= "merge in $donor_subname"; 230 231 # iterate over src and merge into dst 232 while (my ($fid, $src_line_hash) = each %$src_called_by) { 233 234 my $dst_line_hash = $dst_called_by->{$fid}; 235 236 # merge lines in %$src_line_hash into %$dst_line_hash 237 for my $line (keys %$src_line_hash) { 238 my $dst_line_info = $dst_line_hash->{$line} ||= []; 239 my $src_line_info = $src_line_hash->{$line}; 240 delete $src_line_hash->{$line} unless $opts{src_keep}; 241 _merge_in_caller_info($dst_line_info, $src_line_info, %opts); 242 } 243 } 244 245 return; 246} 247 248 249sub _merge_in_caller_info { 250 my ($dst_line_info, $src_line_info, %opts) = @_; 251 my $tag = ($opts{tag}) ? " $opts{tag}" : ""; 252 253 if (!@$src_line_info) { 254 carp sprintf "_merge_in_caller_info%s skipped (empty donor)", $tag 255 if trace_level(); 256 return; 257 } 258 259 if (trace_level() >= 5) { 260 carp sprintf "_merge_in_caller_info%s merging from $src_line_info -> $dst_line_info:", $tag; 261 warn sprintf " . %s\n", _fmt_sc($src_line_info); 262 warn sprintf " + %s\n", _fmt_sc($dst_line_info); 263 } 264 if (!@$dst_line_info) { 265 @$dst_line_info = (0) x NYTP_SCi_elements; 266 $dst_line_info->[NYTP_SCi_CALLING_SUB] = undef; 267 } 268 269 # merge @$src_line_info into @$dst_line_info 270 $dst_line_info->[$_] += $src_line_info->[$_] for ( 271 NYTP_SCi_CALL_COUNT, NYTP_SCi_INCL_RTIME, NYTP_SCi_EXCL_RTIME, 272 ); 273 $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 $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 my $src_cs = $src_line_info->[NYTP_SCi_CALLING_SUB]|| {}; 280 my $dst_cs = $dst_line_info->[NYTP_SCi_CALLING_SUB]||={}; 281 $dst_cs->{$_} = $src_cs->{$_} for keys %$src_cs; 282 283 warn sprintf " = %s\n", _fmt_sc($dst_line_info) 284 if trace_level() >= 5; 285 286 return; 287} 288 289sub _fmt_sc { 290 my ($sc) = @_; 291 return "(empty)" if !@$sc; 292 my $dst_cs = $sc->[NYTP_SCi_CALLING_SUB]||{}; 293 my $by = join " & ", sort keys %$dst_cs; 294 sprintf "calls %d%s", 295 $sc->[NYTP_SCi_CALL_COUNT], ($by) ? ", by $by" : ""; 296} 297 298 299sub caller_fids { 300 my ($self, $merge_evals) = @_; 301 my $callers = $self->caller_fid_line_places($merge_evals) || {}; 302 my @fids = keys %$callers; 303 return @fids; # count in scalar context 304} 305 306sub caller_count { return scalar shift->caller_places; } # XXX deprecate later 307 308# array of [ $fid, $line, $sub_call_info ], ... 309sub caller_places { 310 my ($self, $merge_evals) = @_; 311 my $callers = $self->caller_fid_line_places || {}; 312 313 my @callers; 314 for my $fid (sort { $a <=> $b } keys %$callers) { 315 my $lines_hash = $callers->{$fid}; 316 for my $line (sort { $a <=> $b } keys %$lines_hash) { 317 push @callers, [ $fid, $line, $lines_hash->{$line} ]; 318 } 319 } 320 321 return @callers; # scalar: number of distinct calling locations 322} 323 324sub normalize_for_test { 325 my $self = shift; 326 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 && not $ENV{NYTPROF_TEST_SKIP_EVAL_NORM}; 332 333 # zero subroutine inclusive time 334 $self->[NYTP_SIi_INCL_RTIME] = 0; 335 $self->[NYTP_SIi_EXCL_RTIME] = 0; 336 $self->[NYTP_SIi_RECI_RTIME] = 0; 337 338 # { fid => { line => [ count, incl, excl, ... ] } } 339 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 for my $fid (keys %$callers) { 345 next if not $fid; 346 my $fileinfo = $profile->fileinfo_of($fid) or next; 347 next if $fileinfo->filename !~ /(AutoLoader|Exporter)\.pm$/; 348 349 # normalize the lines X,Y,Z to 1,2,3 350 my %lines = %{ delete $callers->{$fid} }; 351 my @lines = @lines{sort { $a <=> $b } keys %lines}; 352 $callers->{$fid} = { map { $_ => shift @lines } 1..@lines }; 353 } 354 355 for my $sc (map { values %$_ } values %$callers) { 356 # zero per-call-location subroutine inclusive time 357 $sc->[NYTP_SCi_INCL_RTIME] = 358 $sc->[NYTP_SCi_EXCL_RTIME] = 359 $sc->[NYTP_SCi_RECI_RTIME] = 0; 360 361 if (not $ENV{NYTPROF_TEST_SKIP_EVAL_NORM}) { 362 # normalize eval sequence numbers in anon sub names to 0 363 my $names = $sc->[NYTP_SCi_CALLING_SUB]||{}; 364 for my $subname (keys %$names) { 365 (my $newname = $subname) =~ s/ \( ((?:re_)?) eval \s \d+ \) /(${1}eval 0)/xg; 366 next if $newname eq $subname; 367 warn "Normalizing $subname to $newname overwrote other calling-sub data\n" 368 if $names->{$newname}; 369 $names->{$newname} = delete $names->{$subname}; 370 } 371 } 372 373 } 374 return $self->[NYTP_SIi_SUB_NAME]; 375} 376 377sub dump { 378 my ($self, $separator, $fh, $path, $prefix) = @_; 379 380 my ($fid, $l1, $l2, $calls) = @{$self}[ 381 NYTP_SIi_FID, NYTP_SIi_FIRST_LINE, NYTP_SIi_LAST_LINE, NYTP_SIi_CALL_COUNT 382 ]; 383 my @values = @{$self}[ 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 map({ defined($_) ? $_ : 'undef' } $fid, $l1, $l2, $calls), 390 join(" ", map { defined($_) ? $_ : 'undef' } @values); 391 392 my @caller_places = $self->caller_places; 393 for my $cp (@caller_places) { 394 my ($fid, $line, $sc) = @$cp; 395 my @sc = @$sc; 396 $sc[NYTP_SCi_CALLING_SUB] = join "|", sort keys %{ $sc[NYTP_SCi_CALLING_SUB] }; 397 printf $fh "%s%s%s%d:%d%s[ %s ]\n", 398 $prefix, 399 'called_by', $separator, 400 $fid, $line, $separator, 401 join(" ", map { defined($_) ? $_ : 'undef' } @sc); 402 } 403 404 # where a sub has had others merged into it, list them 405 my $merge_subs = $self->meta->{merged_sub_names} || []; 406 for my $ms (sort @$merge_subs) { 407 printf $fh "%s%s%s%s\n", 408 $prefix, 'merge_donor', $separator, $ms; 409 } 410} 411 412# vim:ts=8:sw=4:et 4131; 414