#
# Copyright (c) 2004, 2007, Oracle. All rights reserved.
#
# NAME
# procResUtil.pl
#
# DESCRIPTION
# Metric script that monitors CPU and resident memory utilization of processes
#
#
# OUTPUT:
#
# Zero or more lines of:
# em_result=prog_name|owner|prog_max_cpu_util|prog_max_cpu_util_pid|prog_total_cpu_util|prog_max_cpu_time|prog_max_cpu_time_pid|prog_total_cpu_time|prog_max_rss|prog_max_rss_pid|prog_process_count
#
# NOTES
# Supported platforms: Solaris, HP-UX, AIX and Linux.
#
# Usage: progResUtil.pl
#
# Input: inputs are agentConditionContext environment variables, which specify program criteria
# in terms of program name and owner name
#
#
# MODIFIED (MM/DD/YY)
# ajayshar 09/29/06 - Backport ajayshar_bug-5451828 from main
# snandarg 01/22/07 - Bug 5837452
# ajayshar 09/28/06 - Bug#5451828 - making the script portable This includes:
# bug#5053887 ,
# cpu Time format changes for SOL and HP-UX ,
# AIX support for the metrics and cpucount support for HP-UX,AIX and SOL
# removed reference to hostGenFunctins.pl
#
# sreddy 06/21/05 - fix bug#4442004
# sacgoyal 01/25/05 - moved getCpuCount() method to hostGenFunctins.pl
# sacgoyal 01/25/05 - update computation of %cpu utilization
# sacgoyal 11/23/04 - remove warning
# sacgoal 10/03/04 - correction in getCpuCount(), remove pid-list from
# em_result, fix of "duplicate key-value set bug".
# sacgoyal 07/22/04 - Creation for Agent Condition Context,
# Enterprise Manager, 10.2
#
#
use strict;
use File::Basename;
require "conditionContext.pl";
require "emd_common.pl";
require "semd_common.pl";
my $conditionContextAref = &getConditionContext;
my %key_value_set = ();
my %previous_cputime = ();
my %current_cputime = ();
my $previous_timestamp = "";
my $current_timestamp = "";
$conditionContextAref = &addDefaultConditionContext( $conditionContextAref );
if ($#$conditionContextAref < 0)
{
raise_error_and_exit("No condition context passed.", 1);
}
# ------------------------------------------------------------------------
# determine OS type and run ps get the list of processes and their
# characteristics.
# ------------------------------------------------------------------------
my $pgSize; # page size in bytes on HP-UX
my $os;
if (($os = get_osType())==-1)
{
raise_error_and_exit("Unsupported OS", 2);
}
my $isTestMode=0;
my @psList = (); # list of processes
my %ps_command = (
"SOL" => '/usr/bin/ps -eo "pid user pcpu rss time args"',
"LNX" => '/bin/ps -eo "pid user pcpu rss time args"',
"HP" => '/usr/bin/ps -eo "pid user pcpu sz time args"',
"AIX" => '/usr/sysv/bin/ps -eo "pid user pcpu rss time args"',
"OSF1" => '/bin/ps -eo "pid user pcpu rss time args"'
);
$isTestMode=1 if ($ENV{EM_TEST_MODE});
if ($ARGV[0])
{
unless (open(INPUT, $ARGV[0]))
{
raise_error_and_exit("$ARGV[0] file couldn't be opened",3);
}
@psList = ;
close(INPUT);
$isTestMode=1;
}
else
{
if ($os eq "HP")
{
$ENV{UNIX95} = "XPG4";
$pgSize=`getconf SC_PAGE_SIZE`;
if ($? != 0)
{
raise_error_and_exit("Failed to run getconf command", 4);
}
}
if (!defined($ps_command{$os}))
{
raise_error_and_exit("Unsupported OS", 5);
}
@psList = `$ps_command{$os}`;
if ($? != 0)
{
raise_error_and_exit("Failed to run ps command", 6);
}
$current_timestamp = time;
}
shift @psList; # remove ps header line
getPreviousState(); # get the previous state for %cpu utilization computation
# ------------------------------------------------------------------------
# screen the processes in the process list based on monitoring criteria.
# compute and keep track of total CPU percentage and max resident memory
# used by each user-specified process. also keep track of the PIDs.
# ------------------------------------------------------------------------
foreach my $conditionHref (@$conditionContextAref)
{
my $keysAref = ${$conditionHref}{"keyColumnAref"};
if ($#{$keysAref} < 0 )
{
next;
}
my ($progKeyToMatch,$progKeyToReturn,$progKeyOperator)=("","","");
my ($userKeyToMatch,$userKeyToReturn,$userKeyOperator)=("","","");
foreach my $keyHref (@$keysAref)
{
if (${$keyHref}{"keyName"} eq "prog_name")
{
$progKeyToMatch = ${$keyHref}{"keyValueToMatch"};
$progKeyToReturn = ${$keyHref}{"keyValueToReturn"};
$progKeyOperator = ${$keyHref}{"keyOperator"};
}
elsif (${$keyHref}{"keyName"} eq "owner")
{
$userKeyToMatch = ${$keyHref}{"keyValueToMatch"};
$userKeyToReturn = ${$keyHref}{"keyValueToReturn"};
$userKeyOperator = ${$keyHref}{"keyOperator"};
}
else
{
EMD_PERL_ERROR("Unknown Key Column: ${$keyHref}{'keyName'}");
}
}
if ($progKeyToReturn eq "" && $userKeyToReturn eq "")
{
EMD_PERL_ERROR("Skipping, Required Key Columns are null");
next;
}
# When User hasn't entered one of two keys, then manually assigning them to "%".
# so that in performance-UI page, "ALL" can be put wherever "%" is found in the column value.
# These lines might required to be deleted.
if($progKeyToReturn eq "")
{
$progKeyToReturn = "%";
}
if($userKeyToReturn eq "")
{
$userKeyToReturn = "%";
}
# Checking whether this key-value set is already included
if ( $key_value_set{$progKeyToReturn}{$userKeyToReturn} == 1 )
{
next;
}
# Creating record that current key-value set is included
$key_value_set{$progKeyToReturn}{$userKeyToReturn} =1;
if ($isTestMode)
{
print("progKeyToMatch=[$progKeyToMatch], progKeyOperator=$progKeyOperator, progKeyToReturn=[$progKeyToReturn], userKeyToMatch=[$userKeyToMatch], userKeyOperator=$userKeyOperator, userKeyToReturn=[$userKeyToReturn]\n");
}
EMD_PERL_DEBUG("progKeyToMatch=[$progKeyToMatch], progKeyOperator=$progKeyOperator, progKeyToReturn=[$progKeyToReturn], userKeyToMatch=[$userKeyToMatch], userKeyOperator=$userKeyOperator, userKeyToReturn=[$userKeyToReturn]");
my $maxPcntCpu = 0; # running max CPU percentage
my $maxPcntCpuPid = ""; # PID of the process instance with max Cpu Utilization
my $totPcntCpu = 0; # running total CPU percentage
my $maxCpuTime = 0; # running max CPU time
my $maxCpuTimePid = ""; # PID of the process instance with max Cpu time
my $totCpuTime = 0; # running total CPU time
my $maxMem = 0; # max resident memory by a process instance
my $maxMemPid = ""; # PID of the process instance with max resident memory usage
my $maxMemMB = 0;
my $instanceCount = 0; # instance counter
my $pidStr = ""; # string that holds PIDs
my $command = ""; # Command which appear for the program in ps command output
my $flagCputime = 0; # Flag, so that "storing of Cpu Times of all processes" is done only once.
foreach my $psLine( @psList )
{
$psLine =~ s/^\s+//; # remove any leading space
chomp($psLine);
my ($pid, $user, $pcntCpu, $resMem, $cpuTime, @cmdArgs);
my $resMemPg; # resident memory in pages (HP-UX)
my $computeFlag = 0;
if ( $os eq "HP")
{
($pid, $user, $pcntCpu, $resMemPg, $cpuTime, @cmdArgs) = split(/\s+/,$psLine);
$resMem = ($resMemPg * ($pgSize/1024));
}
else
{
($pid, $user, $pcntCpu, $resMem, $cpuTime, @cmdArgs) = split(/\s+/,$psLine);
}
if( substr( $progKeyToReturn,0,1) ne '[' and substr($progKeyToReturn,-1,1) ne ']' and
substr( $cmdArgs[0],0,1) eq '[' and substr($cmdArgs[0],-1,1) eq ']' )
{ #For defunct, zombie processes who appear in [ProcessName -argument] format; and user-specified progName doesn't contain []
$command = substr($cmdArgs[0],1) ;
chop ($command);
}
else
{
#$command = basename($cmdArgs[0], "");
$command = $cmdArgs[0];
}
#------------------------------------------------------------------------------
# compute the running max and total of cpu TIME & max cpu-TIME- PID
#bug 5053887 - getting a blank line
next if ($cpuTime =~ /^$/);
my @cpuTimeArray = split(":",$cpuTime);
my $days = 0;
my $hours = 0;
my $minutes = 0;
my $seconds = 0;
# For solaris and HP-UX cases, the format may be MM:SS or M:SS in some cases
# Handle those cases
my $timeNo=@cpuTimeArray;
if ($timeNo eq 2)
{
$minutes = $cpuTimeArray[0];
$seconds = $cpuTimeArray[1];
}
elsif ( !( ($cpuTimeArray[0] =~ /^[\d]{2}$/) and ($cpuTimeArray[1] =~ /^[\d]{2}$/) and
($cpuTimeArray[2] =~ /^[\d]{2}$/) ) and !( $cpuTimeArray[0] =~ /^[\d]+-[\d]{2}$/) )
{
raise_error_and_exit("Unexpected format for cpuTime = $cpuTime", 7);
}
else
{
$minutes = $cpuTimeArray[1];
$seconds = $cpuTimeArray[2];
}
# Handle the case ddd-hhh when the accumulated time is > 24 hours
if ($cpuTimeArray[0] =~ /^[\d]+-[\d]{2}$/)
{
@cpuTimeArray = split("-",$cpuTimeArray[0]);
$days = $cpuTimeArray[0];
$hours = $cpuTimeArray[1];
}
else
{
if ($timeNo eq 2)
{
$hours =0;
}
else
{
$hours = $cpuTimeArray[0];
}
}
my $cpuTimeInSeconds = $days*24*3600 + $hours*3600 + $minutes*60 +$seconds;
if ($flagCputime == 0)
{
my $key = $command . "," . $user . "," . $pid;
$current_cputime{$key} = $cpuTimeInSeconds;
}
if( ($command =~ /^$progKeyToMatch$/) and ($user =~ /^$userKeyToMatch$/) )
{
#--------------------------------------------------------------------------
#Computation of Cpu Utilisation
my $key = $command . "," . $user . "," . $pid;
if ( $previous_cputime{$key} && $previous_timestamp && ($current_timestamp - $previous_timestamp)>0 && ($cpuTimeInSeconds - $previous_cputime{$key} ) >= 0)
{
$pcntCpu = sprintf("%.2f", (($cpuTimeInSeconds - $previous_cputime{$key} ) *100) / ($current_timestamp - $previous_timestamp));
}
#-------------------------------------------------------------------------------
# compute the running max and total of cpu utilization & max cpu-utilization-PID
if ($pcntCpu >= $maxPcntCpu )
{
$maxPcntCpu = $pcntCpu;
$maxPcntCpuPid = $pid;
}
$totPcntCpu += $pcntCpu;
#------------------------------------------------------------------------------
# compute the running max and total of cpu TIME & max cpu-TIME- PID
if ($cpuTimeInSeconds >= $maxCpuTime)
{
$maxCpuTime = $cpuTimeInSeconds;
$maxCpuTimePid = $pid;
}
$totCpuTime += $cpuTimeInSeconds;
#-------------------------------------------------------------------------------
# keep track of max resident memory and the PID of this process
if ( $resMem >= $maxMem )
{
$maxMem = $resMem;
$maxMemPid = $pid;
}
#--------------------------------------------------------------------------------
# append the PID of this process instance to PID list, & increment instance-count
$pidStr .= "$pid\,";
$instanceCount++;
next;
}
}# end: inner foreach
$flagCputime = 1; # so that "storing of Cpu Times of all processes" is done only once.
# ---------------------------------------------------------------------
# all processes have been screened based on this rule.
# output results
# ---------------------------------------------------------------------
if ( $pidStr ne "" )
{
$pidStr =~ s/,$//; # remove trailing comma (,)
}
if ($maxMem >= 0)
{
# convert size in KB to size in MB and round up to 2 decimals
$maxMemMB = sprintf("%.2f",$maxMem / 1024);
}
my $cpuCount = getCpuCount();
$totPcntCpu = $totPcntCpu /$cpuCount;
$maxPcntCpu = $maxPcntCpu/$cpuCount;
$maxCpuTime = sprintf("%.2f",$maxCpuTime/60);
$totCpuTime = sprintf("%.2f",$totCpuTime/60);
$maxPcntCpu = sprintf("%.2f",$maxPcntCpu);
$totPcntCpu = sprintf("%.2f",$totPcntCpu);
if (!$isTestMode)
{
print "em_result=$progKeyToReturn|$userKeyToReturn|$maxPcntCpu|$maxPcntCpuPid|$totPcntCpu|$maxCpuTime|$maxCpuTimePid|$totCpuTime|$maxMemMB|$maxMemPid|$instanceCount\n";
}
else
{
print("em_result=prog_name=$progKeyToReturn|owner=$userKeyToReturn|maxPcntCpu=$maxPcntCpu|maxPcntCpuPid=$maxPcntCpuPid|totPcntCpu=$totPcntCpu|maxCpuTime=$maxCpuTime|maxCpuTimePid=$maxCpuTimePid|totCpuTime=$totCpuTime|maxMemMB=$maxMemMB|maxMemPid=$maxMemPid|instanceCount=$instanceCount\n");
}
# EMD_PERL_DEBUG("em_result=prog_name=$progKeyToReturn|owner=$userKeyToReturn|maxPcntCpu=$maxPcntCpu|maxPcntCpuPid=$maxPcntCpuPid|totPcntCpu=$totPcntCpu|maxCpuTime=$maxCpuTime|maxCpuTimePid=$maxCpuTimePid|totCpuTime=$totCpuTime|maxMemMB=$maxMemMB|maxMemPid=$maxMemPid|instanceCount=$instanceCount|pidStr=$pidStr");
} # end of outer foreach
saveStateFile(); # save the state for %cpu utilization computation
#############################################################################
#-----------------------getCpuCount--------------------
#############################################################################
sub getCpuCount()
{
# Support Multiple platforms for getCpuCount method
if ($os eq "LNX")
{
my @processorLines = `grep '^processor[[:space:]]*:' /proc/cpuinfo`;
if ($? != 0)
{
EMD_PERL_ERROR("error in grep from /proc/cpuinfo file");
}
elsif ( @processorLines > 1 )
{
return @processorLines;
}
return 1;
}
elsif ($os eq "SOL")
{
my @processorLines = `/usr/sbin/psrinfo`;
if ($? != 0)
{
EMD_PERL_ERROR(" error during psrinfo");
}
elsif ( @processorLines > 1 )
{
my $cpuCount = @processorLines;
return $cpuCount;
}
return 1;
}
elsif ($os eq "HP")
{
my $cpuCount= ` /sbin/ioscan/ -kC processor | grep processor | wc -l`;
if ($? != 0)
{
EMD_PERL_ERROR(" error during /sbin/ioscan/");
}
elsif ($cpuCount > 1)
{
return $cpuCount;
}
return 1;
}
elsif ($os eq "AIX")
{
my $cpuCount = `lsdev -C|grep Process|wc -l`;
if ($? != 0)
{
EMD_PERL_ERROR(" error during lsdev");
}
elsif ($cpuCount > 1)
{
return $cpuCount;
}
return 1;
}
return 1;
}
#############################################################################
#-----------------------addDefaultConditionContext--------------------
#############################################################################
sub addDefaultConditionContext ()
{
my ($conditionContextAref )= @_;
my @currentKeys = ();
my %currentKey1 = ("keyName" => "prog_name",
"keyOperator" => "1", # LIKE is defined as 1
"keyValueToReturn" => "%",
"keyValueToMatch" => ".*");
push(@currentKeys, \%currentKey1);
my %currentKey2 = ("keyName" => "owner",
"keyOperator" => "1", # LIKE is defined as 1
"keyValueToReturn" => "%",
"keyValueToMatch" => ".*");
push(@currentKeys, \%currentKey2);
my %currentCondition = ("conditionColumnName" => "",
"conditionOperator" => "",
"criticalThreshold" => "",
"warningThreshold" => "",
"keyColumnAref" => \@currentKeys);
push @{$conditionContextAref}, \%currentCondition;
return $conditionContextAref;
}
sub raise_error_and_exit()
{
my ($message, $exit_status) = @_;
EMD_PERL_ERROR($message);
print STDERR "$message \n";
exit $exit_status;
}
#############################################################################
#-----------------------getStateFileName--------------------
#############################################################################
sub getStateFileName
{
unless( exists $ENV{EMSTATE} or defined $ENV{EMSTATE} )
{
&raise_error_and_exit("The environment variable EMSTATE needs to be set in order to run progResUtil.pl",2);
}
my $os;
if (($os = get_osType()) eq "-1")
{
&raise_error_and_exit("Unsupported OS", 20);
}
my $separator = '/'; #default to UNIX path seperator
$separator = '\\' if($os eq "WIN");
my $agentStateDir = $ENV{EMSTATE};
EMD_PERL_DEBUG("progResUtil.pl EMSTATE directory is $agentStateDir\n");
my $separator = $^O =~ m/MSWin32/ ? "\\" : "\/";
my $stateFile = $agentStateDir.$separator."sysman"."$separator"."emd"."$separator"."state"."$separator"."progResUtil.log";
return $stateFile;
}
#############################################################################
#-----------------------getPreviousState--------------------
#############################################################################
sub getPreviousState
{
my $stateFileName = getStateFileName();
open(STATE, "< $stateFileName");
my @stateLines = ;
close STATE;
# This state file is maintained for %cpu-utilization calculation. Its format is -
# first line contains the "previous time_stamp"
# remaining lines contains following entries-> "command,user,pid|||totCpuTimeInSeconds"
$previous_timestamp = shift @stateLines;
chomp $previous_timestamp;
foreach my $process_state (@stateLines)
{
chomp($process_state);
if ($process_state)
{
my @tokens = split ('\|\|\|', $process_state);
$previous_cputime{$tokens[0]} = $tokens[1];
}
}
}
#############################################################################
#-----------------------saveStateFile--------------------
#############################################################################
sub saveStateFile
{
my $stateFileName = getStateFileName();
open(STATE, "> $stateFileName");
my $prog_owner_pid;
print STATE "$current_timestamp\n";
foreach $prog_owner_pid (keys %current_cputime)
{
print STATE "$prog_owner_pid|||$current_cputime{$prog_owner_pid}\n";
}
close(STATE);
}