File Coverage

blib/lib/System/Process.pm
Criterion Covered Total %
statement 141 162 87.0
branch 35 54 64.8
condition 9 12 75.0
subroutine 25 28 89.2
pod 1 1 100.0
total 211 257 82.1


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   40783 use strict;
  2         3  
  2         61  
123 2     2   7 use warnings;
  2         2  
  2         50  
124              
125 2     2   6 no warnings qw/once/;
  2         5  
  2         66  
126              
127 2     2   8 use Carp;
  2         2  
  2         106  
128 2     2   966 use POSIX;
  2         10382  
  2         10  
129              
130             our $VERSION = 0.18;
131             our $ABSTRACT = "Simple OO wrapper over ps.";
132             our $TEST = 0;
133              
134             sub import {
135 2     2   16 my ($class, $import) = @_;
136              
137 2 100 66     14 if ($import && $import =~ m/test/s) {
138 1         2 $TEST = 1;
139 1         1 $System::Process::Unit::TEST = 1;
140             }
141              
142 2 50       10 if ($^O =~ m/MSWin32/is) {
143 0         0 croak "Not implemented for windows yet.";
144             }
145 2         40 *{main::pidinfo} = \&pidinfo;
146             }
147              
148              
149             sub pidinfo {
150 12     12 1 2388 my (%params, $pid);
151              
152 12 50       46 if (scalar @_ & 1) {
153 0         0 %params = (
154             pid => shift
155             );
156             }
157             else {
158 12         48 %params = @_;
159             }
160            
161 12 100 100     79 if ($params{pid} && $params{file}) {
162 1         92 croak 'Choose one';
163             }
164              
165 11 100 66     44 if (!$params{pid} && !$params{file} && !$params{pattern}) {
      66        
166 1         73 croak 'Missing pid or file param';
167             }
168              
169 10 100       34 if ($params{file}) {
    100          
170 2 50       46 return undef unless -r $params{file};
171              
172 2         49 open PID, $params{file};
173 2         28 $pid = ;
174 2         11 close PID;
175 2 100       10 return undef unless $pid;
176 1         4 chomp $pid;
177             }
178             elsif ($params{pattern}) {
179 1         9 return System::Process::Unit->new_bundle($params{pattern});
180              
181             }
182             else {
183 7         11 $pid = $params{pid};
184             }
185            
186 8 100       60 if ($pid !~ m/^\d+$/s) {
187 3         331 croak "PID must be a digits sequence";
188             }
189            
190 5 50       18 if ($pid > POSIX::UINT_MAX) {
191 0         0 croak "PID value ($pid) is too big";
192             }
193              
194 5         27 return System::Process::Unit->new($pid);
195             }
196              
197              
198             1;
199              
200             package System::Process::Unit;
201 2     2   4920 use strict;
  2         3  
  2         66  
202 2     2   7 use warnings;
  2         3  
  2         46  
203 2     2   7 use Carp;
  2         3  
  2         404  
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   37 my $program = $AUTOLOAD;
227              
228 4 50       18 croak "Undefined subroutine $program" unless $hal->{$program};
229              
230             my $sub = sub {
231 6     6   13 my $self = shift;
232 6         13 return $self->internal_info($program);
233 4         19 };
234 2     2   11 no strict 'refs';
  2         2  
  2         76  
235 4         6 *{$program} = $sub;
  4         20  
236 2     2   17 use strict 'refs';
  2         2  
  2         2051  
