File Coverage

lib/Dash/Leak.pm
Criterion Covered Total %
statement 64 75 85.3
branch 20 26 76.9
condition 3 3 100.0
subroutine 15 15 100.0
pod 0 2 0.0
total 102 121 84.3


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