1 # Copyright (c) 2005-2008 Zmanda, Inc. All Rights Reserved.
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.
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.
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.
16 # Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 package Amanda::Process;
26 use vars qw( @ISA @EXPORT_OK );
28 use Amanda::Constants;
29 @ISA = qw( Exporter );
33 use Amanda::MainLoop qw( :GIOCondition );
35 use Amanda::Device qw( :constants );
39 Amanda::Process -- interface to process
45 Amanda::Process::load_ps_table();
47 Amanda::Process::scan_log($logfile);
49 Amanda::Process::add_child();
51 Amanda::Process::set_master($pname, $pid);
53 Amanda::Process::kill_process($signal);
55 my $count = Amanda::Process::process_running();
57 my $count = Amanda::Process::count_process();
59 my $alive = Amanda::Process::process_alive($pid, $pname);
67 This module provides an object-oriented interface to track process used by
70 my $Amanda_process = Amanda::Process->new($verbose);
76 $Amanda_process->load_ps_table();
78 Load a table of all processes in the system.
82 $Amanda_process->scan_log($logfile);
84 Parse all 'pid' and 'pid-done' lines of the logfile.
88 $Amanda_process->add_child();
90 Add all children of already known amanda processes.
94 $Amanda_process->set_master($pname, $pid);
96 Set $Amanda_process->{master_pname} and $Amanda_process->{master_pid}.
100 $Amanda_process->kill_process($signal);
102 Send the $signal to all amanda processes.
104 =item process_running
106 my $count = $Amanda_process->process_running();
108 Return the number of amanda process alive.
112 my $count = $Amanda_process->count_process();
114 Return the number of amanda process in the table.
118 my $alive = Amanda::Process::process_alive($pid, $pname);
120 Return 0 if the process is not alive.
121 Return 1 if the process is still alive.
129 my ($verbose) = shift;
139 bless ($self, $class);
143 # Get information about the current set of processes, using ps -e
147 # - sets %pstable to a map (pid => process name) of all running
149 # - sets %ppid to a map (pid -> parent pid) of all running
150 # processes' parent pids
152 sub load_ps_table() {
154 $self->{pstable} = {};
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>) {
162 my @psline = split " ", $psline;
163 my $pid = $psline[1];
164 my $ppid = $psline[2];
165 my $stime = $psline[4];
167 if ($stime =~ /:/) { # 10:32:44
168 $pname = basename($psline[5])
170 $pname = basename($psline[6])
172 $self->{pstable}->{$pid} = $pname;
173 $self->{ppid}->{$pid} = $ppid;
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>) {
182 my ($pid, $ppid, $pname) = split " ", $psline;
183 $pname = basename($pname);
184 $self->{pstable}->{$pid} = $pname;
185 $self->{ppid}->{$pid} = $ppid;
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
197 # %pstable must be set up (use load_ps_table)
200 # - sets %pids to a map (pid => process name) of all still-running
202 # - sets $master_pname to the top-level process for this run (e.g.,
204 # - sets $master_pid to the pid of $master_pname
206 # @param $logfile: the logfile to scan
214 open(LOGFILE, "<", $logfile) || die("$logfile: $!");
215 while($line = <LOGFILE>) {
216 if ($line =~ /^INFO .* (.*) pid (\d*)$/) {
217 my ($pname, $pid) = ($1, $2);
219 $self->{master_pname} = $pname;
220 $self->{master_pid} = $pid;
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};
228 } elsif ($line =~ /^INFO .* pid-done (\d*)$/) {
230 print "pid $pid is done\n" if $self->{verbose};
231 delete $self->{pids}->{$pid};
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";
246 # Recursive function to add all child processes of $pid to %amprocess.
249 # - %ppid must be set (load_ps_table)
252 # - adds all child processes of $pid to %amprocess
254 # @param $pid: the process to start at
256 sub add_child_pid($);
257 sub add_child_pid($) {
260 foreach my $cpid (keys %{$self->{ppid}}) {
261 my $ppid = $self->{ppid}->{$cpid};
263 if (!defined $self->{amprocess}->{$cpid}) {
264 $self->{amprocess}->{$cpid} = $cpid;
265 $self->add_child_pid($cpid);
271 # Find all children of all amanda processes, as determined by traversing
272 # the process graph (via %ppid).
275 # - %ppid must be set (load_ps_table)
276 # - %pids must be set (scan_log)
279 # - sets %amprocess to a map (pid => pid) of all amanda processes, including
284 foreach my $pid (keys %{$self->{pids}}) {
286 $self->{amprocess}->{$pid} = $pid;
290 foreach my $pid (keys %{$self->{pids}}) {
291 $self->add_child_pid($pid);
295 # Set master_pname and master_pid.
298 # - sets $master_pname and $master_pid.
305 $self->{master_pname} = $pname;
306 $self->{master_pid} = $pid;
307 $self->{pids}->{$pid} = $pname;
310 # Send a signal to all amanda process
313 # - All amanda process receive the signal.
316 # - %amprocess must be set (add_child)
318 # @param $signal: the signal to send
320 sub kill_process($) {
324 foreach my $pid (keys %{$self->{amprocess}}) {
325 print "Sendding $signal signal to pid $pid\n" if $self->{verbose};
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
335 # - %amprocess must be set (add_child)
337 # @returns: number of pids in %amprocess that are still alive
338 sub process_running() {
341 $self->load_ps_table();
343 foreach my $pid (keys %{$self->{amprocess}}) {
344 if (defined $self->{pstable}->{$pid}) {
352 # Count the number of processes in %amprocess.
355 # - %amprocess must be set (add_child)
357 # @returns: number of pids in %amprocess.
358 sub count_process() {
361 return scalar keys( %{$self->{amprocess}} );
364 # return if a process is alive. If $pname is provided,
365 # only returns 1 if the name matches.
368 # - %pstable must be set (load_ps_table)
370 # @param $pid: the pid of the process
371 # @param $pname: the name of the process (optional)
373 # @returns: 1 if process is alive
374 # '' if process is dead
376 sub process_alive() {
381 if (defined $pname && defined $self->{pstable}->{$pid}) {
382 return $self->{pstable}->{$pid} eq $pname;
384 return defined $self->{pstable}->{$pid};