#
# 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 = <INPUT>;
  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 = <STATE>;
    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);
}
