1 # Copyright (c) 2008,2009 Zmanda, Inc. All Rights Reserved.
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.
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
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
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, 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 Amanda::Process -- interface to process
39 Amanda::Process::load_ps_table();
41 Amanda::Process::scan_log($logfile);
43 Amanda::Process::add_child();
45 Amanda::Process::set_master_process(@pname);
47 Amanda::Process::set_master($pname, $pid);
49 Amanda::Process::kill_process($signal);
51 my $count = Amanda::Process::process_running();
53 my $count = Amanda::Process::count_process();
55 my $alive = Amanda::Process::process_alive($pid, $pname);
59 This module provides an object-oriented interface to track process used by
62 my $Amanda_process = Amanda::Process->new($verbose);
68 $Amanda_process->load_ps_table();
70 Load a table of all processes in the system.
74 $Amanda_process->scan_log($logfile);
76 Parse all 'pid' and 'pid-done' lines of the logfile.
80 $Amanda_process->add_child();
82 Add all children of already known amanda processes.
84 =item set_master_process
86 $Amanda_process->set_master_process($arg, @pname);
88 Search the process table to find a process in @pname and make it the master, $arg must be an argument of the process.
92 $Amanda_process->set_master($pname, $pid);
94 Set $Amanda_process->{master_pname} and $Amanda_process->{master_pid}.
98 $Amanda_process->kill_process($signal);
100 Send the $signal to all amanda processes.
102 =item process_running
104 my $count = $Amanda_process->process_running();
106 Return the number of amanda process alive.
110 my $count = $Amanda_process->count_process();
112 Return the number of amanda process in the table.
116 my $alive = Amanda::Process::process_alive($pid, $pname);
118 Return 0 if the process is not alive.
119 Return 1 if the process is still alive.
127 my ($verbose) = shift;
137 bless ($self, $class);
141 # Get information about the current set of processes, using ps -e
145 # - sets %pstable to a map (pid => process name) of all running
147 # - sets %ppid to a map (pid -> parent pid) of all running
148 # processes' parent pids
150 sub load_ps_table() {
152 $self->{pstable} = {};
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>) {
160 my @psline = split " ", $psline;
161 my $pid = $psline[1];
162 my $ppid = $psline[2];
163 my $stime = $psline[4];
165 if ($stime =~ /:/) { # 10:32:44
166 $pname = basename($psline[5])
168 $pname = basename($psline[6])
170 $self->{pstable}->{$pid} = $pname;
171 $self->{ppid}->{$pid} = $ppid;
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>) {
180 my ($pid, $ppid, $pname, $arg1, $arg2) = split " ", $psline;
181 $pname = basename($pname);
182 if ($pname =~ /^perl/ && defined $arg1) {
183 if ($arg1 !~ /^\-/) {
185 } elsif (defined $arg2) {
186 if ($arg2 !~ /^\-/) {
190 $pname = basename($pname);
192 $self->{pstable}->{$pid} = $pname;
193 $self->{ppid}->{$pid} = $ppid;
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
205 # %pstable must be set up (use load_ps_table)
208 # - sets %pids to a map (pid => process name) of all still-running
210 # - sets $master_pname to the top-level process for this run (e.g.,
212 # - sets $master_pid to the pid of $master_pname
214 # @param $logfile: the logfile to scan
222 open(LOGFILE, "<", $logfile) || die("$logfile: $!");
223 while($line = <LOGFILE>) {
224 if ($line =~ /^INFO .* (.*) pid (\d*)$/) {
225 my ($pname, $pid) = ($1, $2);
227 $self->{master_pname} = $pname;
228 $self->{master_pid} = $pid;
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};
239 } elsif ($line =~ /^INFO .* pid-done (\d*)$/) {
241 print "pid $pid is done\n" if $self->{verbose};
242 delete $self->{pids}->{$pid};
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";
257 # Recursive function to add all child processes of $pid to %amprocess.
260 # - %ppid must be set (load_ps_table)
263 # - adds all child processes of $pid to %amprocess
265 # @param $pid: the process to start at
267 sub add_child_pid($);
268 sub add_child_pid($) {
271 foreach my $cpid (keys %{$self->{ppid}}) {
272 my $ppid = $self->{ppid}->{$cpid};
274 if (!defined $self->{amprocess}->{$cpid}) {
275 $self->{amprocess}->{$cpid} = $cpid;
276 $self->add_child_pid($cpid);
282 # Find all children of all amanda processes, as determined by traversing
283 # the process graph (via %ppid).
286 # - %ppid must be set (load_ps_table)
287 # - %pids must be set (scan_log)
290 # - sets %amprocess to a map (pid => pid) of all amanda processes, including
295 foreach my $pid (keys %{$self->{pids}}) {
297 $self->{amprocess}->{$pid} = $pid;
301 foreach my $pid (keys %{$self->{pids}}) {
302 $self->add_child_pid($pid);
306 # Set master_pname and master_pid.
309 # - sets $self->{master_pname} and $self->{master_pid}.
311 sub set_master_process {
316 my $ps_argument_args = $Amanda::Constants::PS_ARGUMENT_ARGS;
317 for my $pname (@pname) {
320 if ($ps_argument_args eq "CYGWIN") {
321 $pid = `ps -ef|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$2}'`;
323 $pid = `$Amanda::Constants::PS $ps_argument_args|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$1}'`;
327 $self->set_master($pname, $pid);
332 # Set master_pname and master_pid.
335 # - sets $self->{master_pname} and $self->{master_pid}.
342 $self->{master_pname} = $pname;
343 $self->{master_pid} = $pid;
344 $self->{pids}->{$pid} = $pname;
346 # Send a signal to all amanda process
349 # - All amanda process receive the signal.
352 # - %amprocess must be set (add_child)
354 # @param $signal: the signal to send
356 sub kill_process($) {
360 foreach my $pid (keys %{$self->{amprocess}}) {
361 print "Sendding $signal signal to pid $pid\n" if $self->{verbose};
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
371 # - %amprocess must be set (add_child)
373 # @returns: number of pids in %amprocess that are still alive
374 sub process_running() {
377 $self->load_ps_table();
379 foreach my $pid (keys %{$self->{amprocess}}) {
380 if (defined $self->{pstable}->{$pid}) {
388 # Count the number of processes in %amprocess.
391 # - %amprocess must be set (add_child)
393 # @returns: number of pids in %amprocess.
394 sub count_process() {
397 return scalar keys( %{$self->{amprocess}} );
400 # return if a process is alive. If $pname is provided,
401 # only returns 1 if the name matches.
404 # - %pstable must be set (load_ps_table)
406 # @param $pid: the pid of the process
407 # @param $pname: the name of the process (optional)
409 # @returns: 1 if process is alive
410 # '' if process is dead
412 sub process_alive() {
417 if (defined $pname && defined $self->{pstable}->{$pid}) {
418 return $self->{pstable}->{$pid} eq $pname;
420 return defined $self->{pstable}->{$pid};