File Coverage

blib/lib/Devel/TrackObjects.pm
Criterion Covered Total %
statement 81 115 70.4
branch 32 86 37.2
condition 12 32 37.5
subroutine 16 16 100.0
pod 4 4 100.0
total 145 253 57.3


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