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