File Coverage

blib/lib/Linux/Info/Processes.pm
Criterion Covered Total %
statement 175 217 80.6
branch 45 84 53.5
condition 20 43 46.5
subroutine 20 21 95.2
pod 4 4 100.0
total 264 369 71.5


line stmt bran cond sub pod time code
1             package Linux::Info::Processes;
2 3     3   19 use strict;
  3         7  
  3         85  
3 3     3   16 use warnings;
  3         7  
  3         106  
4 3     3   1529 use Time::HiRes 1.9725;
  3         3769  
  3         33  
5 3     3   435 use constant NUMBER => qr/^-{0,1}\d+(?:\.\d+){0,1}\z/;
  3         7  
  3         8185  
6              
7             our $VERSION = '1.3'; # 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 11 my $class = shift;
205 3 50       16 my $opts = ref( $_[0] ) ? shift : {@_};
206              
207 3         28 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       23 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         8 foreach my $file ( keys %{ $opts->{files} } ) {
  3         16  
236 0         0 $self{files}{$file} = $opts->{files}->{$file};
237             }
238              
239 3 50       14 if ( $opts->{pages_to_bytes} ) {
240 0         0 $self{pages_to_bytes} = $opts->{pages_to_bytes};
241             }
242              
243 3         24 return bless \%self, $class;
244             }
245              
246             sub init {
247 3     3 1 7 my $self = shift;
248 3         11 $self->{init} = $self->_init;
249             }
250              
251             sub get {
252 3     3 1 14 my $self = shift;
253              
254 3 50       21 if ( !exists $self->{init} ) {
255 0         0 die "there are no initial statistics defined";
256             }
257              
258 3         20 $self->{stats} = $self->_load;
259 3         20 $self->_deltas;
260 3         27 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   7 my $self = shift;
276 3         23 my $file = $self->{files};
277 3         11 my $pids = $self->_get_pids;
278 3         9 my $stats = {};
279              
280 3         26 $stats->{time} = Time::HiRes::gettimeofday();
281              
282 3         12 foreach my $pid (@$pids) {
283 30         88 my $stat = $self->_get_stat($pid);
284              
285 30 50       96 if ( defined $stat ) {
286 30         75 foreach my $key (
287             qw/minflt cminflt mayflt cmayflt utime stime cutime cstime sttime/
288             )
289             {
290 270         688 $stats->{$pid}->{$key} = $stat->{$key};
291             }
292 30         81 $stats->{$pid}->{io} = $self->_get_io($pid);
293             }
294             }
295              
296 3         35 return $stats;
297             }
298              
299             sub _load {
300 3     3   11 my $self = shift;
301 3         13 my $file = $self->{files};
302 3         17 my $uptime = $self->_uptime;
303 3         22 my $pids = $self->_get_pids;
304 3         15 my $stats = {};
305              
306 3         27 $stats->{time} = Time::HiRes::gettimeofday();
307              
308 3         14 PID: foreach my $pid (@$pids) {
309 30         75 foreach my $key (qw/statm stat io owner cmdline wchan fd/) {
310 210         571 my $method = "_get_$key";
311 210         854 my $data = $self->$method($pid);
312              
313 210 50       710 if ( !defined $data ) {
314 0         0 delete $stats->{$pid};
315 0         0 next PID;
316             }
317              
318 210 100 100     1137 if ( $key eq "statm" || $key eq "stat" ) {
319 60         334 for my $x ( keys %$data ) {
320 900         2557 $stats->{$pid}->{$x} = $data->{$x};
321             }
322             }
323             else {
324 150         663 $stats->{$pid}->{$key} = $data;
325             }
326             }
327             }
328              
329 3         17 return $stats;
330             }
331              
332             sub _deltas {
333 3     3   11 my $self = shift;
334 3         10 my $istat = $self->{init};
335 3         9 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     88 if ( $istat->{time} !~ NUMBER || $lstat->{time} !~ NUMBER ) {
343 0         0 die "invalid value for key 'time'";
344             }
345              
346 3         12 my $time = $lstat->{time} - $istat->{time};
347 3         11 $istat->{time} = $lstat->{time};
348 3         10 delete $lstat->{time};
349              
350 3         10 for my $pid ( keys %{$lstat} ) {
  3         20  
351 30         69 my $ipid = $istat->{$pid};
352 30         60 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     157 if ( $ipid && $ipid->{sttime} == $lpid->{sttime} ) {
358 30         68 for my $k (
359             qw(minflt cminflt mayflt cmayflt utime stime cutime cstime))
360             {
361 240 50       690 if ( !defined $ipid->{$k} ) {
362 0         0 die "not defined key found '$k'";
363             }
364 240 50 33     1926 if ( $ipid->{$k} !~ NUMBER || $lpid->{$k} !~ NUMBER ) {
365 0         0 die "invalid value for key '$k'";
366             }
367              
368 240         720 $lpid->{$k} -= $ipid->{$k};
369 240         522 $ipid->{$k} += $lpid->{$k};
370              
371 240 100 66     806 if ( $lpid->{$k} > 0 && $time > 0 ) {
372 5         39 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} / $time );
373             }
374             else {
375 235         1254 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} );
376             }
377             }
378              
379 30         176 $lpid->{ttime} = sprintf( '%.2f', $lpid->{stime} + $lpid->{utime} );
380              
381 30         77 for my $k (
382             qw(rchar wchar syscr syscw read_bytes write_bytes cancelled_write_bytes)
383             )
384             {
385 210 100 66     695 if ( defined $ipid->{io}->{$k} && defined $lpid->{io}->{$k} ) {
386 21 50 33     209 if ( $ipid->{io}->{$k} !~ NUMBER
387             || $lpid->{io}->{$k} !~ NUMBER )
388             {
389 0         0 die "invalid value for io key '$k'";
390             }
391 21         66 $lpid->{io}->{$k} -= $ipid->{io}->{$k};
392 21         50 $ipid->{io}->{$k} += $lpid->{io}->{$k};
393 21 100 66     86 if ( $lpid->{io}->{$k} > 0 && $time > 0 ) {
394             $lpid->{io}->{$k} =
395 6         48 sprintf( '%.2f', $lpid->{io}->{$k} / $time );
396             }
397             else {
398             $lpid->{io}->{$k} =
399 15         81 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   100 my ( $self, $pid ) = @_;
445 30         77 my $file = $self->{files};
446 30         78 my %stat = ();
447              
448 30 50       763 open my $fh, '<', "$file->{path}/$pid/$file->{statm}"
449             or return;
450              
451 30         467 my @line = split /\s+/, <$fh>;
452              
453 30 50       122 if ( @line < 7 ) {
454 0         0 return;
455             }
456              
457 30   33     175 my $ptb = $self->{pages_to_bytes} || $PAGES_TO_BYTES;
458              
459 30 50       96 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         194 @stat{qw(size resident share trs lrs drs dtp)} = @line;
465             }
466              
467 30         369 close($fh);
468 30         169 return \%stat;
469             }
470              
471             sub _get_stat {
472 60     60   159 my ( $self, $pid ) = @_;
473 60         132 my $file = $self->{files};
474 60         147 my %stat = ();
475              
476 60 50       1272 open my $fh, '<', "$file->{path}/$pid/$file->{stat}"
477             or return;
478              
479 60         2784 my @line = split /\s+/, <$fh>;
480              
481 60 50       246 if ( @line < 38 ) {
482 0         0 return;
483             }
484              
485             @stat{
486 60         773 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         203 my $uptime = $self->_uptime;
495             my ( $d, $h, $m, $s ) =
496 60         580 $self->_calsec( sprintf( '%li', $uptime - $stat{sttime} / 100 ) );
497 60         389 $stat{actime} = "$d:" . sprintf( '%02d:%02d:%02d', $h, $m, $s );
498              
499 60         314 close($fh);
500 60         430 return \%stat;
501             }
502              
503             sub _get_owner {
504 30     30   90 my ( $self, $pid ) = @_;
505 30         77 my $file = $self->{files};
506 30         60 my $owner = "N/a";
507              
508 30 50       695 open my $fh, '<', "$file->{path}/$pid/$file->{status}"
509             or return;
510              
511 30         862 while ( my $line = <$fh> ) {
512 240 100       1038 if ( $line =~ /^Uid:(?:\s+|\t+)(\d+)/ ) {
513 30   50     3218 $owner = getpwuid($1) || "N/a";
514 30         218 last;
515             }
516             }
517              
518 30         1012 close($fh);
519 30         199 return $owner;
520             }
521              
522             sub _get_cmdline {
523 30     30   87 my ( $self, $pid ) = @_;
524 30         70 my $file = $self->{files};
525              
526 30 50       743 open my $fh, '<', "$file->{path}/$pid/$file->{cmdline}"
527             or return;
528              
529 30         426 my $cmdline = <$fh>;
530 30         159 close $fh;
531              
532 30 50       101 if ( !defined $cmdline ) {
533 0         0 $cmdline = "N/a";
534             }
535              
536 30         250 $cmdline =~ s/\0/ /g;
537 30         130 $cmdline =~ s/^\s+//;
538 30         214 $cmdline =~ s/\s+$//;
539 30         85 chomp $cmdline;
540 30         152 return $cmdline;
541             }
542              
543             sub _get_wchan {
544 30     30   86 my ( $self, $pid ) = @_;
545 30         84 my $file = $self->{files};
546              
547 30 50       715 open my $fh, '<', "$file->{path}/$pid/$file->{wchan}"
548             or return;
549              
550 30         570 my $wchan = <$fh>;
551 30         172 close $fh;
552              
553 30 50       101 if ( !defined $wchan ) {
554 0         0 $wchan = defined;
555             }
556              
557 30         59 chomp $wchan;
558 30         163 return $wchan;
559             }
560              
561             sub _get_io {
562 60     60   159 my ( $self, $pid ) = @_;
563 60         125 my $file = $self->{files};
564 60         134 my %stat = ();
565              
566 60 50       1403 if ( open my $fh, '<', "$file->{path}/$pid/$file->{io}" ) {
567 60         1024 while ( my $line = <$fh> ) {
568 42 50       202 if ( $line =~ /^([a-z_]+):\s+(\d+)/ ) {
569 42         257 $stat{$1} = $2;
570             }
571             }
572              
573 60         409 close($fh);
574             }
575              
576 60         389 return \%stat;
577             }
578              
579             sub _get_fd {
580 30     30   87 my ( $self, $pid ) = @_;
581 30         70 my $file = $self->{files};
582 30         79 my %stat = ();
583              
584 30 50       711 if ( opendir my $dh, "$file->{path}/$pid/$file->{fd}" ) {
585 30         648 foreach my $link ( grep !/^\.+\z/, readdir($dh) ) {
586 147 100       1947 if ( my $target = readlink("$file->{path}/$pid/$file->{fd}/$link") )
587             {
588 24         94 $stat{$pid}{fd}{$link} = $target;
589             }
590             }
591             }
592              
593 30         283 return \%stat;
594             }
595              
596             sub _get_pids {
597 6     6   16 my $self = shift;
598 6         18 my $file = $self->{files};
599              
600 6 50       24 if ( $self->{pids} ) {
601 0         0 return $self->{pids};
602             }
603              
604             opendir my $dh, $file->{path}
605 6 50       308 or die "unable to open directory $file->{path} ($!)";
606 6         558 my @pids = grep /^\d+\z/, readdir $dh;
607 6         82 closedir $dh;
608 6         36 return \@pids;
609             }
610              
611             sub _uptime {
612 66     66   133 my $self = shift;
613 66         141 my $file = $self->{files};
614              
615             my $filename =
616 66 50       258 $file->{path} ? "$file->{path}/$file->{uptime}" : $file->{uptime};
617 66 50       1738 open my $fh, '<', $filename or die "unable to open $filename ($!)";
618 66         1113 my ( $up, $idle ) = split /\s+/, <$fh>;
619 66         736 close($fh);
620 66         374 return $up;
621             }
622              
623             sub _calsec {
624 60     60   146 my $self = shift;
625 60         164 my ( $s, $m, $h, $d ) = ( shift, 0, 0, 0 );
626 60 50 33     290 $s >= 86400 and $d = sprintf( '%i', $s / 86400 ) and $s = $s % 86400;
627 60 50 33     178 $s >= 3600 and $h = sprintf( '%i', $s / 3600 ) and $s = $s % 3600;
628 60 100 66     238 $s >= 60 and $m = sprintf( '%i', $s / 60 ) and $s = $s % 60;
629 60         227 return ( $d, $h, $m, $s );
630             }
631              
632             1;