Imported Upstream version 3.3.2
[debian/amanda] / perl / Amanda / Process.pm
1 # Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
18
19 package Amanda::Process;
20
21 use strict;
22 use warnings;
23 use Carp;
24 use POSIX ();
25 use Exporter;
26 use vars qw( @ISA @EXPORT_OK );
27 use File::Basename;
28 use Amanda::Constants;
29 @ISA = qw( Exporter );
30
31 =head1 NAME
32
33 Amanda::Process -- interface to process
34
35 =head1 SYNOPSIS
36
37   use Amanda::Process;
38
39   Amanda::Process::load_ps_table();
40
41   Amanda::Process::scan_log($logfile);
42
43   Amanda::Process::add_child();
44
45   Amanda::Process::set_master_process(@pname);
46
47   Amanda::Process::set_master($pname, $pid);
48
49   Amanda::Process::kill_process($signal);
50
51   my $count = Amanda::Process::process_running();
52
53   my $count = Amanda::Process::count_process();
54
55   my $alive = Amanda::Process::process_alive($pid, $pname);
56
57 =head1 INTERFACE
58
59 This module provides an object-oriented interface to track process used by
60 amanda.
61
62 my $Amanda_process = Amanda::Process->new($verbose);
63
64 =over
65
66 =item load_ps_table
67
68   $Amanda_process->load_ps_table();
69
70 Load a table of all processes in the system.
71
72 =item scan_log
73
74   $Amanda_process->scan_log($logfile);
75
76 Parse all 'pid' and 'pid-done' lines of the logfile.
77
78 =item add_child
79
80   $Amanda_process->add_child();
81
82 Add all children of already known amanda processes.
83
84 =item set_master_process
85
86   $Amanda_process->set_master_process($arg, @pname);
87
88 Search the process table to find a process in @pname and make it the master, $arg must be an argument of the process.
89
90 =item set_master
91
92   $Amanda_process->set_master($pname, $pid);
93
94 Set $Amanda_process->{master_pname} and $Amanda_process->{master_pid}.
95
96 =item kill_process
97
98   $Amanda_process->kill_process($signal);
99
100 Send the $signal to all amanda processes.
101
102 =item process_running
103
104   my $count = $Amanda_process->process_running();
105
106 Return the number of amanda process alive.
107
108 =item count_process
109
110   my $count = $Amanda_process->count_process();
111
112 Return the number of amanda process in the table.
113
114 =item process_alive
115
116   my $alive = Amanda::Process::process_alive($pid, $pname);
117
118 Return 0 if the process is not alive.
119 Return 1 if the process is still alive.
120
121 =back
122
123 =cut
124
125 sub new {
126     my $class = shift;
127     my ($verbose) = shift;
128
129     my $self = {
130         verbose => $verbose,
131         master_name => "",
132         master_pid => "",
133         pids => {},
134         pstable => {},
135         ppid => {},
136     };
137     bless ($self, $class);
138     return $self;
139 }
140
141 # Get information about the current set of processes, using ps -e
142 # and ps -ef.
143 #
144 # Side effects:
145 # - sets %pstable to a map (pid => process name) of all running
146 #   processes
147 # - sets %ppid to a map (pid -> parent pid) of all running
148 #   processes' parent pids
149 #
150 sub load_ps_table() {
151     my $self = shift;
152     $self->{pstable} = {};
153     $self->{ppid} = ();
154     my $ps_argument = $Amanda::Constants::PS_ARGUMENT;
155     if ($ps_argument eq "CYGWIN") {
156         open(PSTABLE, "-|", "ps -ef") || die("ps -ef: $!");
157         my $psline = <PSTABLE>; #header line
158         while($psline = <PSTABLE>) {
159             chomp $psline;
160             my @psline = split " ", $psline;
161             my $pid = $psline[1];
162             my $ppid = $psline[2];
163             my $stime = $psline[4];
164             my $pname;
165             if ($stime =~ /:/) {  # 10:32:44
166                 $pname = basename($psline[5])
167             } else {              # May 22
168                 $pname = basename($psline[6])
169             }
170             $self->{pstable}->{$pid} = $pname;
171             $self->{ppid}->{$pid} = $ppid;
172         }
173         close(PSTABLE);
174     } else {
175         open(PSTABLE, "-|", "$Amanda::Constants::PS $ps_argument")
176             or die("$Amanda::Constants::PS $ps_argument: $!");
177         my $psline = <PSTABLE>; #header line
178         while($psline = <PSTABLE>) {
179             chomp $psline;
180             my ($pid, $ppid, $pname, $arg1, $arg2) = split " ", $psline;
181             $pname = basename($pname);
182             if ($pname =~ /^perl/ && defined $arg1) {
183                 if ($arg1 !~ /^\-/) {
184                     $pname = $arg1;
185                 } elsif (defined $arg2) {
186                     if ($arg2 !~ /^\-/) {
187                         $pname = $arg2;
188                     }
189                 }
190                 $pname = basename($pname);
191             }
192             $self->{pstable}->{$pid} = $pname;
193             $self->{ppid}->{$pid} = $ppid;
194         }
195         close(PSTABLE);
196     }
197 }
198
199 # Scan a logfile for processes that should still be running: processes
200 # having an "INFO foo bar pid 1234" line in the log, but no corresponding
201 # "INFO pid-done 1234", and only if pid 1234 has the correct process
202 # name.
203 #
204 # Prerequisites:
205 #  %pstable must be set up (use load_ps_table)
206 #
207 # Side effects:
208 # - sets %pids to a map (pid => process name) of all still-running
209 #   Amanda processes
210 # - sets $master_pname to the top-level process for this run (e.g.,
211 #   amdump, amflush)
212 # - sets $master_pid to the pid of $master_pname
213 #
214 # @param $logfile: the logfile to scan
215 #
216 sub scan_log($) {
217     my $self = shift;
218     my $logfile = shift;
219     my $first = 1;
220     my($line);
221
222     open(LOGFILE, "<", $logfile) || die("$logfile: $!");
223     while($line = <LOGFILE>) {
224         if ($line =~ /^INFO .* (.*) pid (\d*)$/) {
225             my ($pname, $pid) = ($1, $2);
226             if ($first == 1) {
227                 $self->{master_pname} = $pname;
228                 $self->{master_pid} = $pid;
229                 $first = 0;
230             }
231             if (defined $self->{pstable}->{$pid} && $pname eq $self->{pstable}->{$pid}) {
232                 $self->{pids}->{$pid} = $pname;
233             } elsif (defined $self->{pstable}->{$pid} && $self->{pstable}->{$pid} =~ /^perl/) {
234                 # We can get 'perl' for a perl script.
235                 $self->{pids}->{$pid} = $pname;
236             } elsif (defined $self->{pstable}->{$pid}) {
237                 print "pid $pid doesn't match: ", $pname, " != ", $self->{pstable}->{$pid}, "\n" if $self->{verbose};
238             }
239         } elsif ($line =~ /^INFO .* pid-done (\d*)$/) {
240             my $pid = $1;
241             print "pid $pid is done\n" if $self->{verbose};
242             delete $self->{pids}->{$pid};
243         }
244     }
245     close(LOGFILE);
246
247     # log unexpected dead process
248     if ($self->{verbose}) {
249         for my $pid (keys %{$self->{pids}}) {
250             if (!defined $self->{pstable}->{$pid}) {
251                 print "pid $pid is dead\n";
252             }
253         }
254     }
255 }
256
257 # Recursive function to add all child processes of $pid to %amprocess.
258 #
259 # Prerequisites:
260 # - %ppid must be set (load_ps_table)
261 #
262 # Side-effects:
263 # - adds all child processes of $pid to %amprocess
264 #
265 # @param $pid: the process to start at
266 #
267 sub add_child_pid($);
268 sub add_child_pid($) {
269     my $self = shift;
270     my $pid = shift;
271     foreach my $cpid (keys %{$self->{ppid}}) {
272         my $ppid = $self->{ppid}->{$cpid};
273         if ($pid == $ppid) {
274             if (!defined $self->{amprocess}->{$cpid}) {
275                 $self->{amprocess}->{$cpid} = $cpid;
276                 $self->add_child_pid($cpid);
277             }
278         }
279     }
280 }
281
282 # Find all children of all amanda processes, as determined by traversing
283 # the process graph (via %ppid).
284 #
285 # Prerequisites:
286 # - %ppid must be set (load_ps_table)
287 # - %pids must be set (scan_log)
288 #
289 # Side-effects:
290 # - sets %amprocess to a map (pid => pid) of all amanda processes, including
291 #   children
292 #
293 sub add_child() {
294     my $self = shift;
295     foreach my $pid (keys %{$self->{pids}}) {
296         if (defined $pid) {
297             $self->{amprocess}->{$pid} = $pid;
298         }
299     }
300
301     foreach my $pid (keys %{$self->{pids}}) {
302         $self->add_child_pid($pid);
303     }
304 }
305
306 # Set master_pname and master_pid.
307 #
308 # Side-effects:
309 # - sets $self->{master_pname} and $self->{master_pid}.
310 #
311 sub set_master_process {
312     my $self = shift;
313     my $arg = shift;
314     my @pname = @_;
315
316     my $ps_argument_args = $Amanda::Constants::PS_ARGUMENT_ARGS;
317     for my $pname (@pname) {
318         my $pid;
319
320         if ($ps_argument_args eq "CYGWIN") {
321             $pid = `ps -ef|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$2}'`;
322         } else {
323             $pid = `$Amanda::Constants::PS $ps_argument_args|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$1}'`;
324         }
325         chomp $pid;
326         if ($pid ne "") {
327             $self->set_master($pname, $pid);
328         }
329     }
330 }
331
332 # Set master_pname and master_pid.
333 #
334 # Side-effects:
335 # - sets $self->{master_pname} and $self->{master_pid}.
336 #
337 sub set_master($$) {
338     my $self = shift;
339     my $pname = shift;
340     my $pid = shift;
341
342     $self->{master_pname} = $pname;
343     $self->{master_pid} = $pid;
344     $self->{pids}->{$pid} = $pname;
345 }
346 # Send a signal to all amanda process
347 #
348 # Side-effects:
349 # - All amanda process receive the signal.
350 #
351 # Prerequisites:
352 # - %amprocess must be set (add_child)
353 #
354 # @param $signal: the signal to send
355 #
356 sub kill_process($) {
357     my $self = shift;
358     my $signal = shift;
359
360     foreach my $pid (keys %{$self->{amprocess}}) {
361         print "Sendding $signal signal to pid $pid\n" if $self->{verbose};
362         kill $signal, $pid;
363     }
364 }
365
366 # Count the number of processes in %amprocess that are still running.  This
367 # re-runs 'ps -e' every time, so calling it repeatedly may result in a
368 # decreasing count.
369 #
370 # Prerequisites:
371 # - %amprocess must be set (add_child)
372 #
373 # @returns: number of pids in %amprocess that are still alive
374 sub process_running() {
375     my $self = shift;
376
377     $self->load_ps_table();
378     my $count = 0;
379     foreach my $pid (keys %{$self->{amprocess}}) {
380         if (defined $self->{pstable}->{$pid}) {
381             $count++;
382         }
383     }
384
385     return $count;
386 }
387
388 # Count the number of processes in %amprocess.
389 #
390 # Prerequisites:
391 # - %amprocess must be set (add_child)
392 #
393 # @returns: number of pids in %amprocess.
394 sub count_process() {
395     my $self = shift;
396
397     return scalar keys( %{$self->{amprocess}} );
398 }
399
400 # return if a process is alive.  If $pname is provided,
401 # only returns 1 if the name matches.
402 #
403 # Prerequisites:
404 # - %pstable must be set (load_ps_table)
405 #
406 # @param $pid: the pid of the process
407 # @param $pname: the name of the process (optional)
408 #
409 # @returns: 1 if process is alive
410 #           '' if process is dead
411
412 sub process_alive() {
413     my $self = shift;
414     my $pid = shift;
415     my $pname = shift;
416
417     if (defined $pname && defined $self->{pstable}->{$pid}) {
418         return $self->{pstable}->{$pid} eq $pname;
419     } else {
420         return defined $self->{pstable}->{$pid};
421     }
422 }
423
424 1;