237 4         17 goto &$sub;
238             }
239              
240              
241             sub new {
242 5     5   12 my ($class, $pid) = @_;
243              
244 5         9 my $self = {};
245 5         26 bless $self, $class;
246 5         17 $self->pid($pid);
247 5 50       14 unless ($self->process_info()) {
248 0         0 return undef;
249             }
250              
251 5         68 return $self;
252             }
253              
254              
255             sub new_bundle {
256 1     1   1 my ($class, $pattern) = @_;
257              
258 1         3 return get_bundle($class, $pattern);
259             }
260              
261              
262             sub refresh {
263 0     0   0 my $self = shift;
264 0         0 my $pid = $self->pid();
265              
266 0         0 $self = System::Process::pidinfo(pid => $pid);
267 0         0 return 1;
268             }
269              
270              
271             sub process_info {
272 5     5   11 my $self = shift;
273              
274 5         9 my $command = 'ps u ' . $self->pid();
275 5         26418 my @res = `$command`;
276 5         83 my $parse_result = parse_output(@res);
277              
278             # return $self->parse_output(@res);
279              
280 5 50       20 return $parse_result unless $parse_result;
281              
282 5         29 $self->internal_info($parse_result);
283 5         31 return 1;
284             }
285              
286              
287             sub get_bundle {
288 1     1   2 my ($class, $pattern) = @_;
289              
290 1         2 my $command = qq/ps uax/;
291 1         23 my @res;
292              
293 1 50       2 if ($TEST) {
294 1         4 @res = (
295             'PID CPU USER COMMAND',
296             '65532 123 test_user blahblahblah',
297             '65533 123 test_user blahblahblah',
298             '65531 123 root rm -rf',
299             '65534 123 test_user blahblahblah',
300             );
301             }
302             else {
303 0         0 @res = `$command`;
304             }
305              
306 1         1 my $header = shift @res;
307              
308             @res = grep {
309 4 100       21 if (m/$pattern/s) {
  4         21  
310 3         7 1;
311             }
312             else {
313 1         2 0;
314             }
315             } map {
316 1         2 s/\s*$//;
317 4         6 $_;
318             } @res;
319              
320 1 50       3 return [] unless scalar @res;
321              
322 1         1 my $bundle = [];
323              
324 1         3 for my $r (@res) {
325 3         5 my $res = parse_output($header, $r);
326 3         7 my $object = {
327             pid => $res->{pid},
328             _procinfo => $res,
329             };
330              
331 3         4 bless $object, $class;
332 3         4 push @$bundle, $object;
333             }
334 1         5 return $bundle;
335             }
336              
337              
338             sub write_pid {
339 0     0   0 my ($self, $file) = @_;
340              
341 0 0       0 return 0 unless $self->pid();
342 0 0       0 open PID, '>', $file or return 0;
343              
344 0 0       0 print PID $self->pid() or return 0;
345              
346 0         0 close PID;
347 0         0 return 1;
348             }
349              
350              
351             sub parse_n_generate {
352 1     1   10 my ($self, @params) = @_;
353              
354 1         3 my $res = parse_output(@params);
355 1         5 $self->internal_info($res);
356 1         3 return 1;
357             }
358              
359              
360             sub parse_output {
361 9 50   9   39 if (ref $_[0] eq __PACKAGE__) {
362 0         0 shift;
363             }
364              
365 9         25 my (@out) = @_;
366              
367 9 50       26 return 0 unless $out[1];
368              
369 9         59 my @header = split /\s+/, $out[0];
370 9         47 my @values = split /\s+/, $out[1];
371 9         16 my $res;
372              
373             my $last_key;
374              
375 9         34 for (0 .. $#values) {
376 77 100       98 unless (@header) {
377 6         14 unshift @values, $res->{$last_key};
378 6         23 $res->{$last_key} = join ' ', @values;
379 6         12 last;
380             }
381             else {
382 71         76 my $k = $last_key = shift @header;
383 71         77 my $v = shift @values;
384 71         163 $res->{$k} = $v;
385             }
386             }
387              
388 9         37 for my $key (keys %$res) {
389 71         73 my $k2 = lc $key;
390 71         125 $k2 =~ s/[^A-Za-z]//gs;
391 71         191 $res->{$k2} = delete $res->{$key};
392             }
393            
394 9         31 return $res;
395             }
396              
397              
398             sub pid {
399 14     14   608 my ($self, $pid) = @_;
400              
401 14 100       32 if ($pid) {
402 5         33 $self->{pid} = $pid;
403             }
404 14         74 return $self->{pid};
405             }
406              
407              
408             sub internal_info {
409 12     12   22 my ($self, $param) = @_;
410              
411 12 100       36 if (ref $param eq 'HASH') {
412 6         23 $self->{_procinfo} = $param;
413 6         18 return 1;
414             }
415             else {
416 6         29 $param =~ s|^.+::||;
417 6         44 return $self->{_procinfo}->{$param};
418             }
419             }
420              
421              
422             sub cankill {
423 1     1   1 my $self = shift;
424              
425 1         5 my $pid = $self->pid();
426              
427 1 50       17 if (kill 0, $pid) {
428 1         9 return 1;
429             }
430 0         0 return 0;
431             }
432              
433              
434             sub is_alive {
435 0     0   0 my ($self) = @_;
436            
437 0         0 $self->refresh();
438 0         0 return $self->cankill();
439             }
440              
441             sub kill {
442 1     1   7 my ($self, $signal) = @_;
443              
444 1 50       6 if (!defined $signal) {
445 0         0 croak 'Signal must be specified';
446             }
447 1         5 return kill $signal, $self->pid();
448             }
449              
450              
451             sub DESTROY {
452 8     8   1001439 my $self = shift;
453 8         154 undef $self;
454             }
455              
456              
457             1;
458              
459             __END__;