File Coverage

blib/lib/System/Process.pm
Criterion Covered Total %
statement 153 170 90.0
branch 36 56 64.2
condition 12 15 80.0
subroutine 26 28 92.8
pod 1 1 100.0
total 228 270 84.4


line stmt bran cond sub pod time code
1             package System::Process;
2              
3             =head1 NAME
4              
5             System::Process
6              
7             =head1 DESCRIPTION
8              
9             Manipulate system process as perl object. This is a simple wrapper over ps on
10             *nix systems. For Windows systems - under construction.
11              
12             =head1 SYNOPSIS
13              
14             use System::Process;
15              
16             my $process_object = pidinfo pid => 5321;
17             if ($process_object) {
18             print $process_object->command();
19             }
20              
21             =head1 METHODS
22              
23             =over
24              
25             =item B
26              
27             pidinfo(%)
28              
29             params is hash (pid=>4444) || (file=>'/path/to/pid/file' || pattern => 'my\scool\sname')
30              
31             returns System::Process::Unit object that supports following methods if pid or file option specified.
32             If pattern option specified - returns arrayref of System::Process objects.
33              
34             =back
35              
36             =head1 PROCESS OBJECT METHODS
37              
38             If your ps uax header simillar to my:
39             USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
40             You will get these methods:
41              
42             =over
43              
44             =item B
45              
46             Returns %CPU.
47              
48             =item B
49              
50             Returns TIME.
51              
52             =item B
53              
54             Returns STAT.
55              
56             =item B
57              
58             Returns TTY.
59              
60             =item B
61              
62             Returns USER
63              
64             =item B
65              
66             Returns %MEM
67              
68             =item B
69              
70             Returns %RSS
71              
72             =item B
73              
74             Returns VSZ
75              
76             =item B
77              
78             Returns COMMAND
79              
80             =item B
81              
82             Returns START
83              
84             =item B
85              
86             Returns PID
87              
88             =back
89              
90             Anyway, you will get methods named as lowercase header values.
91              
92             =over
93              
94             =item B
95              
96             Checks possibility of 'kill' process.
97             Returns 1 if possible
98              
99             =item B
100              
101             Kills a process with specified signal
102             $process_object->kill(9);
103              
104             =item B
105              
106             Refreshes data for current pid.
107              
108             =item B
109              
110             Writes pid to desired file.
111             $process_object->write_pid('/my/pid/path');
112              
113             =item B
114              
115             Returns true if process alive.
116              
117             =back
118              
119             =cut
120              
121              
122 2     2   101598 use strict;
  2         11  
  2         59  
123 2     2   11 use warnings;
  2         3  
  2         55  
124              
125 2     2   10 no warnings qw/once/;
  2         4  
  2         62  
126              
127 2     2   12 use Carp;
  2         3  
  2         177  
128 2     2   1060 use POSIX;
  2         14867  
  2         12  
129              
130             our $VERSION = '0.20';
131             our $ABSTRACT = "Simple OO wrapper over ps.";
132             our $TEST = 0;
133              
134             sub import {
135 2     2   20 my ($class, $import) = @_;
136              
137 2 100 66     15 if ($import && $import =~ m/test/s) {
138 1         1 $TEST = 1;
139 1         2 $System::Process::Unit::TEST = 1;
140             }
141              
142 2 50       8 if ($^O =~ m/MSWin32/is) {
143 0         0 croak "Not implemented for windows yet.";
144             }
145 2         45 *{main::pidinfo} = \&pidinfo;
146             }
147              
148              
149             sub pidinfo {
150 13     13 1 3661 my (%params, $pid);
151              
152 13 50       52 if (scalar @_ & 1) {
153 0         0 %params = (
154             pid => shift
155             );
156             }
157             else {
158 13         61 %params = @_;
159             }
160            
161 13 100 100     133 if ($params{pid} && $params{file}) {
162 1         90 croak 'Choose one';
163             }
164              
165 12 100 100     54 if (!$params{pid} && !$params{file} && !$params{pattern}) {
      100        
166 1         71 croak 'Missing pid or file param';
167             }
168              
169 11 100       36 if ($params{file}) {
    100          
170 2 50       33 return undef unless -r $params{file};
171              
172 2         54 open PID, $params{file};
173 2         53 $pid = ;
174 2         19 close PID;
175 2 100       13 return undef unless $pid;
176 1         4 chomp $pid;
177             }
178             elsif ($params{pattern}) {
179 1         11 return System::Process::Unit->new_bundle($params{pattern});
180              
181             }
182             else {
183 8         14 $pid = $params{pid};
184             }
185            
186 9 100       95 if ($pid !~ m/^\d+$/s) {
187 3         363 croak "PID must be a digits sequence";
188             }
189            
190 6 50       21 if ($pid > POSIX::UINT_MAX) {
191 0         0 croak "PID value ($pid) is too big";
192             }
193              
194 6         23 return System::Process::Unit->new($pid);
195             }
196              
197              
198             1;
199              
200             package System::Process::Unit;
201 2     2   6582 use strict;
  2         4  
  2         50  
