#!/bin/sh
# Description: Basic CPU load check
# Author: Swift Griggs <sgriggs@parsec.com>
# 
# [Log]
#   Apr-2021: Initial version written for demonstration during MEP webinar at PARSEC
#

# Thresholds for load.  The first is the load value you want to alert on. 
# The second is the amount of time that level of load should persist before
# we actually send an alert (in seconds).  Default is to set the level to
# the same as the number of CPUs if the value is left at 0.  Otherwise, the
# actual non-zero value is used to set the alert level.
ALERT_LOAD_LEVEL=0
ALERT_LOAD_TIME=900

# Do not depend on an upstream alerter.  Send directly from this script. 
# Set to "NO" for disable or enter your preferred email to enable.
EMAIL_ALERTS=NO

# Globals in use
LOAD60=0
LOAD300=0
LOAD900=0
RETURN_VALUE=0

# Make sure we have what we need to run the script
sanity_check(){
  which w >/dev/null 2>&1
  if [ $? -ne 0 ]; then
    echo "ERROR: Cannot find the 'w' command!"
    exit 1 # Unique error for each problem is important upstream
  fi 
  
  # make sure we can create files in /tmp
  echo testing > /tmp/test.$$
  if [ $? -ne 0 ]; then
    # Something is wrong with /tmp
    echo "ERROR: Cannot create a test file in /tmp!"
    exit 2
  else
    rm /tmp/test.$$
  fi 

  which mailx >/dev/null 2>&1
  if [ $? -ne 0 ]; then
    echo "WARNING: The 'mailx' program is not in your path. This system won't be able to send email alerts!"
  fi 

  which bc >/dev/null 2>&1
  if [ $? -ne 0 ]; then
    echo "ERROR: The 'bc' program is not in your path. Floating point math won't work."
    exit 4
  fi 
  
  # we need to check to see if the alert level is the default
  # if so, we should set this value to the same as the number 
  # of CPUs that the system has.
  if [ $ALERT_LOAD_LEVEL -eq 0 ]; then
    # It's the default. Find the number of CPUs in the system
    NUM_CPUS=`detect_cpu_threads`
    ALERT_LOAD_LEVEL=$NUM_CPUS
  fi 
}

detect_cpu_threads(){

  # Honor any custom value that was set
  if [ $ALERT_LOAD_LEVEL -ne 0 ]; then
    echo $ALERT_LOAD_LEVEL
    return
  fi 

  # Find a sane alert-default based on the number of CPUs
  MYOS=`uname -s`
  case $MYOS in
  Linux)
    cat /proc/cpuinfo | grep flags | wc -l
    ;;
  NetBSD)
    /sbin/sysctl -n hw.ncpu
    ;;
  OpenBSD)
    /sbin/sysctl -n hw.ncpu
    ;;
  FreeBSD)
    /sbin/sysctl -n hw.ncpu
    ;;
  SunOS)
    # All versions of Solaris, but not "real" SunOS
    /usr/sbin/psrinfo | grep line | wc -l
    ;;
  AIX)
    lsdev -Cc processor | wc -l
    ;;
  OSF1)
    # Tru64, OSF1, and Digital Unix
    /usr/sbin/psrinfo | wc -l
    ;;
  IRIX)
    hinv | grep -i processor | wc -l
    ;;
  HP-UX)
    ioscan -kf | grep processor | grep CLAIMED | wc -l
    ;;
  OpenUNIX)
    # this is for Novell Unixware 
    /usr/sbin/psrinfo | wc -l
    ;;
  *)
    # Can't figure it out. Assume a single CPU
    echo 1
    ;;
  esac
}

get_cpu_load(){
  # create one data file instead of running w three times
  case $MY_OS in
    *)
      # Seems to work everywhere I've tried but I'm skeptical
      # it'll always be the case, thus the fru-fru here. 
      w | grep load | cut -f2 -d'v' | cut -f2 -d: > /tmp/load_average.$$
      ;;
  esac
    
  # 60 second load
  LOAD60=`cat /tmp/load_average.$$ | cut -f1 -d, | awk '{print $1}'`
  
  # 5 minute load
  LOAD300=`cat /tmp/load_average.$$ | cut -f2 -d, | awk '{print $1}'`
  
  # 15 minute load
  LOAD900=`cat /tmp/load_average.$$ | cut -f3 -d, | awk '{print $1}'`  
  
  # remove the temporary data file
  rm /tmp/load_average.$$
  
  # for debugging
  # echo "$LOAD60,$LOAD300,$LOAD900"
}

send_email_alert(){
  IS_EMAIL_ENABLED=`echo $EMAIL_ALERTS | grep '@' | wc -l`
  if [ $IS_EMAIL_ENABLED -ne 1 ]; then
    # Email feature is disabled. Do nothing.
    return 0
  fi 
  # Send the alert 
  echo $1 | mailx -s "CPU Alert ($LOAD900)" 
}


check_limits(){
 KNOW_CPU_NUM=`echo $ALERT_LOAD_LEVEL | egrep '[0-9]+' | wc -l`
 if [ $KNOW_CPU_NUM -eq 0 ]; then
   echo "ERROR: Script failed to set the number of CPUs properly. Unknown OS."
   exit 3
 fi
 
 # If the value is negative we have room to run.
 # If the value is positive, then we are overloaded
 LOAD_DECISION=`echo $LOAD900 - $ALERT_LOAD_LEVEL | bc`
 IS_OVERLOADED=`echo $LOAD_DECISION | grep -- - | wc -l`
 if [ $IS_OVERLOADED -ne 0 ]; then
   echo "OK"
 else
   echo $LOAD900
   MY_HOSTNAME=`uname -n`
   send_email_alert "Load is too high on $MY_HOSTNAME (15avg is $LOAD900 limit is $ALERT_LOAD_LEVEL)."
   RETURN_VALUE=900
 fi 

}


# main()
ALERT_LOAD_LEVEL=`detect_cpu_threads`
get_cpu_load
check_limits
exit $RETURN_VALUE
