File Coverage

blib/lib/Piddy.pm
Criterion Covered Total %
statement 46 127 36.2
branch 5 48 10.4
condition 0 6 0.0
subroutine 9 18 50.0
pod 9 10 90.0
total 69 209 33.0


line stmt bran cond sub pod time code
1             package Piddy;
2              
3             =head1 NAME
4              
5             Piddy - Easy Linux PID Management
6              
7             =head1 DESCRIPTION
8              
9             Manage the current process/pid and/or external ones (Not the current process) easily with this module. Use
10             it to create helpful sysadmin scripts while it lets you control the flow of a process by suspending and resuming
11             it at will. Some options require root access, but Piddy will let you know which ones when you try to run them.
12             Piddy will even attempt to determine if the pid instance is actually running as a threaded process.
13             This module probably still needs a lot of work, but it functions fine for the most part.
14              
15             =head1 SYNOPSIS
16              
17             use Piddy;
18              
19             my $pid = Piddy->new({
20             pid => 5367,
21             path => '/var/run/pids',
22             });
23              
24             if ($pid->running($pid->pid)) {
25             $pid->suspend($pid->pid); # temporarily stop the process where it is
26             print $pid->info('state') . "\n"; # read the current state of the process
27             sleep 20;
28             $pid->continue($pid->pid); # resume the process from where it was stopped
29             }
30             else { print "Oh.. " . $pid->pid . " is not actually running..\n"; }
31              
32             =cut
33              
34 2     2   61696 use strict;
  2         5  
  2         174  
35 2     2   12 use warnings;
  2         6  
  2         61  
36 2     2   57 use 5.010;
  2         10  
  2         98  
37              
38 2     2   8158 use FindBin;
  2         2600  
  2         97  
39 2     2   14 use File::Basename 'fileparse';
  2         5  
  2         3705  