202 2     2   18 use warnings;
  2         5  
  2         63  
203 2     2   11 use Carp;
  2         3  
  2         408  
204              
205             our $TEST = 0;
206             our $AUTOLOAD;
207              
208             my @allowed_subs = qw/
209             cpu
210             time
211             stat
212             tty
213             user
214             mem
215             rss
216             vsz
217             command
218             start
219             /;
220              
221             my $hal;
222             %$hal = map {(__PACKAGE__ . '::' . $_, 1)} @allowed_subs;
223              
224              
225             sub AUTOLOAD {
226 4     4   61 my $program = $AUTOLOAD;
227              
228 4 50       29 croak "Undefined subroutine $program" unless $hal->{$program};
229              
230             my $sub = sub {
231 6     6   22 my $self = shift;
232 6         18 return $self->internal_info($program);
233 4         31 };
234 2     2   14 no strict 'refs';
  2         4  
  2         102  
235 4         10 *{$program} = $sub;
  4         31  
236 2     2   17 use strict 'refs';
  2         4  
  2         2601  
237 4         31 goto &$sub;
238             }
239              
240              
241             sub new {
242 6     6   16 my ($class, $pid) = @_;
243              
244 6         13 my $self = {};
245 6         18 bless $self, $class;
246 6         17 $self->pid($pid);
247 6 50       19 unless ($self->process_info()) {
248 0         0 return undef;
249             }
250              
251 6         195 return $self;
252             }
253              
254              
255             sub new_bundle {
256 1     1   3 my ($class, $pattern) = @_;
257              
258 1         5 return get_bundle($class, $pattern);
259             }
260              
261              
262             sub refresh {
263 1     1   3 my $self = shift;
264 1         4 my $pid = $self->pid();
265              
266 1         12 $self = System::Process::pidinfo(pid => $pid);
267 1         22 return 1;
268             }
269              
270              
271             sub process_info {
272 6     6   10 my $self = shift;
273              
274 6         12 my $command = 'ps u ' . $self->pid();
275 6         36878 my @res = `$command`;
276              
277 6         101 my $parse_result;
278             # BusyBox ps ouputs all processes only. Do postfiltering.
279 6         55 my $header = shift @res;
280 6         84 for my $r (@res) {
281 6         68 my $out = parse_output($header, $r);
282              
283 6 50 33     79 if ( $out && $out->{pid} == $self->pid() ) {
284 6         17 $parse_result = $out;
285 6         14 last;
286             }
287             }
288              
289             # return $self->parse_output(@res);
290              
291 6 50       15 return $parse_result unless $parse_result;
292              
293 6         19 $self->internal_info($parse_result);
294 6         48 return 1;
295             }
296              
297              
298             sub get_bundle {
299 1     1   3 my ($class, $pattern) = @_;
300              
301 1         2 my $command = qq/ps uax/;
302 1         2 my @res;
303              
304 1 50       4 if ($TEST) {
305 1         4 @res = (
306             'PID CPU USER COMMAND',
307             '65532 123 test_user blahblahblah',
308             '65533 123 test_user blahblahblah',
309             '65531 123 root rm -rf',
310             '65534 123 test_user blahblahblah',
311             );
312             }
313             else {
314 0         0 @res = `$command`;
315             }
316              
317 1         3 my $header = shift @res;
318              
319             @res = grep {
320 4 100       20 if (m/$pattern/s) {
321 3         8 1;
322             }
323             else {
324 1         3 0;
325             }
326             } map {
327 1         3 s/\s*$//;
  4         24  
328 4         10 $_;
329             } @res;
330              
331 1 50       4 return [] unless scalar @res;
332              
333 1         4 my $bundle = [];
334              
335 1         3 for my $r (@res) {
336 3         8 my $res = parse_output($header, $r);
337             my $object = {
338             pid => $res->{pid},
339 3         10 _procinfo => $res,
340             };
341              
342 3         7 bless $object, $class;
343 3         6 push @$bundle, $object;
344             }
345 1         6 return $bundle;
346             }
347              
348              
349             sub write_pid {
350 0     0   0 my ($self, $file) = @_;
351              
352 0 0       0 return 0 unless $self->pid();
353 0 0       0 open PID, '>', $file or return 0;
354              
355 0 0       0 print PID $self->pid() or return 0;
356              
357 0         0 close PID;
358 0         0 return 1;
359             }
360              
361              
362             sub parse_n_generate {
363 1     1   31 my ($self, @params) = @_;
364              
365 1         14 my $res = parse_output(@params);
366 1         4 $self->internal_info($res);
367 1         3 return 1;
368             }
369              
370              
371             sub parse_output {
372 10 50   10   47 if (ref $_[0] eq __PACKAGE__) {
373 0         0 shift;
374             }
375              
376 10         64 my (@out) = @_;
377              
378 10 50       36 return 0 unless $out[1];
379              
380 10         236 my @header = grep { length } split /\s+/, $out[0];
  82         166  
381 10         118 my @values = grep { length } split /\s+/, $out[1];
  102         202  
382              
383 10         34 my $res;
384             my $last_key;
385 10         62 for (0 .. $#values) {
386 88 100       201 unless (@header) {
387 7         18 unshift @values, $res->{$last_key};
388 7         38 $res->{$last_key} = join ' ', @values;
389 7         19 last;
390             }
391             else {
392 81         151 my $k = $last_key = shift @header;
393 81         120 my $v = shift @values;
394 81         528 $res->{$k} = $v;
395             }
396             }
397              
398 10         57 for my $key (keys %$res) {
399 81         160 my $k2 = lc $key;
400 81         187 $k2 =~ s/[^A-Za-z]//gs;
401 81         424 $res->{$k2} = delete $res->{$key};
402             }
403            
404 10         44 return $res;
405             }
406              
407              
408             sub pid {
409 24     24   2013 my ($self, $pid) = @_;
410              
411 24 100       51 if ($pid) {
412 6         21 $self->{pid} = $pid;
413             }
414 24         364 return $self->{pid};
415             }
416              
417              
418             sub internal_info {
419 13     13   33 my ($self, $param) = @_;
420              
421 13 100       36 if (ref $param eq 'HASH') {
422 7         43 $self->{_procinfo} = $param;
423 7         19 return 1;
424             }
425             else {
426 6         39 $param =~ s|^.+::||;
427 6         55 return $self->{_procinfo}->{$param};
428             }
429             }
430              
431              
432             sub cankill {
433 1     1   21 my $self = shift;
434              
435 1         13 my $pid = $self->pid();
436              
437 1 50       27 if (kill 0, $pid) {
438 1         38 return 1;
439             }
440 0         0 return 0;
441             }
442              
443              
444             sub is_alive {
445 0     0   0 my ($self) = @_;
446            
447 0         0 $self->refresh();
448 0         0 return $self->cankill();
449             }
450              
451             sub kill {
452 1     1   28 my ($self, $signal) = @_;
453              
454 1 50       17 if (!defined $signal) {
455 0         0 croak 'Signal must be specified';
456             }
457 1         45 return kill $signal, $self->pid();
458             }
459              
460              
461             sub DESTROY {
462 9     9   1003567 my $self = shift;
463 9         522 undef $self;
464             }
465              
466              
467             1;
468              
469             __END__;