File Coverage

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