File Coverage

blib/lib/Linux/Smaps.pm
Criterion Covered Total %
statement 268 287 93.3
branch 78 102 76.4
condition 42 112 37.5
subroutine 36 38 94.7
pod 9 9 100.0
total 433 548 79.0


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