1# Copyright (c) 2008-2013 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 20package Amanda::Process; 21 22use strict; 23use warnings; 24use Carp; 25use POSIX (); 26use Exporter; 27use vars qw( @ISA @EXPORT_OK ); 28use File::Basename; 29use Amanda::Constants; 30use Amanda::Debug qw( debug ); 31@ISA = qw( Exporter ); 32 33=head1 NAME 34 35Amanda::Process -- interface to process 36 37=head1 SYNOPSIS 38 39 use Amanda::Process; 40 41 Amanda::Process::load_ps_table(); 42 43 Amanda::Process::scan_log($logfile); 44 45 Amanda::Process::add_child(); 46 47 Amanda::Process::set_master_process(@pname); 48 49 Amanda::Process::set_master($pname, $pid); 50 51 Amanda::Process::kill_process($signal); 52 53 my $count = Amanda::Process::process_running(); 54 55 my $count = Amanda::Process::count_process(); 56 57 my $alive = Amanda::Process::process_alive($pid, $pname); 58 59=head1 INTERFACE 60 61This module provides an object-oriented interface to track process used by 62amanda. 63 64my $Amanda_process = Amanda::Process->new($verbose); 65 66=over 67 68=item load_ps_table 69 70 $Amanda_process->load_ps_table(); 71 72Load a table of all processes in the system. 73 74=item scan_log 75 76 $Amanda_process->scan_log($logfile); 77 78Parse all 'pid' and 'pid-done' lines of the logfile. 79 80=item add_child 81 82 $Amanda_process->add_child(); 83 84Add all children of already known amanda processes. 85 86=item set_master_process 87 88 $Amanda_process->set_master_process($arg, @pname); 89 90Search the process table to find a process in @pname and make it the master, $arg must be an argument of the process. 91 92=item set_master 93 94 $Amanda_process->set_master($pname, $pid); 95 96Set $Amanda_process->{master_pname} and $Amanda_process->{master_pid}. 97 98=item kill_process 99 100 $Amanda_process->kill_process($signal); 101 102Send the $signal to all amanda processes. 103 104=item process_running 105 106 my $count = $Amanda_process->process_running(); 107 108Return the number of amanda process alive. 109 110=item count_process 111 112 my $count = $Amanda_process->count_process(); 113 114Return the number of amanda process in the table. 115 116=item process_alive 117 118 my $alive = Amanda::Process::process_alive($pid, $pname); 119 120Return 0 if the process is not alive. 121Return 1 if the process is still alive. 122 123=back 124 125=cut 126 127sub 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# 152sub 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 -f") || die("ps -f: $!"); 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, $arg1, $arg2) = split " ", $psline; 183 $pname = basename($pname); 184 if ($pname =~ /^perl/ && defined $arg1) { 185 if ($arg1 !~ /^\-/) { 186 $pname = $arg1; 187 } elsif (defined $arg2) { 188 if ($arg2 !~ /^\-/) { 189 $pname = $arg2; 190 } 191 } 192 $pname = basename($pname); 193 } 194 $self->{pstable}->{$pid} = $pname; 195 $self->{ppid}->{$pid} = $ppid; 196 } 197 close(PSTABLE); 198 } 199} 200 201# Scan a logfile for processes that should still be running: processes 202# having an "INFO foo bar pid 1234" line in the log, but no corresponding 203# "INFO pid-done 1234", and only if pid 1234 has the correct process 204# name. 205# 206# Prerequisites: 207# %pstable must be set up (use load_ps_table) 208# 209# Side effects: 210# - sets %pids to a map (pid => process name) of all still-running 211# Amanda processes 212# - sets $master_pname to the top-level process for this run (e.g., 213# amdump, amflush) 214# - sets $master_pid to the pid of $master_pname 215# 216# @param $logfile: the logfile to scan 217# 218sub scan_log($) { 219 my $self = shift; 220 my $logfile = shift; 221 my $first = 1; 222 my($line); 223 224 open(LOGFILE, "<", $logfile) || die("$logfile: $!"); 225 while($line = <LOGFILE>) { 226 if ($line =~ /^INFO .* (.*) pid (\d*)$/) { 227 my ($pname, $pid) = ($1, $2); 228 if ($first == 1) { 229 $self->{master_pname} = $pname; 230 $self->{master_pid} = $pid; 231 $first = 0; 232 } 233 if (defined $self->{pstable}->{$pid} && $pname eq $self->{pstable}->{$pid}) { 234 $self->{pids}->{$pid} = $pname; 235 } elsif (defined $self->{pstable}->{$pid} && $self->{pstable}->{$pid} =~ /perl/) { 236 # We can get 'perl' for a perl script. 237 $self->{pids}->{$pid} = $pname; 238 } elsif (defined $self->{pstable}->{$pid}) { 239 debug("pid $pid doesn't match: " . $pname . " != " . $self->{pstable}->{$pid}); 240 print "pid $pid doesn't match: ", $pname, " != ", $self->{pstable}->{$pid}, "\n" if $self->{verbose}; 241 } 242 } elsif ($line =~ /^INFO .* pid-done (\d*)$/) { 243 my $pid = $1; 244 print "pid $pid is done\n" if $self->{verbose}; 245 delete $self->{pids}->{$pid}; 246 } 247 } 248 close(LOGFILE); 249 250 # log unexpected dead process 251 if ($self->{verbose}) { 252 for my $pid (keys %{$self->{pids}}) { 253 if (!defined $self->{pstable}->{$pid}) { 254 print "pid $pid is dead\n"; 255 } 256 } 257 } 258} 259 260# Recursive function to add all child processes of $pid to %amprocess. 261# 262# Prerequisites: 263# - %ppid must be set (load_ps_table) 264# 265# Side-effects: 266# - adds all child processes of $pid to %amprocess 267# 268# @param $pid: the process to start at 269# 270sub add_child_pid($); 271sub add_child_pid($) { 272 my $self = shift; 273 my $pid = shift; 274 foreach my $cpid (keys %{$self->{ppid}}) { 275 my $ppid = $self->{ppid}->{$cpid}; 276 if ($pid == $ppid) { 277 if (!defined $self->{amprocess}->{$cpid}) { 278 $self->{amprocess}->{$cpid} = $cpid; 279 $self->add_child_pid($cpid); 280 } 281 } 282 } 283} 284 285# Find all children of all amanda processes, as determined by traversing 286# the process graph (via %ppid). 287# 288# Prerequisites: 289# - %ppid must be set (load_ps_table) 290# - %pids must be set (scan_log) 291# 292# Side-effects: 293# - sets %amprocess to a map (pid => pid) of all amanda processes, including 294# children 295# 296sub add_child() { 297 my $self = shift; 298 foreach my $pid (keys %{$self->{pids}}) { 299 if (defined $pid) { 300 $self->{amprocess}->{$pid} = $pid; 301 } 302 } 303 304 foreach my $pid (keys %{$self->{pids}}) { 305 $self->add_child_pid($pid); 306 } 307} 308 309# Set master_pname and master_pid. 310# 311# Side-effects: 312# - sets $self->{master_pname} and $self->{master_pid}. 313# 314sub set_master_process { 315 my $self = shift; 316 my $arg = shift; 317 my @pname = @_; 318 319 my $ps_argument_args = $Amanda::Constants::PS_ARGUMENT_ARGS; 320 for my $pname (@pname) { 321 my $pid; 322 323 if ($ps_argument_args eq "CYGWIN") { 324 $pid = `ps -ef|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$2}'`; 325 } else { 326 $pid = `$Amanda::Constants::PS $ps_argument_args|grep -w ${pname}|grep -w ${arg}| grep -v grep | awk '{print \$1}'`; 327 } 328 chomp $pid; 329 if ($pid ne "") { 330 $self->set_master($pname, $pid); 331 } 332 } 333} 334 335# Set master_pname and master_pid. 336# 337# Side-effects: 338# - sets $self->{master_pname} and $self->{master_pid}. 339# 340sub set_master($$) { 341 my $self = shift; 342 my $pname = shift; 343 my $pid = shift; 344 345 $self->{master_pname} = $pname; 346 $self->{master_pid} = $pid; 347 $self->{pids}->{$pid} = $pname; 348} 349# Send a signal to all amanda process 350# 351# Side-effects: 352# - All amanda process receive the signal. 353# 354# Prerequisites: 355# - %amprocess must be set (add_child) 356# 357# @param $signal: the signal to send 358# 359sub kill_process($) { 360 my $self = shift; 361 my $signal = shift; 362 363 foreach my $pid (keys %{$self->{amprocess}}) { 364 print "Sendding $signal signal to pid $pid\n" if $self->{verbose}; 365 kill $signal, $pid; 366 } 367} 368 369# Count the number of processes in %amprocess that are still running. This 370# re-runs 'ps -e' every time, so calling it repeatedly may result in a 371# decreasing count. 372# 373# Prerequisites: 374# - %amprocess must be set (add_child) 375# 376# @returns: number of pids in %amprocess that are still alive 377sub process_running() { 378 my $self = shift; 379 380 $self->load_ps_table(); 381 my $count = 0; 382 foreach my $pid (keys %{$self->{amprocess}}) { 383 if (defined $self->{pstable}->{$pid}) { 384 $count++; 385 } 386 } 387 388 return $count; 389} 390 391# Count the number of processes in %amprocess. 392# 393# Prerequisites: 394# - %amprocess must be set (add_child) 395# 396# @returns: number of pids in %amprocess. 397sub count_process() { 398 my $self = shift; 399 400 return scalar keys( %{$self->{amprocess}} ); 401} 402 403# return if a process is alive. If $pname is provided, 404# only returns 1 if the name matches. 405# 406# Prerequisites: 407# - %pstable must be set (load_ps_table) 408# 409# @param $pid: the pid of the process 410# @param $pname: the name of the process (optional) 411# 412# @returns: 1 if process is alive 413# '' if process is dead 414 415sub process_alive() { 416 my $self = shift; 417 my $pid = shift; 418 my $pname = shift; 419 420 if (defined $pname && defined $self->{pstable}->{$pid}) { 421 return $self->{pstable}->{$pid} eq $pname; 422 } else { 423 return defined $self->{pstable}->{$pid}; 424 } 425} 426 4271; 428