40              
41             $Piddy::VERSION = '0.02';
42              
43             =head2 new
44              
45             Creates a new PID instance. There are a couple of options you can pass...
46              
47             pid = Use an external PID (Not the current running process).
48             path = Location of the pid file
49              
50             # Use pid 5367 and save the pid file as /var/run/pids/5367.pid
51             my $p = Piddy->new({pid => 5367, path => '/var/run/pids'});
52              
53             =cut
54              
55             sub new {
56 1     1 1 16 my ($class, $args) = @_;
57            
58 1         2 my $ext_pid = 0; # are we using a pid other than the current running script?
59 1         3 my $self = {};
60 1         73 my ($name, $path, $suffix) = fileparse($0, '\.[^\.]*');
61 1         7 $self->{path} = "$FindBin::Bin";
62 1         2 $self->{errors} = [];
63 1 50       5 if ($args) {
64 1         4 for (keys %$args) {
65 1         3 given ($_) {
66 1         10 when ('path') {
67 0         0 $self->{path} = $args->{$_};
68             }
69 1         4 when ('pid') {
70 1         3 $self->{pid} = $args->{$_};
71 1         5 $ext_pid = 1;
72             }
73 0         0 default {
74 0         0 warn "Unknown option: $_";
75 0         0 __PACKAGE__->last_error("Unknown option: $_");
76             }
77             }
78             }
79             }
80              
81 1         3 bless $self, __PACKAGE__;
82 1 50       3 if (! $ext_pid) {
83 0         0 my $filename = "$self->{path}/$name.pid";
84 0         0 $self = {
85             pid => $$,
86             pid_file => $filename,
87             };
88              
89 0         0 $self->_read_info;
90              
91 0 0       0 if ($self->_pid_exists($filename)) {
92             # we may be inside a thread if the pid is still running
93             # check to see if it's the ppid, if not, kill it.
94 0         0 my $rpid = $self->read($filename);
95 0 0       0 if (! $rpid eq getppid()) {
96             # remove it nicely, but if that fails, then DESTROY IT
97 0         0 kill 15, $rpid;
98 0 0       0 if ($self->running) {
99 0         0 kill 9, $rpid;
100 0 0       0 if ($self->running) {
101 0         0 warn "Argh. I could not kill $rpid... can you please do it for me? :-(";
102             }
103             }
104            
105 0         0 unlink $filename;
106             }
107             else {
108             # need to change filename to reflect the forked process
109 0   0     0 my $path = $self->{path}||"$FindBin::Bin";
110 0         0 $filename = $path . "/" . $name . "." . $self->{pid} . ".pid";
111 0         0 $self->{pid_file} = $filename;
112             }
113             }
114 0 0       0 open(my $pid_file, ">$filename") or
115             die "Could not open pid file $filename for writing";
116              
117 0 0       0 print $pid_file $$ or do {
118 0         0 $self->last_error("Could not write PID to $filename");
119             };
120            
121 0         0 close $pid_file;
122 0         0 return $self;
123             }
124             else {
125 1         6 $self->{extpid} = 1;
126 1         5 $self->_read_info;
127 1         6 return $self;
128             }
129             }
130              
131             =head2 info
132              
133             Reads information on the process from /proc
134              
135             my $state = $pid->info('state'); # Piddy formats state to make it look nicer, too!
136              
137             =cut
138              
139             sub info {
140 0     0 1 0 my ($self, $info) = @_;
141              
142 0         0 $self->_read_info;
143 0 0       0 if (exists $self->{info}->{$info}) { return $self->{info}->{$info}; }
  0         0  
144 0         0 else { return 0; }
145             }
146              
147             =head2 suspend
148              
149             Temporarily suspend a process (will not kill it, simply stops it exactly where it is so you
150             can resume it later. Handy when writing scripts to monitor performance - you can stop the process
151             then resume it when things have cooled down.
152              
153             $pid->suspend(5367);
154              
155             =cut
156              
157             sub suspend {
158 0     0 1 0 my ($self, $pid) = @_;
159              
160 0 0       0 if ($self->kill('-STOP', $pid)) { return 1; }
  0         0  
161 0         0 else { return 0; }
162             }
163              
164             =head2 continue
165              
166             Resumes a stopped process.
167              
168             $pid->continue(5367);
169              
170             =cut
171              
172             sub continue {
173 0     0 1 0 my ($self, $pid) = @_;
174              
175 0 0       0 if ($self->kill('-CONT', $pid)) { return 1; }
  0         0  
176 0         0 else { return 0; }
177             }
178              
179             =head2 kill
180              
181             Uses the systems kill command instead of Perl's. If you simply want to -9 or -15
182             a process then use Perl, but for things like stopping/continuing processes, I could
183             not get it to work any other way.
184              
185             $pid->kill('-9', 5367);
186             $pid->kill('-STOP', 5367); # or just use $pid->suspend(5367);
187              
188             =cut
189              
190             sub kill {
191 0     0 1 0 my ($self, $args, $pid) = @_;
192              
193 0 0       0 if ($< != 0) {
194 0         0 warn "This action requires root access";
195 0         0 return 0;
196             }
197            
198 0         0 my $cmd = `kill $args $pid`;
199 0         0 chomp $cmd;
200 0 0       0 if ($cmd eq '') { return 1; }
  0         0  
201 0         0 else { return 0; }
202             }
203              
204             =head2 ppid
205              
206             Returns the parent process id
207              
208             =cut
209              
210             sub ppid {
211 0     0 1 0 my $self = shift;
212            
213 0         0 return getppid();
214             }
215              
216             =head2 pid
217              
218             Returns the pid of the current instance
219              
220             =cut
221              
222             sub pid {
223 1     1 1 7 my $self = shift;
224            
225 1         12 return $self->{pid};
226             }
227              
228             =head2 running
229              
230             Determines whether the current pid is running, or if you pass
231             another pid as an argument it will check that instead.
232             By default it will use /proc, otherwise it will revert to ps and grep.
233              
234             if ($pid->running(5367)) { print "It's running!\n"; }
235              
236             =cut
237              
238             sub running {
239 0     0 1 0 my ($self, $cpid) = @_;
240              
241             # if the fs has /proc, then use it
242             # otherwise fallback on ps
243 0   0     0 my $pid = $cpid||$self->{pid};
244 0 0       0 if (-d '/proc') {
245 0 0       0 if (-d "/proc/$pid") {
246 0         0 return 1;
247             }
248 0         0 else { return 0; }
249             }
250             else {
251 0         0 my $ps = `ps -A | grep $pid`;
252 0         0 $ps =~ s/^\s*//;
253 0 0       0 if ($ps =~ /^$pid /) { return 1; }
  0         0  
254 0         0 else { return 0; }
255             }
256             }
257              
258             =head2 last_error
259              
260             Returns the last known error
261              
262             =cut
263              
264             sub last_error {
265 0     0 1 0 my ($self, $err) = @_;
266              
267 0 0       0 if ($err) { push @{$self->{errors}}, $err }
  0         0  
  0         0  
268 0         0 else { return $self->{errors}->[ scalar(@{$self->{errors}})+1 ]; }
  0         0  
269             }
270              
271             sub read {
272 0     0 0 0 my ($self, $fname) = @_;
273              
274 0 0       0 if (! $self->_pid_exists($fname)) { return 0; }
  0         0  
275             else {
276 0         0 my $getpid;
277 0 0       0 open (my $pid, "<$fname") or return 0;
278 0         0 while(<$pid>) {
279 0         0 $getpid = $_;
280             }
281 0         0 close $pid;
282            
283 0         0 return $getpid;
284             }
285             }
286              
287             sub _pid_exists {
288 0     0   0 my ($self, $fname) = @_;
289              
290 0 0       0 if (-f $fname) { return 1; }
  0         0  
291 0         0 else { return 0; }
292             }
293              
294             sub _read_info {
295 1     1   2 my $self = shift;
296              
297 1 50       58 open(my $proc, "/proc/$self->{pid}/status") or return 0;
298 1         47 while(<$proc>) {
299 41         191 my ($key, $value) = /(.+):\s*(.+)/;
300 41         221 $self->{info}->{lc($key)} = $value;
301             }
302 1         10 close $proc;
303              
304             # some fixups
305             # state
306 1         3 my $state = $self->{info}->{state};
307 1 50       9 if ($state =~ /(.+)\s*\((.+)\)/) {
308 1         7 $self->{info}->{state} = lc $2;
309             }
310             }
311              
312             sub DESTROY {
313 1     1   345 my $self = shift;
314 1 50       180 if (! $self->{extpid}) {
315 0 0         if (! unlink $self->{pid_file}) {
316 0           warn "There was a problem removing '$self->{pid_file}'";
317             }
318 0           else { say "Removed $self->{pid_file}"; }
319             }
320             }
321              
322             =head1 BUGS
323              
324             Please e-mail bradh@cpan.org
325              
326             =head1 AUTHOR
327              
328             Brad Haywood
329              
330             =head1 COPYRIGHT & LICENSE
331              
332             Copyright 2011 the above author(s).
333              
334             This sofware is free software, and is licensed under the same terms as perl itself.
335              
336             =cut
337              
338             1;