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