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   22 use strict;
  3         6  
  3         90  
3 3     3   15 use warnings;
  3         6  
  3         90  
4 3     3   1523 use Time::HiRes 1.9725;
  3         4196  
  3         17  
5 3     3   485 use constant NUMBER => qr/^-{0,1}\d+(?:\.\d+){0,1}\z/;
  3         7  
  3         9069  
6              
7             our $VERSION = '1.5'; # 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       14 my $opts = ref( $_[0] ) ? shift : {@_};
206              
207 3         21 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       12 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         4 foreach my $file ( keys %{ $opts->{files} } ) {
  3         14  
236 0         0 $self{files}{$file} = $opts->{files}->{$file};
237             }
238              
239 3 50       10 if ( $opts->{pages_to_bytes} ) {
240 0         0 $self{pages_to_bytes} = $opts->{pages_to_bytes};
241             }
242              
243 3         19 return bless \%self, $class;
244             }
245              
246             sub init {
247 3     3 1 6 my $self = shift;
248 3         8 $self->{init} = $self->_init;
249             }
250              
251             sub get {
252 3     3 1 14 my $self = shift;
253              
254 3 50       16 if ( !exists $self->{init} ) {
255 0         0 die "there are no initial statistics defined";
256             }
257              
258 3         18 $self->{stats} = $self->_load;
259 3         20 $self->_deltas;
260 3         33 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   6 my $self = shift;
276 3         17 my $file = $self->{files};
277 3         9 my $pids = $self->_get_pids;
278 3         9 my $stats = {};
279              
280 3         15 $stats->{time} = Time::HiRes::gettimeofday();
281              
282 3         9 foreach my $pid (@$pids) {
283 30         89 my $stat = $self->_get_stat($pid);
284              
285 30 50       75 if ( defined $stat ) {
286 30         64 foreach my $key (
287             qw/minflt cminflt mayflt cmayflt utime stime cutime cstime sttime/
288             )
289             {
290 270         615 $stats->{$pid}->{$key} = $stat->{$key};
291             }
292 30         74 $stats->{$pid}->{io} = $self->_get_io($pid);
293             }
294             }
295              
296 3         24 return $stats;
297             }
298              
299             sub _load {
300 3     3   9 my $self = shift;
301 3         9 my $file = $self->{files};
302 3         15 my $uptime = $self->_uptime;
303 3         21 my $pids = $self->_get_pids;
304 3         12 my $stats = {};
305              
306 3         33 $stats->{time} = Time::HiRes::gettimeofday();
307              
308 3         12 PID: foreach my $pid (@$pids) {
309 30         67 foreach my $key (qw/statm stat io owner cmdline wchan fd/) {
310 210         593 my $method = "_get_$key";
311 210         689 my $data = $self->$method($pid);
312              
313 210 50       559 if ( !defined $data ) {
314 0         0 delete $stats->{$pid};
315 0         0 next PID;
316             }
317              
318 210 100 100     875 if ( $key eq "statm" || $key eq "stat" ) {
319 60         306 for my $x ( keys %$data ) {
320 900         1799 $stats->{$pid}->{$x} = $data->{$x};
321             }
322             }
323             else {
324 150         616 $stats->{$pid}->{$key} = $data;
325             }
326             }
327             }
328              
329 3         17 return $stats;
330             }
331              
332             sub _deltas {
333 3     3   8 my $self = shift;
334 3         11 my $istat = $self->{init};
335 3         8 my $lstat = $self->{stats};
336 3         12 my $uptime = $self->_uptime;
337              
338 3 50 33     37 if ( !defined $istat->{time} || !defined $lstat->{time} ) {
339 0         0 die "not defined key found 'time'";
340             }
341              
342 3 50 33     116 if ( $istat->{time} !~ NUMBER || $lstat->{time} !~ NUMBER ) {
343 0         0 die "invalid value for key 'time'";
344             }
345              
346 3         15 my $time = $lstat->{time} - $istat->{time};
347 3         9 $istat->{time} = $lstat->{time};
348 3         11 delete $lstat->{time};
349              
350 3         7 for my $pid ( keys %{$lstat} ) {
  3         23  
351 30         57 my $ipid = $istat->{$pid};
352 30         55 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     130 if ( $ipid && $ipid->{sttime} == $lpid->{sttime} ) {
358 30         52 for my $k (
359             qw(minflt cminflt mayflt cmayflt utime stime cutime cstime))
360             {
361 240 50       506 if ( !defined $ipid->{$k} ) {
362 0         0 die "not defined key found '$k'";
363             }
364 240 50 33     1385 if ( $ipid->{$k} !~ NUMBER || $lpid->{$k} !~ NUMBER ) {
365 0         0 die "invalid value for key '$k'";
366             }
367              
368 240         519 $lpid->{$k} -= $ipid->{$k};
369 240         360 $ipid->{$k} += $lpid->{$k};
370              
371 240 100 66     560 if ( $lpid->{$k} > 0 && $time > 0 ) {
372 8         53 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} / $time );
373             }
374             else {
375 232         829 $lpid->{$k} = sprintf( '%.2f', $lpid->{$k} );
376             }
377             }
378              
379 30         139 $lpid->{ttime} = sprintf( '%.2f', $lpid->{stime} + $lpid->{utime} );
380              
381 30         60 for my $k (
382             qw(rchar wchar syscr syscw read_bytes write_bytes cancelled_write_bytes)
383             )
384             {
385 210 100 66     461 if ( defined $ipid->{io}->{$k} && defined $lpid->{io}->{$k} ) {
386 21 50 33     135 if ( $ipid->{io}->{$k} !~ NUMBER
387             || $lpid->{io}->{$k} !~ NUMBER )
388             {
389 0         0 die "invalid value for io key '$k'";
390             }
391 21         53 $lpid->{io}->{$k} -= $ipid->{io}->{$k};
392 21         39 $ipid->{io}->{$k} += $lpid->{io}->{$k};
393 21 100 66     86 if ( $lpid->{io}->{$k} > 0 && $time > 0 ) {
394             $lpid->{io}->{$k} =
395 6         40 sprintf( '%.2f', $lpid->{io}->{$k} / $time );
396             }
397             else {
398             $lpid->{io}->{$k} =
399 15         64 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   76 my ( $self, $pid ) = @_;
445 30         49 my $file = $self->{files};
446 30         53 my %stat = ();
447              
448 30 50       1161 open my $fh, '<', "$file->{path}/$pid/$file->{statm}"
449             or return;
450              
451 30         626 my @line = split /\s+/, <$fh>;
452              
453 30 50       117 if ( @line < 7 ) {
454 0         0 return;
455             }
456              
457 30   33     139 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         239 @stat{qw(size resident share trs lrs drs dtp)} = @line;
465             }
466              
467 30         277 close($fh);
468 30         185 return \%stat;
469             }
470              
471             sub _get_stat {
472 60     60   134 my ( $self, $pid ) = @_;
473 60         91 my $file = $self->{files};
474 60         102 my %stat = ();
475              
476 60 50       2049 open my $fh, '<', "$file->{path}/$pid/$file->{stat}"
477             or return;
478              
479 60         8210 my @line = split /\s+/, <$fh>;
480              
481 60 50       280 if ( @line < 38 ) {
482 0         0 return;
483             }
484              
485             @stat{
486 60         825 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         194 my $uptime = $self->_uptime;
495             my ( $d, $h, $m, $s ) =
496 60         942 $self->_calsec( sprintf( '%li', $uptime - $stat{sttime} / 100 ) );
497 60         282 $stat{actime} = "$d:" . sprintf( '%02d:%02d:%02d', $h, $m, $s );
498              
499 60         535 close($fh);
500 60         464 return \%stat;
501             }
502              
503             sub _get_owner {
504 30     30   74 my ( $self, $pid ) = @_;
505 30         53 my $file = $self->{files};
506 30         49 my $owner = "N/a";
507              
508 30 50       1039 open my $fh, '<', "$file->{path}/$pid/$file->{status}"
509             or return;
510              
511 30         1272 while ( my $line = <$fh> ) {
512 240 100       706 if ( $line =~ /^Uid:(?:\s+|\t+)(\d+)/ ) {
513 30   50     3844 $owner = getpwuid($1) || "N/a";
514 30         128 last;
515             }
516             }
517              
518 30         1113 close($fh);
519 30         186 return $owner;
520             }
521              
522             sub _get_cmdline {
523 30     30   72 my ( $self, $pid ) = @_;
524 30         57 my $file = $self->{files};
525              
526 30 50       1009 open my $fh, '<', "$file->{path}/$pid/$file->{cmdline}"
527             or return;
528              
529 30         691 my $cmdline = <$fh>;
530 30         251 close $fh;
531              
532 30 50       103 if ( !defined $cmdline ) {
533 0         0 $cmdline = "N/a";
534             }
535              
536 30         259 $cmdline =~ s/\0/ /g;
537 30         110 $cmdline =~ s/^\s+//;
538 30         188 $cmdline =~ s/\s+$//;
539 30         75 chomp $cmdline;
540 30         130 return $cmdline;
541             }
542              
543             sub _get_wchan {
544 30     30   66 my ( $self, $pid ) = @_;
545 30         51 my $file = $self->{files};
546              
547 30 50       974 open my $fh, '<', "$file->{path}/$pid/$file->{wchan}"
548             or return;
549              
550 30         2685 my $wchan = <$fh>;
551 30         390 close $fh;
552              
553 30 50       110 if ( !defined $wchan ) {
554 0         0 $wchan = defined;
555             }
556              
557 30         55 chomp $wchan;
558 30         176 return $wchan;
559             }
560              
561             sub _get_io {
562 60     60   116 my ( $self, $pid ) = @_;
563 60         96 my $file = $self->{files};
564 60         92 my %stat = ();
565              
566 60 50       2016 if ( open my $fh, '<', "$file->{path}/$pid/$file->{io}" ) {
567 60         4967 while ( my $line = <$fh> ) {
568 42 50       198 if ( $line =~ /^([a-z_]+):\s+(\d+)/ ) {
569 42         222 $stat{$1} = $2;
570             }
571             }
572              
573 60         663 close($fh);
574             }
575              
576 60         537 return \%stat;
577             }
578              
579             sub _get_fd {
580 30     30   71 my ( $self, $pid ) = @_;
581 30         53 my $file = $self->{files};
582 30         51 my %stat = ();
583              
584 30 50       896 if ( opendir my $dh, "$file->{path}/$pid/$file->{fd}" ) {
585 30         908 foreach my $link ( grep !/^\.+\z/, readdir($dh) ) {
586 141 100       11226 if ( my $target = readlink("$file->{path}/$pid/$file->{fd}/$link") )
587             {
588 18         75 $stat{$pid}{fd}{$link} = $target;
589             }
590             }
591             }
592              
593 30         496 return \%stat;
594             }
595              
596             sub _get_pids {
597 6     6   13 my $self = shift;
598 6         15 my $file = $self->{files};
599              
600 6 50       21 if ( $self->{pids} ) {
601 0         0 return $self->{pids};
602             }
603              
604             opendir my $dh, $file->{path}
605 6 50       264 or die "unable to open directory $file->{path} ($!)";
606 6         716 my @pids = grep /^\d+\z/, readdir $dh;
607 6         112 closedir $dh;
608 6         43 return \@pids;
609             }
610              
611             sub _uptime {
612 66     66   120 my $self = shift;
613 66         121 my $file = $self->{files};
614              
615             my $filename =
616 66 50       233 $file->{path} ? "$file->{path}/$file->{uptime}" : $file->{uptime};
617 66 50       2665 open my $fh, '<', $filename or die "unable to open $filename ($!)";
618 66         1643 my ( $up, $idle ) = split /\s+/, <$fh>;
619 66         862 close($fh);
620 66         400 return $up;
621             }
622              
623             sub _calsec {
624 60     60   113 my $self = shift;
625 60         149 my ( $s, $m, $h, $d ) = ( shift, 0, 0, 0 );
626 60 50 33     196 $s >= 86400 and $d = sprintf( '%i', $s / 86400 ) and $s = $s % 86400;
627 60 50 33     136 $s >= 3600 and $h = sprintf( '%i', $s / 3600 ) and $s = $s % 3600;
628 60 100 66     177 $s >= 60 and $m = sprintf( '%i', $s / 60 ) and $s = $s % 60;
629 60         191 return ( $d, $h, $m, $s );
630             }
631              
632             1;