File Coverage

blib/lib/Devel/TrackObjects.pm
Criterion Covered Total %
statement 81 117 69.2
branch 31 82 37.8
condition 12 32 37.5
subroutine 16 16 100.0
pod 4 4 100.0
total 144 251 57.3


line stmt bran cond sub pod time code
1             package Devel::TrackObjects;
2 2     2   20240 use strict;
  2         6  
  2         76  
3 2     2   12 use warnings;
  2         3  
  2         68  
4 2     2   10 use Scalar::Util 'weaken';
  2         15  
  2         274  
5 2     2   3932 use overload;
  2         2245  
  2         11  
6              
7             our $VERSION = 0.5;
8              
9             my @weak_objects; # List of weak objects incl file + line
10             my @conditions; # which objects to track, set by import
11             my $is_redefined; # flag if already redefined
12             my $old_bless; # bless sub before redefining
13              
14             my $debug; # enable internal debugging
15             my $verbose; # detailed output instead of compact
16             my $with_tstamp; # prefix output with timestamp
17             my $with_size; # with size of objects
18             my $with_sizediff; # track changes in size
19             my $no_end; # no show tracked at END
20              
21              
22             ############################################################################
23             # redefined CORE::GLOBAL::bless if restrictions are given
24             # which classes should get tracked
25             ############################################################################
26             sub import {
27 2     2   15 shift;
28 2         2 my @opt;
29 2         9 while (@_) {
30 4         7 local $_ = shift;
31 4 100 100     37 if ( ! ref && m{^-(\w+)$} ) {
    100 33        
    50          
32 2         12 push @opt,$1;
33             } elsif ( $_ eq 'track_object' ) {
34             # export function
35 1         4 my ($pkg) = caller();
36 2     2   322 no strict 'refs';
  2         2  
  2         2447  
37 1         12 *{"${pkg}::track_object"} = \&track_object;
  1         9  
38             } elsif ( ! ref && m{^/} ) {
39             # assume uncompiled regex
40 0         0 my $rx = eval "qr$_";
41 0 0       0 die $@ if $@;
42 0         0 push @conditions,$rx;
43             } else {
44 1         3 push @conditions,$_
45             }
46             }
47 2         4 for(@opt) {
48 2 50       12 if ( $_ eq 'debug' ) {
    50          
    50          
    50          
    0          
    0          
49 0         0 $debug = 1;
50             } elsif ( $_ eq 'verbose' ) {
51 0         0 $verbose = 1;
52             } elsif ( $_ eq 'timestamp' ) {
53 0         0 $with_tstamp = 1;
54             } elsif ( $_ eq 'noend' ) {
55 2         5 $no_end = 1;
56             } elsif ( $_ eq 'size' ) {
57             # need Devel::Size;
58 0 0       0 $with_size = eval { require Devel::Size }
  0         0  
59             or die "need Devel::Size installed for '-size' option"
60             } elsif ( $_ eq 'sizediff' ) {
61 0         0 $with_sizediff = 1;
62 0 0       0 push @opt,'size' if ! $with_size;
63             } else {
64 0         0 die "unknown option $_";
65             }
66             }
67 2 100       2150 _redefine_bless() if @conditions;
68             }
69              
70             ############################################################################
71             # show everything tracked at the end
72             ############################################################################
73             sub END {
74 2 50   2   334 $no_end && return;
75 0 0       0 __PACKAGE__->show_tracked() if $is_redefined;
76 0         0 1;
77             }
78              
79              
80             ############################################################################
81             # depending on $verbose show detailed or compact version
82             ############################################################################
83             sub show_tracked {
84 4 50   4 1 112 return $verbose
85             ? show_tracked_detailed(@_)
86             : show_tracked_compact(@_);
87             }
88              
89             ############################################################################
90             # show what's still used. If I want something back give reference to
91             # \@weak_objects, else print myself to STDERR
92             ############################################################################
93             sub show_tracked_detailed {
94 1     1 1 2404 shift;
95 1   50     8 my $prefix = shift || '';
96 1         3 _remove_destroyed();
97 1 50       4 if ( defined wantarray ) {
98 0         0 return \@weak_objects;
99             } else {
100 1 50       3 if ( @weak_objects ) {
101 1         1 my (%s,%l);
102 1 50       26 print STDERR "LEAK$prefix "
103             . ($with_tstamp ? localtime().' ' :'' ) . " >> \n";
104 1         5 for my $o ( sort {
  0         0  
105             overload::StrVal($a->[0]) cmp overload::StrVal($b->[0])
106             } @weak_objects ) {
107 1         1 my $line = '-- ';
108 1 50       4 if ( $with_size ) {
109 0         0 my $size = Devel::Size::size($o->[0]);
110 0         0 my $total_size = Devel::Size::total_size($o->[0]);
111 0 0       0 if ( $with_sizediff ) {
112 0   0     0 $line .= sprintf("size=%d/%+d/%+d ",$size,
      0        
113             $size-($o->[6]||0),$size-($o->[4]||0));
114 0   0     0 $line .= sprintf("%d/%+d/%+d ", $total_size,
      0        
115             $total_size-($o->[7]||0),$total_size-($o->[5]||0));
116 0 0       0 $o->[4] = $size if ! defined $o->[4];
117 0 0       0 $o->[5] = $total_size if ! defined $o->[5];
118 0         0 $o->[6] = $size;
119 0         0 $o->[7] = $total_size;
120             } else {
121 0         0 $line .= "size=$size total=$total_size ";
122             }
123             }
124 1 50       6 $line .= sprintf "%s | %s:%s%s\n",
125             overload::StrVal($o->[0]),$o->[1],$o->[2],
126             defined($o->[3]) ? " $o->[3]":'';
127 1         15 print STDERR $line;
128             }
129 1         6 print STDERR "LEAK$prefix --\n";
130             } else {
131 0 0       0 print STDERR "LEAK$prefix "
132             . ($with_tstamp ? localtime().' ' :'' ) . " >> empty --\n";
133             }
134             }
135             }
136              
137             ############################################################################
138             # show tracked objects in compact form, e.g. only counter for each class
139             ############################################################################
140             sub show_tracked_compact {
141 4     4 1 7 shift;
142 4   50     26 my $prefix = shift || '';
143 4         51 _remove_destroyed();
144 4         8 my %count4class;
145 4         20 foreach my $o (@weak_objects) {
146 8   100     49 ( $count4class{ ref($o->[0]) } ||= 0 )++;
147             }
148 4 50       12 if ( defined wantarray ) {
149 4 50       25 return %count4class ? \%count4class : undef
150             }
151              
152 0         0 my $msg = "LEAK$prefix >> ";
153 0 0       0 if ( %count4class ) {
154 0         0 foreach ( sort keys %count4class ) {
155 0         0 $msg .= $_.'='.$count4class{$_}.' ';
156             }
157             } else {
158 0         0 $msg .= "empty "
159             }
160 0         0 $msg .= "--\n";
161 0         0 print STDERR $msg;
162             }
163              
164             ############################################################################
165             # bless object and track it, if it matches @condition
166             ############################################################################
167             sub _bless_and_track($;$) {
168 9     9   21548 my ($pkg,$filename,$line) = caller();
169 9   66     35 my $class = $_[1] || $pkg;
170 9 50       32 my $object = $old_bless ? $old_bless->( $_[0],$class) : CORE::bless( $_[0],$class );
171              
172 9         14 my $track = 0;
173 9 50       20 if ( @conditions ) {
174 9         20 foreach my $c ( @conditions ) {
175 9 50       48 if ( ! ref($c) ) {
    50          
    0          
176 0 0 0     0 $track = 1,last if $c eq $pkg or $c eq $class;
177             } elsif ( UNIVERSAL::isa($c,'Regexp' )) {
178 9 100 66     130 $track = 1,last if $pkg =~m{$c} or $class =~m{$c};
179             } elsif ( UNIVERSAL::isa($c,'CODE' )) {
180 0 0 0     0 $track = 1,last if $c->($pkg) or $c->($class);
181             }
182             }
183             } else {
184 0         0 $track = 1;
185             }
186 9 100       29 _register( $object,$filename,$line ) if $track;
187              
188 9         86 return $object;
189             }
190              
191             ############################################################################
192             sub track_object {
193 1     1 1 13 my ($object,$info) = @_;
194 1         4 my (undef,$filename,$line) = caller();
195 1         4 _register( $object,$filename,$line,$info );
196             }
197              
198             ############################################################################
199             # redefine bless unless it's already redefined
200             ############################################################################
201             sub _redefine_bless {
202 1 50   1   2 return if $is_redefined;
203              
204             # take redefined variant if exists
205 1         3 $old_bless = \&CORE::CLOBAL::bless;
206 1         2 eval { $old_bless->( {}, __PACKAGE__ ) };
  1         14  
207 1 50       4 $old_bless = undef if $@;
208              
209             # redefine 'bless'
210 2     2   13 no warnings 'once';
  2         5  
  2         443  
211 1         2 *CORE::GLOBAL::bless = \&_bless_and_track;
212 1         50 $is_redefined = 1;
213             }
214              
215              
216             ############################################################################
217             # register object, called from _bless_and_track
218             ############################################################################
219             sub _register {
220 4     4   9 my ($ref,$fname,$line,$info) = @_;
221 4 0       14 warn "TrackObjects: register ".overload::StrVal($ref).
    50          
222             " $fname:$line ".(defined($info) ? $info:'' )."\n"
223             if $debug;
224             #0: referenz
225             #1: file name
226             #2: line in file
227             #3: info message
228             #4: initial size
229             #5: initial total_size
230             #6: last size
231             #7: last total_size
232 4         13 push @weak_objects, [ $ref,$fname,$line,$info ];
233 4         20 weaken( $weak_objects[-1][0] );
234             }
235              
236             ############################################################################
237             # eliminate destroyed objects, eg where the weak ref is undef
238             ############################################################################
239             sub _remove_destroyed {
240 5     5   11 @weak_objects = grep { defined( $_->[0] ) } @weak_objects;
  10         33  
241             }
242              
243              
244             1;
245              
246             __END__