line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Dash::Leak; |
2
|
|
|
|
|
|
|
|
3
|
4
|
|
|
4
|
|
113169
|
use 5.008008; |
|
4
|
|
|
|
|
17
|
|
|
4
|
|
|
|
|
212
|
|
4
|
4
|
|
|
4
|
|
25
|
use strict; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
133
|
|
5
|
4
|
|
|
4
|
|
22
|
use warnings; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
228
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
Dash::Leak - Track memory allocation |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=cut |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '0.06'; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 SYNOPSIS |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Quick summary of what the module does. |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
BEGIN { |
21
|
|
|
|
|
|
|
# enables operation of Dash::Leak, leaksz is a nop without this |
22
|
|
|
|
|
|
|
$ENV{DEBUG_MEM} = 1; |
23
|
|
|
|
|
|
|
} |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
use Dash::Leak; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
{ |
28
|
|
|
|
|
|
|
leaksz "block label"; |
29
|
|
|
|
|
|
|
# some code, that may leak |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
# If your code leaked, you'll be noticed about change |
32
|
|
|
|
|
|
|
# of process vsize after leaving block |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
leaksz "tests begin"; |
35
|
|
|
|
|
|
|
some_operation($arg); |
36
|
|
|
|
|
|
|
leaksz "some_operation", sub { |
37
|
|
|
|
|
|
|
warn sprintf "We leaked after some_operation($arg) by %+d kilobytes;", shift |
38
|
|
|
|
|
|
|
}; |
39
|
|
|
|
|
|
|
another_operation(); |
40
|
|
|
|
|
|
|
leaksz "another_operation"; |
41
|
|
|
|
|
|
|
# ... |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
use Dash::Leak sub { ... }; # Will call this cb for every alloc, instead of warn |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 EXPORT |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
Export of this module is "virtual", by using L. |
48
|
|
|
|
|
|
|
When C<$ENV{DEBUG_MEM}> is true, it will work, when false, this opcodes will be ignored, like with C; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 FUNCTIONS |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 leaksz $label, [$cb->($delta)] |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Starts tracking current block. |
55
|
|
|
|
|
|
|
If something changed since last note, notice will be warned. |
56
|
|
|
|
|
|
|
If callback is passed, it will be invoked instead of warn. |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=cut |
59
|
|
|
|
|
|
|
|
60
|
4
|
|
|
4
|
|
5024
|
use Devel::Declare (); |
|
4
|
|
|
|
|
46016
|
|
|
4
|
|
|
|
|
256
|
|
61
|
4
|
|
|
4
|
|
3848
|
use Guard; |
|
4
|
|
|
|
|
2422
|
|
|
4
|
|
|
|
|
1266
|
|
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
sub sz(); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
BEGIN { |
66
|
4
|
50
|
|
4
|
|
30
|
if ($^O eq 'freebsd') { |
|
|
50
|
|
|
|
|
|
67
|
0
|
|
|
|
|
0
|
require BSD::Process; |
68
|
0
|
|
|
|
|
0
|
*sz = sub () { BSD::Process->new->{size} }; |
|
0
|
|
|
|
|
0
|
|
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
elsif ($^O eq 'linux') { |
71
|
4
|
|
|
|
|
3017
|
require Proc::ProcessTable; |
72
|
4
|
|
|
132
|
|
456252
|
*sz = sub () { (map { $_->{size} } grep { $_->{pid} == $$ } @{Proc::ProcessTable->new->table})[0] }; |
|
132
|
|
|
|
|
192
|
|
|
132
|
|
|
|
|
714
|
|
|
1320
|
|
|
|
|
464554
|
|
|
132
|
|
|
|
|
872
|
|
73
|
|
|
|
|
|
|
} else { |
74
|
0
|
|
|
|
|
0
|
require Win32::Process::Info; |
75
|
0
|
|
|
|
|
0
|
Win32::Process::Info->import( 'WMI' ); |
76
|
|
|
|
|
|
|
|
77
|
0
|
|
|
|
|
0
|
my $pi = Win32::Process::Info->new; |
78
|
0
|
|
|
|
|
0
|
$pi->Set( elapsed_in_seconds => 0 ); |
79
|
|
|
|
|
|
|
|
80
|
0
|
|
|
|
|
0
|
*sz = sub () { $pi->GetProcInfo( { no_user_info => 1 }, $$ )->[0]->{PrivatePageCount} }; |
|
0
|
|
|
|
|
0
|
|
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
our $cmem = 0; |
85
|
|
|
|
|
|
|
our $SUBNAME = 'leaksz'; |
86
|
|
|
|
|
|
|
our $idx; |
87
|
|
|
|
|
|
|
our $OUT = 0; |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
BEGIN { |
90
|
4
|
100
|
|
4
|
|
37
|
if ($ENV{DEBUG_MEM}) { |
91
|
2
|
|
|
|
|
5
|
my $debug = $ENV{DEBUG_MEM}; |
92
|
2
|
|
|
|
|
333
|
*DEBUG = sub () { $debug }; |
|
0
|
|
|
|
|
0
|
|
93
|
|
|
|
|
|
|
} else { |
94
|
2
|
|
|
|
|
387
|
*DEBUG = sub () { 0 }; |
95
|
|
|
|
|
|
|
} |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
our $FIRST; |
99
|
|
|
|
|
|
|
our %CBS; |
100
|
|
|
|
|
|
|
sub import{ |
101
|
4
|
|
|
4
|
|
50
|
my $class = shift; |
102
|
4
|
|
|
|
|
13
|
my $caller = caller; |
103
|
4
|
100
|
|
|
|
22
|
my $cb = shift if @_; |
104
|
4
|
|
|
|
|
123
|
check("use $class from @{[ (caller)[1,2] ]}",$cb ? $cb : ()) if DEBUG; |
105
|
4
|
|
|
|
|
21
|
if (DEBUG and $cb) { |
106
|
|
|
|
|
|
|
$FIRST ||= $cb; |
107
|
|
|
|
|
|
|
$CBS{$caller} = $cb; |
108
|
|
|
|
|
|
|
} |
109
|
|
|
|
|
|
|
Devel::Declare->setup_for( |
110
|
4
|
|
|
|
|
56
|
$caller, |
111
|
|
|
|
|
|
|
{ $SUBNAME => { const => \&parse } } |
112
|
|
|
|
|
|
|
); |
113
|
|
|
|
|
|
|
{ |
114
|
4
|
|
|
4
|
|
34
|
no strict 'refs'; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
365
|
|
|
3
|
|
|
|
|
71
|
|
115
|
3
|
|
|
|
|
7
|
*{$caller.'::'.$SUBNAME } = sub() { DEBUG }; |
|
4
|
|
|
|
|
1350
|
|
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub check(@) { |
120
|
4
|
|
|
4
|
|
3677
|
use integer; |
|
4
|
|
|
|
|
39
|
|
|
4
|
|
|
|
|
18
|
|
121
|
134
|
|
|
132
|
0
|
4058
|
my $cb; |
122
|
134
|
100
|
100
|
|
|
1406
|
$cb = pop if @_ > 1 and UNIVERSAL::isa( $_[-1], 'CODE' ); |
123
|
134
|
|
|
|
|
2105
|
my $op = "@_"; |
124
|
132
|
|
|
|
|
321
|
my $mem = sz / 1024; |
125
|
132
|
|
|
|
|
4538
|
my $delta = $mem - $cmem; |
126
|
132
|
100
|
|
|
|
566
|
if ($delta != 0) { |
127
|
36
|
|
|
|
|
61
|
$cmem = $mem; |
128
|
36
|
100
|
|
|
|
103
|
if ($cb) { |
129
|
35
|
100
|
|
|
|
286
|
$cb->($delta,$OUT ? 'out' : 'in' ,$op); |
130
|
|
|
|
|
|
|
} else { |
131
|
1
|
|
|
|
|
10
|
my ($caller,$file,$line) = (caller($OUT))[0..2]; |
132
|
1
|
50
|
|
|
|
4
|
if (exists $CBS{$caller}) { |
133
|
0
|
0
|
|
|
|
0
|
$CBS{$caller}->($delta, $OUT ? 'out' : 'in' ,$op); |
134
|
|
|
|
|
|
|
} else { |
135
|
1
|
50
|
|
|
|
15
|
warn sprintf "%s %s: %+dk at %s line %s\n",($OUT ? '<-' : '->'),$op,$delta,$file,$line; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
} |
139
|
132
|
100
|
|
|
|
2150
|
return 1 if $OUT; |
140
|
|
|
|
|
|
|
return guard { |
141
|
66
|
|
|
66
|
|
72738
|
local $OUT = 1; |
142
|
66
|
100
|
|
|
|
357
|
check($op,$cb ? $cb : ()); |
143
|
66
|
|
|
|
|
690
|
}; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
sub parse { |
148
|
4
|
|
|
4
|
0
|
3771
|
my $offset = $_[1]; |
149
|
4
|
|
|
|
|
18
|
$offset += Devel::Declare::toke_move_past_token($offset); |
150
|
4
|
|
|
|
|
21
|
my $linestr = Devel::Declare::get_linestr(); |
151
|
4
|
|
|
|
|
25
|
substr($linestr,$offset,0) = 'and my $__leaksz_'.++$idx.'__ = '.__PACKAGE__.'::check'; |
152
|
4
|
|
|
|
|
12
|
Devel::Declare::set_linestr($linestr); |
153
|
4
|
|
|
|
|
31
|
return; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
END { |
157
|
4
|
|
|
4
|
|
3975
|
DEBUG and check("Finishing", $FIRST ? $FIRST : ()); |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=head1 ACKNOWLEDGEMENTS |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=over 4 |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=item * Thanks to knevgen (L) for linux version patch |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=back |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=head1 AUTHOR |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
Mons Anderson, C<< >> |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head1 COPYRIGHT & LICENSE |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
Copyright 2010 Mons Anderson, all rights reserved. |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
177
|
|
|
|
|
|
|
under the same terms as Perl itself. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=cut |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
1; # End of Dash::Leak |