File Coverage

blib/lib/Linux/Info/Processes.pm
Criterion Covered Total %
statement 175 217 80.6
branch 44 84 52.3
condition 19 43 44.1
subroutine 20 21 95.2
pod 4 4 100.0
total 262 369 71.0


line stmt bran cond sub pod time code
1             package Linux::Info::Processes;
2 3     3   21 use strict;
  3         6  
  3         88  
3 3     3   15 use warnings;
  3         5  
  3         88  
4 3     3   1508 use Time::HiRes 1.9725;
  3         4024  
  3         16  
5 3     3   450 use constant NUMBER => qr/^-{0,1}\d+(?:\.\d+){0,1}\z/;
  3         6  
  3         9043  
6              
7             our $VERSION = '1.4'; # VERSION
8              
9             =head1 NAME
10              
11             Linux::Info::Processes - Collect linux process statistics.
12              
13             =head1 SYNOPSIS
14              
15             use Linux::Info::Processes;
16              
17             my $lxs = Linux::Info::Processes->new;
18             # or Linux::Info::Processes->new(pids => \@pids)
19              
20             $lxs->init;
21             sleep 1;
22             my $stat = $lxs->get;
23              
24             =head1 DESCRIPTION
25              
26             Linux::Info::Processes gathers process information from the virtual
27             F filesystem (procfs).
28              
29             For more information read the documentation of the front-end module
30             L.
31              
32             =head1 PROCESS STATISTICS
33              
34             Generated by FpidE/stat>, FpidE/status>,
35             FpidE/cmdline> and F.
36              
37             Note that if F isn't readable, the key owner is set to F.
38              
39             ppid - The parent process ID of the process.
40             nlwp - The number of light weight processes that runs by this process.
41             owner - The owner name of the process.
42             pgrp - The group ID of the process.
43             state - The status of the process.
44             session - The session ID of the process.
45             ttynr - The tty the process use.
46             minflt - The number of minor faults the process made.
47             cminflt - The number of minor faults the child process made.
48             mayflt - The number of mayor faults the process made.
49             cmayflt - The number of mayor faults the child process made.
50             stime - The number of jiffies the process have beed scheduled in kernel mode.
51             utime - The number of jiffies the process have beed scheduled in user mode.
52             ttime - The number of jiffies the process have beed scheduled (user + kernel).
53             cstime - The number of jiffies the process waited for childrens have been scheduled in kernel mode.
54             cutime - The number of jiffies the process waited for childrens have been scheduled in user mode.
55             prior - The priority of the process (+15).
56             nice - The nice level of the process.
57             sttime - The time in jiffies the process started after system boot.
58             actime - The time in D:H:M:S (days, hours, minutes, seconds) the process is active.
59             vsize - The size of virtual memory of the process.
60             nswap - The size of swap space of the process.
61             cnswap - The size of swap space of the childrens of the process.
62             cpu - The CPU number the process was last executed on.
63             wchan - The "channel" in which the process is waiting.
64             fd - This is a subhash containing each file which the process has open, named by its file descriptor.
65             0 is standard input, 1 standard output, 2 standard error, etc. Because only the owner or root
66             can read /proc//fd this hash could be empty.
67             cmd - Command of the process.
68             cmdline - Command line of the process.
69              
70             Generated by FpidE/statm>. All statistics provides information
71             about memory in pages:
72              
73             size - The total program size of the process.
74             resident - Number of resident set size, this includes the text, data and stack space.
75             share - Total size of shared pages of the process.
76             trs - Total text size of the process.
77             drs - Total data/stack size of the process.
78             lrs - Total library size of the process.
79             dtp - Total size of dirty pages of the process (unused since kernel 2.6).
80              
81             It's possible to convert pages to bytes or kilobytes. Example - if the pagesize of your
82             system is 4kb:
83              
84             $Linux::Info::Processes::PAGES_TO_BYTES = 0; # pages (default)
85             $Linux::Info::Processes::PAGES_TO_BYTES = 4; # convert to kilobytes
86             $Linux::Info::Processes::PAGES_TO_BYTES = 4096; # convert to bytes
87              
88             # or with
89             Linux::Info::Processes->new(pages_to_bytes => 4096);
90              
91             Generated by FpidE/io>.
92              
93             rchar - Bytes read from storage (might have been from pagecache).
94             wchar - Bytes written.
95             syscr - Number of read syscalls.
96             syscw - Numner of write syscalls.
97             read_bytes - Bytes really fetched from storage layer.
98             write_bytes - Bytes sent to the storage layer.
99             cancelled_write_bytes - Refer to docs.
100              
101             See Documentation/filesystems/proc.txt for more (from kernel 2.6.20)
102              
103             =head1 METHODS
104              
105             =head2 new()
106              
107             Call C to create a new object.
108              
109             my $lxs = Linux::Info::Processes->new;
110              
111             It's possible to handoff an array reference with a PID list.
112              
113             my $lxs = Linux::Info::Processes->new(pids => [ 1, 2, 3 ]);
114              
115             It's also possible to set the path to the proc filesystem.
116              
117             Linux::Info::Processes->new(
118             files => {
119             # This is the default
120             path => '/proc',
121             uptime => 'uptime',
122             stat => 'stat',
123             statm => 'statm',
124             status => 'status',
125             cmdline => 'cmdline',
126             wchan => 'wchan',
127             fd => 'fd',
128             io => 'io',
129             }
130             );
131              
132             =head2 init()
133              
134             Call C to initialize the statistics.
135              
136             $lxs->init;
137              
138             =head2 get()
139              
140             Call C to get the statistics. C returns the statistics as a hash reference.
141              
142             my $stat = $lxs->get;
143              
144             Note:
145              
146             Processes that were created between the call of init() and get() are returned as well,
147             but the keys minflt, cminflt, mayflt, cmayflt, utime, stime, cutime, and cstime are set
148             to the value 0.00 because there are no inititial values to calculate the deltas.
149              
150             =head2 raw()
151              
152             Get raw values.
153              
154             =head1 EXPORTS
155              
156             Nothing.
157              
158             =head1 SEE ALSO
159              
160             =over
161              
162             =item *
163              
164             B
165              
166             =item *
167              
168             B
169              
170             =item *
171              
172             L
173              
174             =back
175              
176             =head1 AUTHOR
177              
178             Alceu Rodrigues de Freitas Junior, Earfreitas@cpan.orgE
179              
180             =head1 COPYRIGHT AND LICENSE
181              
182             This software is copyright (c) 2015 of Alceu Rodrigues de Freitas Junior, Earfreitas@cpan.orgE
183              
184             This file is part of Linux Info project.
185              
186             Linux-Info is free software: you can redistribute it and/or modify
187             it under the terms of the GNU General Public License as published by
188             the Free Software Foundation, either version 3 of the License, or
189             (at your option) any later version.
190              
191             Linux-Info is distributed in the hope that it will be useful,
192             but WITHOUT ANY WARRANTY; without even the implied warranty of
193             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
194             GNU General Public License for more details.
195              
196             You should have received a copy of the GNU General Public License
197             along with Linux Info. If not, see .
198              
199             =cut
200              
201             our $PAGES_TO_BYTES = 0;
202              
203             sub new {
204 3     3 1 6 my $class = shift;
205 3 50       11 my $opts = ref( $_[0] ) ? shift : {@_};
206              
207 3         22 my %self = (
208             files => {
209             path => '/proc',
210             uptime => 'uptime',
211             stat => 'stat',
212             statm => 'statm',
213             status => 'status',
214             cmdline => 'cmdline',
215             wchan => 'wchan',
216             fd => 'fd',
217             io => 'io',
218             },
219             );
220              
221 3 50       11 if ( defined $opts->{pids} ) {
222 0 0       0 if ( ref( $opts->{pids} ) ne 'ARRAY' ) {
223 0         0 die "the PIDs must be passed as a array reference to new()";
224             }
225              
226 0         0 foreach my $pid ( @{ $opts->{pids} } ) {
  0         0  
227 0 0       0 if ( $pid !~ /^\d+\z/ ) {
228 0         0 die "PID '$pid' is not a number";
229             }
230             }
231              
232 0         0 $self{pids} = $opts->{pids};
233             }
234              
235 3         5 foreach my $file ( keys %{ $opts->{files} } ) {
  3         12  
236 0         0 $self{files}{$file} = $opts->{files}->{$file};
237             }
238              
239 3 50       11 if ( $opts->{pages_to_bytes} ) {
240 0         0 $self{pages_to_bytes} = $opts->{pages_to_bytes};
241             }
242              
243 3         18 return bless \%self, $class;
244             }
245              
246             sub init {
247 3     3 1 7 my $self = shift;
248 3         8 $self->{init} = $self->_init;
249             }
250              
251             sub get {
252 3     3 1 10 my $self = shift;
253              
254 3 50       17 if ( !exists $self->{init} ) {
255 0         0 die "there are no initial statistics defined";
256             }
257              
258 3         23 $self->{stats} = $self->_load;
259 3         19 $self->_deltas;
260 3         21 return $self->{stats};
261             }
262              
263             sub raw {
264 0     0 1 0 my $self = shift;
265 0         0 my $stat = $self->_load;
266              
267 0         0 return $stat;
268             }
269              
270             #
271             # private stuff
272             #
273              
274             sub _init {
275 3     3   5 my $self = shift;
276 3         18 my $file = $self->{files};
277 3         6 my $pids = $self->_get_pids;
278 3         8 my $stats = {};
279              
280 3         17 $stats->{time} = Time::HiRes::gettimeofday();
281              
282 3         10 foreach my $pid (@$pids) {
283 30         85 my $stat = $self->_get_stat($pid);
284              
285 30 50       79 if ( defined $stat ) {
286 30         59 foreach my $key (
287             qw/minflt cminflt mayflt cmayflt utime stime cutime cstime sttime/
288             )
289             {
290 270         653 $stats->{$pid}->{$key} = $stat->{$key};
291             }
292 30         70 $stats->{$pid}->{io} = $self->_get_io($pid);
293             }
294             }
295              
296 3         30 return $stats;
297             }
298              
299             sub _load {
300 3     3   7 my $self = shift;
301 3         9 my $file = $self->{files};
302 3         31 my $uptime = $self->_uptime;
303 3         20 my $pids = $self->_get_pids;
304 3         15 my $stats = {};
305              
306 3         28 $stats->{time} = Time::HiRes::gettimeofday();
307              
308 3         13 PID: foreach my $pid (@$pids) {
309 30         64 foreach my $key (qw/statm stat io owner cmdline wchan fd/) {
310 210         495 my $method = "_get_$key";
311 210         734 my $data = $self->$method($pid);
312              
313 210 50       560 if ( !defined $data ) {
314 0         0 delete $stats->{$pid};
315 0         0 next PID;
316             }
317              
318 210 100 100     1012 if ( $key eq "statm" || $key eq "stat" ) {
319 60         334 for my $x ( keys %$data ) {
320 900         2021 $stats->{$pid}->{$x} = $data->{$x};
321             }
322             }
323             else {
324 150         687 $stats->{$pid}->{$key} = $data;
325             }
326             }
327             }
328              
329 3         16 return $stats;
330             }
331              
332             sub _deltas {
333 3     3   9 my $self = shift;
334 3         9 my $istat = $self->{init};
335 3         8 my $lstat = $self->{stats};
336 3         13 my $uptime = $self->_uptime;
337              
338 3 50 33     32 if ( !defined $istat->{time} || !defined $lstat->{time} ) {
339 0         0 die "not defined key found 'time'";
340             }
341              
342 3 50 33     84 if ( $istat->{time} !~ NUMBER || $lstat->{time} !~ NUMBER ) {
343 0         0 die "invalid value for key 'time'";
344             }
345              
346 3         13 my $time = $lstat->{time} - $istat->{time};
347 3         9 $istat->{time} = $lstat->{time};
348 3         10 delete $lstat->{time};
349              
350 3         9 for my $pid ( keys %{$lstat} ) {
  3         19  
351 30         46 my $ipid = $istat->{$pid};
352 30         46 my $lpid = $lstat->{$pid};
353              
354             # yeah, what happends if the start time is different... it seems that a new
355             # process with the same process-id were created... for this reason I have to
356             # check if the start time is equal!
357 30 50 33     123 if ( $ipid && $ipid->{sttime} == $lpid->{sttime} ) {
358 30         57 for my $k (
359             qw(minflt cminflt mayflt cmayflt utime stime cutime cstime))
360             {
361 240 50       469 if ( !defined $ipid->{$k} ) {
362 0         0 die "not defined key found '$k'";
363             }
364 240 50 33     1424 if ( $ipid->{$k} !~ NUMBER || $lpid->{$k} !~ NUMBER ) {
365 0         0 die "invalid value for key '$k'";
366             }
367              
368 240         516 $lpid->{$k} -= $ipid->{$k};
369 240         347 $ipid->{$k} += $lpid->{$k};
370              
371 240 100 66     497 if ( $lpid->{$k} > 0 && $time > 0 ) {
372 8         51 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} / $time );
373             }
374             else {
375 232         832 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} );
376             }
377             }
378              
379 30         134 $lpid->{ttime} = sprintf( '%.2f', $lpid->{stime} + $lpid->{utime} );
380              
381 30         59 for my $k (
382             qw(rchar wchar syscr syscw read_bytes write_bytes cancelled_write_bytes)
383             )
384             {
385 210 100 66     445 if ( defined $ipid->{io}->{$k} && defined $lpid->{io}->{$k} ) {
386 21 50 33     142 if ( $ipid->{io}->{$k} !~ NUMBER
387             || $lpid->{io}->{$k} !~ NUMBER )
388             {
389 0         0 die "invalid value for io key '$k'";
390             }
391 21         50 $lpid->{io}->{$k} -= $ipid->{io}->{$k};
392 21         39 $ipid->{io}->{$k} += $lpid->{io}->{$k};
393 21 100 66     110 if ( $lpid->{io}->{$k} > 0 && $time > 0 ) {
394             $lpid->{io}->{$k} =
395 6         45 sprintf( '%.2f', $lpid->{io}->{$k} / $time );
396             }
397             else {
398             $lpid->{io}->{$k} =
399 15         60 sprintf( '%.2f', $lpid->{io}->{$k} );
400             }
401             }
402             }
403             }
404             else {
405             # calculate the statistics since process creation
406 0         0 for my $k (
407             qw(minflt cminflt mayflt cmayflt utime stime cutime cstime))
408             {
409 0         0 my $p_uptime = $uptime - $lpid->{sttime} / 100;
410 0         0 $istat->{$pid}->{$k} = $lpid->{$k};
411              
412 0 0       0 if ( $p_uptime > 0 ) {
413 0         0 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} / $p_uptime );
414             }
415             else {
416 0         0 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} );
417             }
418             }
419              
420 0         0 for my $k (
421             qw(rchar wchar syscr syscw read_bytes write_bytes cancelled_write_bytes)
422             )
423             {
424 0         0 my $p_uptime = $uptime - $lpid->{sttime} / 100;
425 0   0     0 $lpid->{io}->{$k} ||= 0;
426 0         0 $istat->{$pid}->{io}->{$k} = $lpid->{io}->{$k};
427              
428 0 0       0 if ( $p_uptime > 0 ) {
429             $lpid->{io}->{$k} =
430 0         0 sprintf( '%.2f', $lpid->{io}->{$k} / $p_uptime );
431             }
432             else {
433 0         0 $lpid->{io}->{$k} = sprintf( '%.2f', $lpid->{io}->{$k} );
434             }
435             }
436              
437 0         0 $lpid->{ttime} = sprintf( '%.2f', $lpid->{stime} + $lpid->{utime} );
438 0         0 $istat->{$pid}->{sttime} = $lpid->{sttime};
439             }
440             }
441             }
442              
443             sub _get_statm {
444 30     30   82 my ( $self, $pid ) = @_;
445 30         61 my $file = $self->{files};
446 30         48 my %stat = ();
447              
448 30 50       1134 open my $fh, '<', "$file->{path}/$pid/$file->{statm}"
449             or return;
450              
451 30         637 my @line = split /\s+/, <$fh>;
452              
453 30 50       112 if ( @line < 7 ) {
454 0         0 return;
455             }
456              
457 30   33     164 my $ptb = $self->{pages_to_bytes} || $PAGES_TO_BYTES;
458              
459 30 50       85 if ($ptb) {
460             @stat{qw(size resident share trs lrs drs dtp)} =
461 0         0 map { $_ * $ptb } @line;
  0         0  
462             }
463             else {
464 30         201 @stat{qw(size resident share trs lrs drs dtp)} = @line;
465             }
466              
467 30         303 close($fh);
468 30         192 return \%stat;
469             }
470              
471             sub _get_stat {
472 60     60   137 my ( $self, $pid ) = @_;
473 60         102 my $file = $self->{files};
474 60         90 my %stat = ();
475              
476 60 50       2051 open my $fh, '<', "$file->{path}/$pid/$file->{stat}"
477             or return;
478              
479 60         7671 my @line = split /\s+/, <$fh>;
480              
481 60 50       298 if ( @line < 38 ) {
482 0         0 return;
483             }
484              
485             @stat{
486 60         821 qw(
487             cmd state ppid pgrp session ttynr minflt
488             cminflt mayflt cmayflt utime stime cutime cstime
489             prior nice nlwp sttime vsize nswap cnswap
490             cpu
491             )
492             } = @line[ 1 .. 6, 9 .. 19, 21 .. 22, 35 .. 36, 38 ];
493              
494 60         182 my $uptime = $self->_uptime;
495             my ( $d, $h, $m, $s ) =
496 60         676 $self->_calsec( sprintf( '%li', $uptime - $stat{sttime} / 100 ) );
497 60         316 $stat{actime} = "$d:" . sprintf( '%02d:%02d:%02d', $h, $m, $s );
498              
499 60         584 close($fh);
500 60         492 return \%stat;
501             }
502              
503             sub _get_owner {
504 30     30   76 my ( $self, $pid ) = @_;
505 30         55 my $file = $self->{files};
506 30         51 my $owner = "N/a";
507              
508 30 50       1083 open my $fh, '<', "$file->{path}/$pid/$file->{status}"
509             or return;
510              
511 30         1432 while ( my $line = <$fh> ) {
512 240 100       773 if ( $line =~ /^Uid:(?:\s+|\t+)(\d+)/ ) {
513 30   50     3958 $owner = getpwuid($1) || "N/a";
514 30         128 last;
515             }
516             }
517              
518 30         1136 close($fh);
519 30         215 return $owner;
520             }
521              
522             sub _get_cmdline {
523 30     30   78 my ( $self, $pid ) = @_;
524 30         58 my $file = $self->{files};
525              
526 30 50       1052 open my $fh, '<', "$file->{path}/$pid/$file->{cmdline}"
527             or return;
528              
529 30         755 my $cmdline = <$fh>;
530 30         256 close $fh;
531              
532 30 50       123 if ( !defined $cmdline ) {
533 0         0 $cmdline = "N/a";
534             }
535              
536 30         257 $cmdline =~ s/\0/ /g;
537 30         119 $cmdline =~ s/^\s+//;
538 30         189 $cmdline =~ s/\s+$//;
539 30         72 chomp $cmdline;
540 30         131 return $cmdline;
541             }
542              
543             sub _get_wchan {
544 30     30   86 my ( $self, $pid ) = @_;
545 30         72 my $file = $self->{files};
546              
547 30 50       1049 open my $fh, '<', "$file->{path}/$pid/$file->{wchan}"
548             or return;
549              
550 30         2917 my $wchan = <$fh>;
551 30         347 close $fh;
552              
553 30 50       105 if ( !defined $wchan ) {
554 0         0 $wchan = defined;
555             }
556              
557 30         57 chomp $wchan;
558 30         179 return $wchan;
559             }
560              
561             sub _get_io {
562 60     60   125 my ( $self, $pid ) = @_;
563 60         100 my $file = $self->{files};
564 60         106 my %stat = ();
565              
566 60 50       2072 if ( open my $fh, '<', "$file->{path}/$pid/$file->{io}" ) {
567 60         5423 while ( my $line = <$fh> ) {
568 42 50       183 if ( $line =~ /^([a-z_]+):\s+(\d+)/ ) {
569 42         218 $stat{$1} = $2;
570             }
571             }
572              
573 60         672 close($fh);
574             }
575              
576 60         491 return \%stat;
577             }
578              
579             sub _get_fd {
580 30     30   69 my ( $self, $pid ) = @_;
581 30         82 my $file = $self->{files};
582 30         51 my %stat = ();
583              
584 30 50       928 if ( opendir my $dh, "$file->{path}/$pid/$file->{fd}" ) {
585 30         971 foreach my $link ( grep !/^\.+\z/, readdir($dh) ) {
586 141 100       12346 if ( my $target = readlink("$file->{path}/$pid/$file->{fd}/$link") )
587             {
588 18         85 $stat{$pid}{fd}{$link} = $target;
589             }
590             }
591             }
592              
593 30         519 return \%stat;
594             }
595              
596             sub _get_pids {
597 6     6   12 my $self = shift;
598 6         13 my $file = $self->{files};
599              
600 6 50       18 if ( $self->{pids} ) {
601 0         0 return $self->{pids};
602             }
603              
604             opendir my $dh, $file->{path}
605 6 50       242 or die "unable to open directory $file->{path} ($!)";
606 6         687 my @pids = grep /^\d+\z/, readdir $dh;
607 6         104 closedir $dh;
608 6         40 return \@pids;
609             }
610              
611             sub _uptime {
612 66     66   109 my $self = shift;
613 66         123 my $file = $self->{files};
614              
615             my $filename =
616 66 50       222 $file->{path} ? "$file->{path}/$file->{uptime}" : $file->{uptime};
617 66 50       2979 open my $fh, '<', $filename or die "unable to open $filename ($!)";
618 66         1673 my ( $up, $idle ) = split /\s+/, <$fh>;
619 66         864 close($fh);
620 66         385 return $up;
621             }
622              
623             sub _calsec {
624 60     60   121 my $self = shift;
625 60         142 my ( $s, $m, $h, $d ) = ( shift, 0, 0, 0 );
626 60 50 33     187 $s >= 86400 and $d = sprintf( '%i', $s / 86400 ) and $s = $s % 86400;
627 60 50 33     138 $s >= 3600 and $h = sprintf( '%i', $s / 3600 ) and $s = $s % 3600;
628 60 50 33     149 $s >= 60 and $m = sprintf( '%i', $s / 60 ) and $s = $s % 60;
629 60         180 return ( $d, $h, $m, $s );
630             }
631              
632             1;