File Coverage

blib/lib/Devel/TrackObjects.pm
Criterion Covered Total %
statement 84 120 70.0
branch 33 86 38.3
condition 12 32 37.5
subroutine 16 16 100.0
pod 4 4 100.0
total 149 258 57.7


line stmt bran cond sub pod time code
1             package Devel::TrackObjects;
2 2     2   55249 use strict;
  2         4  
  2         48  
3 2     2   12 use warnings;
  2         4  
  2         63  
4 2     2   11 use Scalar::Util 'weaken';
  2         8  
  2         216  
5 2     2   3104 use overload;
  2         2256  
  2         12  
6              
7             our $VERSION = '0.600';
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   14 shift;
28 2         3 my @opt;
29 2         11 while (@_) {
30 4         8 local $_ = shift;
31 4 100 100     40 if ( ! ref && m{^-(\w+)$} ) {
    100 33        
    50          
32 2         10 push @opt,$1;
33             } elsif ( $_ eq 'track_object' ) {
34             # export function
35 1         3 my ($pkg) = caller();
36 2     2   313 no strict 'refs';
  2         4  
  2         2622  
37 1         9 *{"${pkg}::track_object"} = \&track_object;
  1         8  
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         5 for(@opt) {
48 2 50       14 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         4 $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       1567 _redefine_bless() if @conditions;
68             }
69              
70             ############################################################################
71             # show everything tracked at the end
72             ############################################################################
73             sub END {
74 2 50   2   195 $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 115 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 2210 shift;
95 1   50     8 my $prefix = shift || '';
96 1         3 _remove_destroyed();
97 1 50       3 if ( defined wantarray ) {
98 0         0 return \@weak_objects;
99             } else {
100 1 50       4 if ( @weak_objects ) {
101 1         2 my (%s,%l);
102 1 50       23 print STDERR "LEAK$prefix "
103             . ($with_tstamp ? localtime().' ' :'' ) . " >> \n";
104 1         4 for my $o ( sort {
105 0         0 overload::StrVal($a->[0]) cmp overload::StrVal($b->[0])
106             } @weak_objects ) {
107 1         2 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         7 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 6 shift;
142 4   50     20 my $prefix = shift || '';
143 4         10 _remove_destroyed();
144 4         7 my %count4class;
145 4         8 foreach my $o (@weak_objects) {
146 8   100     39 ( $count4class{ ref($o->[0]) } ||= 0 )++;
147             }
148 4 50       12 if ( defined wantarray ) {
149 4 50       18 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   19551 my ($pkg,$filename,$line) = caller();
169 9   66     33 my $class = $_[1] || $pkg;
170              
171 9 50       23 if (ref($_[0])) {
172             # unregister
173 9 50       18 @weak_objects = grep { $_->[0] && $_->[0] != $_[0] } @weak_objects;
  5         36  
174             }
175 9 50       27 my $object = $old_bless ? $old_bless->( $_[0],$class) : CORE::bless( $_[0],$class );
176              
177 9         12 my $track = 0;
178 9 50       20 if ( @conditions ) {
179 9         14 foreach my $c ( @conditions ) {
180 9 50       37 if ( ! ref($c) ) {
    50          
    0          
181 0 0 0     0 $track = 1,last if $c eq $pkg or $c eq $class;
182             } elsif ( UNIVERSAL::isa($c,'Regexp' )) {
183 9 100 66     72 $track = 1,last if $pkg =~m{$c} or $class =~m{$c};
184             } elsif ( UNIVERSAL::isa($c,'CODE' )) {
185 0 0 0     0 $track = 1,last if $c->($pkg) or $c->($class);
186             }
187             }
188             } else {
189 0         0 $track = 1;
190             }
191 9 100       24 _register( $object,$filename,$line ) if $track;
192              
193 9         73 return $object;
194             }
195              
196             ############################################################################
197             sub track_object {
198 1     1 1 15 my ($object,$info) = @_;
199 1         4 my (undef,$filename,$line) = caller();
200 1         4 _register( $object,$filename,$line,$info );
201             }
202              
203             ############################################################################
204             # redefine bless unless it's already redefined
205             ############################################################################
206             sub _redefine_bless {
207 1 50   1   3 return if $is_redefined;
208              
209             # take redefined variant if exists
210 1         4 $old_bless = \&CORE::CLOBAL::bless;
211 1         1 eval { $old_bless->( {}, __PACKAGE__ ) };
  1         15  
212 1 50       5 $old_bless = undef if $@;
213              
214             # redefine 'bless'
215 2     2   12 no warnings 'once';
  2         8  
  2         436  
216 1         3 *CORE::GLOBAL::bless = \&_bless_and_track;
217 1         63 $is_redefined = 1;
218             }
219              
220              
221             ############################################################################
222             # register object, called from _bless_and_track
223             ############################################################################
224             sub _register {
225 4     4   10 my ($ref,$fname,$line,$info) = @_;
226 4 0       12 warn "TrackObjects: register ".overload::StrVal($ref).
    50          
227             " $fname:$line ".(defined($info) ? $info:'' )."\n"
228             if $debug;
229             #0: referenz
230             #1: file name
231             #2: line in file
232             #3: info message
233             #4: initial size
234             #5: initial total_size
235             #6: last size
236             #7: last total_size
237 4         11 push @weak_objects, [ $ref,$fname,$line,$info ];
238 4         19 weaken( $weak_objects[-1][0] );
239             }
240              
241             ############################################################################
242             # eliminate destroyed objects, eg where the weak ref is undef
243             ############################################################################
244             sub _remove_destroyed {
245 5     5   11 @weak_objects = grep { defined( $_->[0] ) } @weak_objects;
  10         29  
246             }
247              
248              
249             1;
250              
251             __END__