File Coverage

blib/lib/Linux/Smaps.pm
Criterion Covered Total %
statement 207 283 73.1
branch 61 102 59.8
condition 48 113 42.4
subroutine 31 37 83.7
pod 9 9 100.0
total 356 544 65.4


line stmt bran cond sub pod time code
1             package Linux::Smaps;
2              
3 5     5   170642 use 5.008;
  5         20  
  5         200  
4 5     5   28 use strict;
  5         12  
  5         187  
5 5     5   27 use warnings FATAL=>'all';
  5         17  
  5         346  
6 5     5   27 no warnings qw(uninitialized portable);
  5         8  
  5         208  
7 5     5   4558 use Errno qw/EACCES/;
  5         7293  
  5         818  
8              
9             my $min_vma_off;
10              
11 5     5   469 BEGIN {
12             package Linux::Smaps::VMA;
13              
14 5     5   32 use strict;
  5         10  
  5         460  
15             BEGIN {
16 5     5   30 our @attributes=qw(vma_start vma_end r w x mayshare file_off
17             dev_major dev_minor inode file_name is_deleted _line);
18             # it seems a bit faster (~4%) if _line is placed at the end of
19             # @attributes.
20 5         12 my $line_idx=$#attributes;
21 5         10 our %attributes;
22 5         31 for( my $i=0; $i<@attributes; $i++ ) {
23 5     5   23 no strict 'refs';
  5         8  
  5         1734  
24 65         79 my $n=$i;
25 65         353 *{__PACKAGE__.'::'.$attributes[$n]}=
26             $attributes{$attributes[$n]}=
27             sub : lvalue {
28 92     92   612 my $I=$_[0];
29 92 50 66     584 if( @_>1 ) {
    100          
30 0         0 $I->[$n]=$_[1];
31             } elsif( defined($I->[$n]) || !defined($I->[$line_idx]) ) {
32 11         64 $I->[$n];
33             } else {
34 81 50       254 $I->_parse if defined $I->[$line_idx];
35 81         435 $I->[$n];
36             }
37 65         438 };
38 65     17190   306 my $const=sub () {$n};
  17190         68727  
39 65         130 *{__PACKAGE__.'::V_'.$attributes[$n]}=$const;
  65         318  
40 65         75 *{'Linux::Smaps::V_'.$attributes[$n]}=$const;
  65         234  
41 65         233 $Linux::Smaps::VMA::attr_idx{$attributes[$n]}=$n;
42             }
43 5         9 $min_vma_off=@attributes;
44              
45 394         2450 our %special=
46             (
47 394         737 vmflags=>sub {my @l=split /\s+/, $_[0]; shift @l; \@l},
  394         6792  
48 5         30 );
49 5         1960 our @special;
50             }
51              
52 0 0   0   0 sub new {bless [@_[1..$#_]]=>(ref $_[0] ? ref $_[0] : $_[0])}
53              
54             sub _parse {
55 798     798   1087 my ($I)=@_;
56 798 50       1233 @{$I}[V_vma_start..V_is_deleted]=(hex($1), hex($2), ($3 eq 'r'),
  798         5407  
57             ($4 eq 'w'), ($5 eq 'x'), ($6 eq 's'),
58             hex($7), hex($8), hex($9), $10, $11,
59             defined($12))
60             if $I->[V__line]=~/^
61             ([\da-f]+)-([\da-f]+)\s # range
62             ([r\-])([w\-])([x\-])([sp])\s # access mode
63             ([\da-f]+)\s # page offset in file
64             ([\da-f]+):([\da-f]+)\s # device
65             (\d+)\s* # inode
66             (.*?) # file name
67             (\s\(deleted\))? # is deleted?
68             $
69             /xi;
70 798         2205 undef $I->[V__line]; # eval it only once
71 798         1194 return;
72             }
73             }
74              
75             BEGIN {
76 5     5   16 our @attributes=qw{pid lasterror filename procdir _elem};
77 5         6 our %attributes;
78 5         22 for( my $i=0; $i<@attributes; $i++ ) {
79 25         31 my $n=$i;
80 25 50       67 die "Internal Error" # should not happen
81             if exists $Linux::Smaps::VMA::attributes{$attributes[$n]};
82 5     5   31 no strict 'refs';
  5         20  
  5         663  
83 25 100   21   108 *{__PACKAGE__.'::'.$attributes[$n]}=
  21         2394  
84             $attributes{$attributes[$n]}=
85 25         125 sub : lvalue {@_>1 ? $_[0]->[$n]=$_[1] : $_[0]->[$n]};
86 25     1995   156 *{__PACKAGE__.'::M_'.$attributes[$n]}=sub () {$n};
  25         3081  
  1995         4107  
87             }
88             }
89              
90             our $VERSION = '0.13';
91              
92             sub new {
93 14     14 1 10082782 my $class=shift;
94 14 50       68 $class=ref($class) if( ref($class) );
95 14         47 my $I=bless []=>$class;
96 14         36 my %h;
97              
98 14         90 $I->[M_procdir]='/proc';
99 14         50 $I->[M_pid]='self';
100              
101 14 100       56 if( @_==1 ) {
102 1         18 $I->[M_pid]=shift;
103             } else {
104 13         21 our @attributes;
105 13         25 our %attributes;
106 13         98 %h=@_;
107 13         39 foreach my $k (@attributes) {
108 65 100       204 $attributes{$k}->($I, $h{$k}) if exists $h{$k};
109             }
110             }
111              
112 14 100       62 return $I if( $h{uninitialized} );
113              
114 11         54 my $rc=$I->update;
115 11 100       143 die __PACKAGE__.": ".$I->[M_lasterror]."\n" unless( $rc );
116              
117 10         282472 return $rc;
118             }
119              
120             sub clear_refs {
121 0     0 1 0 my ($I)=@_;
122              
123 0         0 my $name=$I->[M_procdir].'/'.$I->[M_pid].'/clear_refs';
124              
125 0 0       0 open my $f, '>', $name or do {
126 0         0 $I->[M_lasterror]="Cannot open $name: $!";
127 0         0 return;
128             };
129 0         0 print $f "1\n";
130 0         0 close $f;
131              
132 0         0 return $I;
133             }
134              
135             my ($cnt1, $fmt1)=(0);
136              
137             sub update {
138 15     15 1 1219 my ($I)=@_;
139              
140 15         21 my $name;
141              
142             # this way one can use one object to loop through a list of processes like:
143             # foreach (@pids) {
144             # $smaps->pid=$_; $smaps->update;
145             # process($smaps);
146             # }
147 15 100       49 if( defined $I->[M_filename] ) {
148 7         16 $name=$I->[M_filename];
149             } else {
150 8         19 $name=$I->[M_procdir].'/'.$I->[M_pid].'/smaps';
151             }
152              
153             # Normally, access permissions for a file are checked when it is opened.
154             # /proc/PID/smaps is different. Here permissions are checked by the read
155             # syscall.
156 15 100       903 open my $f, '<', $name or do {
157 2         26 $I->[M_lasterror]="Cannot open $name: $!";
158 2         15 return;
159             };
160              
161 13         23 my $current;
162 13         53 $I->[M__elem]=[];
163 13         185 my %cache;
164 13         19 my ($l, $tmp, $m);
165 13         25 my $current_off=@Linux::Smaps::VMA::attributes;
166              
167 13         37 $!=0;
168 13         6075 while( defined($l=<$f>) ) {
169 16792 100 66     47301 if( $current_off<@Linux::Smaps::VMA::attributes ) {
    100          
    100          
    50          
170 14849 100       27155 if( $tmp=$Linux::Smaps::VMA::special[$current_off] ) {
171 391         817 $current->[$current_off++]=$tmp->($l);
172             } else {
173 5     5   30 no warnings qw(numeric);
  5         15  
  5         1325  
174 14458         83318 $current->[$current_off++]=0+(unpack $fmt1, $l)[0];
175             }
176             } elsif( $l=~/^(\w+):\s*(\d+) kB$/ ) {
177 55         117 $m=lc $1;
178              
179 55 50       202 if( exists $Linux::Smaps::VMA::attributes{$m} ) {
180 0         0 $I->[M_lasterror]="Linux::Smaps::VMA::$m method is already defined";
181 0         0 return;
182             }
183 55 50       217 if( exists $Linux::Smaps::attributes{$m} ) {
184 0         0 $I->[M_lasterror]="Linux::Smaps::$m method is already defined";
185 0         0 return;
186             }
187              
188 55         266 $current->[$current_off++]=0+$2;
189              
190 55         97 push @Linux::Smaps::VMA::attributes, $m;
191             {
192 5     5   26 no strict 'refs';
  5         8  
  5         619  
  55         56  
193 55         69 my $n=$#Linux::Smaps::VMA::attributes;
194 55 50   1978   260 *{'Linux::Smaps::VMA::'.$m}=
  1978         7222  
195             $Linux::Smaps::VMA::attributes{$m}=
196 55         271 sub : lvalue {@_>1 ? $_[0]->[$n]=$_[1] : $_[0]->[$n]};
197 55         111 $Linux::Smaps::VMA::attr_idx{$m}=$n;
198             }
199              
200             {
201 5     5   23 no strict 'refs';
  5         9  
  5         2054  
  55         60  
202 55         67 my $attr_nr=$#Linux::Smaps::VMA::attributes;
203 55         287 *{__PACKAGE__."::$m"}=$Linux::Smaps::attributes{$m}=sub {
204 9     9   1837 my ($I, $n)=@_;
205 9         22 my $rc=0;
206 9 100 66     35 foreach my $el (length $n
  1500 100       4137  
207             ? grep(
208             {
209 6         25 $_->_parse if(!defined($_->[V_file_name]) and
210             defined($_->[V__line]));
211 1500         8071 $_->[V_file_name] eq $n;
212 3         6 } @{$I->[M__elem]}
213             )
214             : @{$I->[M__elem]}) {
215 246         350 $rc+=$el->[$attr_nr];
216             }
217 9         225 return $rc;
218 55         291 };
219             }
220              
221 55 100       250 if( length($m)>$cnt1 ) {
222 18         21 $cnt1=length($m);
223 18         94 $fmt1="x".($cnt1+1)."A*";
224             }
225             } elsif( $l=~/^(\w+):.+$/ and $tmp=$Linux::Smaps::VMA::special{$m=lc $1} ) {
226 3 50       17 if( exists $Linux::Smaps::VMA::attributes{$m} ) {
227 0         0 $I->[M_lasterror]="Linux::Smaps::VMA::$m method is already defined";
228 0         0 return;
229             }
230              
231 3         8 $Linux::Smaps::VMA::special[$current_off]=$tmp;
232 3         12 $current->[$current_off++]=$tmp->($l);
233              
234 3         7 push @Linux::Smaps::VMA::attributes, $m;
235             {
236 5     5   25 no strict 'refs';
  5         9  
  5         2204  
  3         5  
237 3         6 my $n=$#Linux::Smaps::VMA::attributes;
238 3 50   5   18 *{'Linux::Smaps::VMA::'.$m}=
  5         49  
239             $Linux::Smaps::VMA::attributes{$m}=
240 3         14 sub : lvalue {@_>1 ? $_[0]->[$n]=$_[1] : $_[0]->[$n]};
241 3         21 $Linux::Smaps::VMA::attr_idx{$m}=$n;
242             }
243             } elsif( $l=~/^([\da-f]+-[\da-f]+)\s/i ) {
244             # the rest of the line is lazily parsed
245 1885         3643 @{$current=bless [], 'Linux::Smaps::VMA'}[V__line]=$l;
  1885         7931  
246              
247             # use %cache to work around a bug in some implementations,
248             # VMAs may be reported twice.
249 1885 100       8330 push @{$I->[M__elem]}, $current unless $cache{$1}++;
  1884         3701  
250 1885         14072 $current_off=$min_vma_off;
251             } else {
252 0         0 $I->[M_lasterror]="$name($.): not parsed: $l";
253 0         0 return;
254             }
255             }
256              
257 13 50       102 if( $.==0 ) { # nothing read
258 0   0     0 $!||=EACCES; # some kernels just report it as an empty file
259 0         0 $I->[M_lasterror]="$name: read failed: $!";
260 0         0 close $f;
261 0         0 return;
262             }
263              
264 13         245 close $f;
265              
266 13         838 return $I;
267             }
268              
269             BEGIN {
270 5     5   14 foreach my $n (qw{heap stack vdso vsyscall}) {
271 5     5   29 no strict 'refs';
  5         8  
  5         710  
272 20         24 my $name=$n;
273 20         34 my $s="[$n]";
274 20         16425 *{__PACKAGE__.'::'.$name}=sub {
275 11     11   21 foreach my $el (@{$_[0]->[M__elem]}) {
  11         30  
276 860 100 66     1451 $el->_parse if !defined($el->[V_file_name]) and defined($el->[V__line]);
277 860 100       1500 return $el if $s eq $el->[V_file_name];
278             }
279 20         110 };
280             }
281             }
282              
283             sub unnamed {
284 0     0 1 0 my $I=shift;
285 0 0       0 if( wantarray ) {
286 0 0 0     0 return grep {
287 0         0 $_->_parse if !defined($_->[V_file_name]) and defined($_->[V__line]);
288 0         0 !length $_->[V_file_name];
289 0         0 } @{$I->[M__elem]};
290             } else {
291 0         0 my @idx=@Linux::Smaps::VMA::attr_idx{qw/size rss shared_clean shared_dirty
292             private_clean private_dirty/};
293 0         0 my $sum=Linux::Smaps::VMA->new((0)x@Linux::Smaps::VMA::attributes);
294 0         0 foreach my $el (@{$I->[M__elem]}) {
  0         0  
295 0 0 0     0 $el->_parse if !defined($el->[V_file_name]) and defined($el->[V__line]);
296 0 0       0 next if( length $el->[V_file_name] );
297 0         0 foreach my $idx (@idx) {$sum->[$idx]+=$el->[$idx]}
  0         0  
298             }
299 0         0 return $sum;
300             }
301             }
302              
303             sub named {
304 0     0 1 0 my $I=shift;
305 0 0       0 if( wantarray ) {
306 0 0 0     0 return grep {
307 0         0 $_->_parse if !defined($_->[V_file_name]) and defined($_->[V__line]);
308 0         0 length $_->[V_file_name];
309 0         0 } @{$I->[M__elem]};
310             } else {
311 0         0 my @idx=@Linux::Smaps::VMA::attr_idx{qw/size rss shared_clean shared_dirty
312             private_clean private_dirty/};
313 0         0 my $sum=Linux::Smaps::VMA->new((0)x@Linux::Smaps::VMA::attributes);
314 0         0 foreach my $el (@{$I->[M__elem]}) {
  0         0  
315 0 0 0     0 $el->_parse if !defined($el->[V_file_name]) and defined($el->[V__line]);
316 0 0       0 next if( !length $el->[V_file_name] );
317 0         0 foreach my $idx (@idx) {$sum->[$idx]+=$el->[$idx]}
  0         0  
318             }
319 0         0 return $sum;
320             }
321             }
322              
323             sub all {
324 0     0 1 0 my $I=shift;
325 0 0       0 if( wantarray ) {
326 0         0 return @{$I->[M__elem]};
  0         0  
327             } else {
328 0         0 my @idx=@Linux::Smaps::VMA::attr_idx{qw/size rss shared_clean shared_dirty
329             private_clean private_dirty/};
330 0         0 my $sum=Linux::Smaps::VMA->new((0)x@Linux::Smaps::VMA::attributes);
331 0         0 foreach my $el (@{$I->[M__elem]}) {
  0         0  
332 0         0 foreach my $idx (@idx) {$sum->[$idx]+=$el->[$idx]}
  0         0  
333             }
334 0         0 return $sum;
335             }
336             }
337              
338             sub names {
339 0     0 1 0 my $I=shift;
340 0         0 local $_;
341 0         0 my %h;
342 0 0 0     0 undef @h{map {
343 0         0 $_->_parse if !defined($_->[V_file_name]) and defined($_->[V__line]);
344 0         0 $_->[V_file_name];
345 0         0 } @{$I->[M__elem]}};
346 0         0 delete @h{'',qw/[heap] [stack] [vdso] [vsyscall]/};
347 0         0 return keys %h;
348             }
349              
350             sub diff {
351 4     4 1 20 my $I=shift;
352 4         5 my @my_special;
353 253 100 66     360 my @my=map {
354 4         11 $_->_parse if !defined($_->[V_file_name]) and defined($_->[V__line]);
355 253 100       383 if( $_->[V_file_name]=~/\[\w+\]/ ) {
356 15         26 push @my_special, $_;
357 15         59 ();
358             } else {
359 238         348 $_;
360             }
361 4         8 } @{$I->[M__elem]};
362 4         28 my %other_special;
363 253 100 66     397 my %other=map {
364 4         12 $_->_parse if !defined($_->[V_file_name]) and defined($_->[V__line]);
365 253 100       392 if( $_->[V_file_name]=~/^(\[\w+\])$/ ) {
366 15         77 $other_special{$1}=$_;
367 15         206 ();
368             } else {
369 238         346 ($_->[V_vma_start]=>$_);
370             }
371 4         10 } @{shift->[M__elem]};
372              
373 4         33 my @new;
374             my @diff;
375 0         0 my @old;
376              
377 4         9 foreach my $vma (@my_special) {
378 15 50       29 if( exists $other_special{$vma->[V_file_name]} ) {
379 15         26 my $x=delete $other_special{$vma->[V_file_name]};
380 15 50 66     67 push @diff, [$vma, $x]
      66        
      66        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
381             if( $vma->[V_vma_start] != $x->[V_vma_start] or
382             $vma->[V_vma_end] != $x->[V_vma_end] or
383             $vma->shared_clean != $x->shared_clean or
384             $vma->shared_dirty != $x->shared_dirty or
385             $vma->private_clean != $x->private_clean or
386             $vma->private_dirty != $x->private_dirty or
387             $vma->[V_dev_major] != $x->[V_dev_major] or
388             $vma->[V_dev_minor] != $x->[V_dev_minor] or
389             $vma->[V_r] != $x->[V_r] or
390             $vma->[V_w] != $x->[V_w] or
391             $vma->[V_x] != $x->[V_x] or
392             $vma->[V_file_off] != $x->[V_file_off] or
393             $vma->[V_inode] != $x->[V_inode] or
394             $vma->[V_mayshare] != $x->[V_mayshare] );
395             } else {
396 0         0 push @new, $vma;
397             }
398             }
399 4         12 @old=values %other_special;
400              
401 4         8 foreach my $vma (@my) {
402 238 50       388 if( exists $other{$vma->[V_vma_start]} ) {
403 238         335 my $x=delete $other{$vma->[V_vma_start]};
404 238 50 100     414 push @diff, [$vma, $x]
      66        
      100        
      66        
      66        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
      33        
405             if( $vma->[V_vma_end] != $x->[V_vma_end] or
406             $vma->shared_clean != $x->shared_clean or
407             $vma->shared_dirty != $x->shared_dirty or
408             $vma->private_clean != $x->private_clean or
409             $vma->private_dirty != $x->private_dirty or
410             $vma->[V_dev_major] != $x->[V_dev_major] or
411             $vma->[V_dev_minor] != $x->[V_dev_minor] or
412             $vma->[V_r] != $x->[V_r] or
413             $vma->[V_w] != $x->[V_w] or
414             $vma->[V_x] != $x->[V_x] or
415             $vma->[V_file_off] != $x->[V_file_off] or
416             $vma->[V_inode] != $x->[V_inode] or
417             $vma->[V_mayshare] != $x->[V_mayshare] or
418             $vma->[V_file_name] ne $x->[V_file_name] );
419             } else {
420 0         0 push @new, $vma;
421             }
422             }
423 4         20 push @old, sort {$a->[V_vma_start] <=> $b->[V_vma_start]} values %other;
  0         0  
424              
425 4         41 return \@new, \@diff, \@old;
426             }
427              
428 9     9 1 1892 sub vmas {return @{$_[0]->_elem};}
  9         36  
429              
430             {
431             my $once;
432             sub import {
433 3     3   29 my $class=shift;
434 3 50       14 unless( $once ) {
435 3         4 $once=1;
436 3         6 eval {$class->new(@_)};
  3         12  
437             }
438             }
439             }
440              
441             1;
442             __END__