1 # Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
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.
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
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
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
20 package Amanda::Process;
27 use vars qw( @ISA @EXPORT_OK );
29 use Amanda::Constants;
30 @ISA = qw( Exporter );
34 Amanda::Process -- interface to process
40 Amanda::Process::load_ps_table();
42 Amanda::Process::scan_log($logfile);
44 Amanda::Process::add_child();
46 Amanda::Process::set_master_process(@pname);
48 Amanda::Process::set_master($pname, $pid);
50 Amanda::Process::kill_process($signal);
52 my $count = Amanda::Process::process_running();
54 my $count = Amanda::Process::count_process();
56 my $alive = Amanda::Process::process_alive($pid, $pname);
60 This module provides an object-oriented interface to track process used by
63 my $Amanda_process = Amanda::Process->new($verbose);
69 $Amanda_process->load_ps_table();
71 Load a table of all processes in the system.
75 $Amanda_process->scan_log($logfile);
77 Parse all 'pid' and 'pid-done' lines of the logfile.
81 $Amanda_process->add_child();
83 Add all children of already known amanda processes.
85 =item set_master_process
87 $Amanda_process->set_master_process($arg, @pname);
89 Search the process table to find a process in @pname and make it the master, $arg must be an argument of the process.
93 $Amanda_process->set_master($pname, $pid);
95 Set $Amanda_process->{master_pname} and $Amanda_process->{master_pid}.
99 $Amanda_process->kill_process($signal);
101 Send the $signal to all amanda processes.
103 =item process_running
105 my $count = $Amanda_process->process_running();
107 Return the number of amanda process alive.
111 my $count = $Amanda_process->count_process();
113 Return the number of amanda process in the table.
117 my $alive = Amanda::Process::process_alive($pid, $pname);
119 Return 0 if the process is not alive.
120 Return 1 if the process is still alive.
128 my ($verbose) = shift;
138 bless ($self, $class);
142 # Get information about the current set of processes, using ps -e
146 # - sets %pstable to a map (pid => process name) of all running
148 # - sets %ppid to a map (pid -> parent pid) of all running
149 # processes' parent pids
151 sub load_ps_table() {
153 $self->{pstable} = {};
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>) {
161 my @psline = split " ", $psline;
162 my $pid = $psline[1];
163 my $ppid = $psline[2];
164 my $stime = $psline[4];
166 if ($stime =~ /:/) { # 10:32:44
167 $pname = basename($psline[5])
169 $pname = basename($psline[6])
171 $self->{pstable}->{$pid} = $pname;
172 $self->{ppid}->{$pid} = $ppid;
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>) {
181 my ($pid, $ppid, $pname, $arg1, $arg2) = split " ", $psline;
182 $pname = basename($pname);
183 if ($pname =~ /^perl/ && defined $arg1) {
184 if ($arg1 !~ /^\-/) {
186 } elsif (defined $arg2) {
187 if ($arg2 !~ /^\-/) {
191 $pname = basename($pname);
193 $self->{pstable}->{$pid} = $pname;
194 $self->{ppid}->{$pid} = $ppid;
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
206 # %pstable must be set up (use load_ps_table)
209 # - sets %pids to a map (pid => process name) of all still-running
211 # - sets $master_pname to the top-level process for this run (e.g.,
213 # - sets $master_pid to the pid of $master_pname
215 # @param $logfile: the logfile to scan
223 open(LOGFILE, "<", $logfile) || die("$logfile: $!");
224 while($line = <LOGFILE>) {
225 if ($line =~ /^INFO .* (.*) pid (\d*)$/) {
226 my ($pname, $pid) = ($1, $2);
228 $self->{master_pname} = $pname;
229 $self->{master_pid} = $pid;
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};
240 } elsif ($line =~ /^INFO .* pid-done (\d*)$/) {
242 print "pid $pid is done\n" if $self->{verbose};
243 delete $self->{pids}->{$pid};
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";
258 # Recursive function to add all child processes of $pid to %amprocess.
261 # - %ppid must be set (load_ps_table)
264 # - adds all child processes of $pid to %amprocess
266 # @param $pid: the process to start at
268 sub add_child_pid($);
269 sub add_child_pid($) {
272 foreach my $cpid (keys %{$self->{ppid}}) {
273 my $ppid = $self->{ppid}->{$cpid};
275 if (!defined $self->{amprocess}->{$cpid}) {
276 $self->{amprocess}->{$cpid} = $cpid;
277 $self->add_child_pid($cpid);
283 # Find all children of all amanda processes, as determined by traversing
284 # the process graph (via %ppid).
287 # - %ppid must be set (load_ps_table)
288 # - %pids must be set (scan_log)
291 # - sets %amprocess to a map (pid => pid) of all amanda processes, including
296 foreach my $pid (keys %{$self->{pids}}) {
298 $self->{amprocess}->{$pid} = $pid;
302 foreach my $pid (keys %{$self->{pids}}) {
303 $self->add_child_pid($pid);
307 # Set master_pname and master_pid.
310 # - sets $self->{master_pname} and $self->{master_pid}.
312 sub set_master_process {
317 my $ps_argument_args = $Amanda::Constants::PS_ARGUMENT_ARGS;
318 for my $pname (@pname) {
321 if ($ps_argument_args eq "CYGWIN") {
322 $pid = `ps -ef|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$2}'`;
324 $pid = `$Amanda::Constants::PS $ps_argument_args|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$1}'`;
328 $self->set_master($pname, $pid);
333 # Set master_pname and master_pid.
336 # - sets $self->{master_pname} and $self->{master_pid}.
343 $self->{master_pname} = $pname;
344 $self->{master_pid} = $pid;
345 $self->{pids}->{$pid} = $pname;
347 # Send a signal to all amanda process
350 # - All amanda process receive the signal.
353 # - %amprocess must be set (add_child)
355 # @param $signal: the signal to send
357 sub kill_process($) {
361 foreach my $pid (keys %{$self->{amprocess}}) {
362 print "Sendding $signal signal to pid $pid\n" if $self->{verbose};
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
372 # - %amprocess must be set (add_child)
374 # @returns: number of pids in %amprocess that are still alive
375 sub process_running() {
378 $self->load_ps_table();
380 foreach my $pid (keys %{$self->{amprocess}}) {
381 if (defined $self->{pstable}->{$pid}) {
389 # Count the number of processes in %amprocess.
392 # - %amprocess must be set (add_child)
394 # @returns: number of pids in %amprocess.
395 sub count_process() {
398 return scalar keys( %{$self->{amprocess}} );
401 # return if a process is alive. If $pname is provided,
402 # only returns 1 if the name matches.
405 # - %pstable must be set (load_ps_table)
407 # @param $pid: the pid of the process
408 # @param $pname: the name of the process (optional)
410 # @returns: 1 if process is alive
411 # '' if process is dead
413 sub process_alive() {
418 if (defined $pname && defined $self->{pstable}->{$pid}) {
419 return $self->{pstable}->{$pid} eq $pname;
421 return defined $self->{pstable}->{$pid};