Skip to content
Snippets Groups Projects
inc_pluginfunctions 17.97 KiB
#!/bin/bash
# ======================================================================
#
# NAGIOS CLIENT CHECK :: FUNCTIONS
#
# ----------------------------------------------------------------------
#
# Functions 
#   execIfReady COMMAND [SLEEPTIME] [MAXTRIES]
#                       execute a command max MAXTRIES times until it returns
#                       exitcode 0 (or reaches limit)
#   ph.getOS            get operating system as lowercase - centos|debian|ubuntu|...
#   ph.getOSMajor       get OS Major version as integer, i.e. 7 on a CentOS7
#   ph.getValueWithParam VALUE PARAMNAME "$@"
#                       return default value or its override from command line
#   ph.hasParamoption PARAMNAME "$@"
#                       check if a letter was used as command line option
#   ph.toUnit VALUE TO-UNIT
#                       convert value i.e. "12M" into other unit i.e. "K"
#   ph.setStatus VALUE  set a status
#   ph.setStatusByLimit VALUE WARNLIMIT CRITLIMIT
#   ph.status [TEXT]    show status as Text
#
#   ph.abort [TEXT]     shows error message and exit with status unknown 
#   ph.exit             exit plugin (with set statuscode)
#
# ----------------------------------------------------------------------
# 2016-09-23  added getOS
# 2019-10-29  added setExitcode
# 2020-03-05  v1.2  <axel.hahn@iml.unibe.ch>  switch to ph.* helper functions
# 2020-09-01  v1.3  <axel.hahn@iml.unibe.ch>  added ph.hasParamoption
# 2022-08-31  v1.4  <axel.hahn@iml.unibe.ch>  shellfix corrections
# 2022-10-25  v1.5  <axel.hahn@iml.unibe.ch>  handle empty value in ph.perfadd
# 2023-01-30  v1.6  <axel.hahn@unibe.ch>      check performance params 5+6 and show a warning if missing
# 2023-02-16  v1.7  <axel.hahn@unibe.ch>      adding a generic min and max value did not really help
# 2023-04-24  v1.8  <axel.hahn@unibe.ch>      fix unit conversion
# 2023-05-05  v1.9  <axel.hahn@unibe.ch>      user specific counter directory
# 2023-05-17  v1.10 <axel.hahn@unibe.ch>      ph.getOS searches in os-release first
# 2023-06-22  v1.11 <axel.hahn@unibe.ch>      fix ph.toUnit with float values; shell fixes
# 2023-08-24  v1.12 <axel.hahn@unibe.ch>      toUnit got 3rd param for count of digits after "."
# 2023-08-30  v1.13 <axel.hahn@unibe.ch>      reverse return code in ph.hasParamoption to unix like return codes: 0=true; <>0 = false
# 2023-09-05  v1.14 <axel.hahn@unibe.ch>      ph.require - show error below status line
# 2023-09-14  v1.15 <axel.hahn@unibe.ch>      add ph.showtimer; fix broken pipe messages in journallog
# ======================================================================


# nagios exit code values
typeset -i ph_cfg__EXIT_OK=0
typeset -i ph_cfg__EXIT_WARNING=1
typeset -i ph_cfg__EXIT_CRITICAL=2
typeset -i ph_cfg__EXIT_UNKNOWN=3

typeset -i ph_cfg__EXIT_CODE

ph_timer_start=$( date +%s.%N )

declare ph_perfdatafile=

# abort a check and exit with status "unknown"
function ph.abort(){
  ph.setStatus "unknown"
  echo "$*"
  ph.exit
}

# check required binaries in the path
# param(s)  string  name of binary to check with "which" command
function ph.require(){
  local _out;
  if ! _out=$( which $* 2>&1 ); then
      ph.setStatus "unknown"
      ph.status "$0 requires the following tools to run: $*"
      echo "$_out"
      ph.exit
  fi
}

# get time in sec and milliseconds since start
# no parameter is required
function ph.showtimer(){
  local timer_end; timer_end=$( date +%s.%N )
  local totaltime; totaltime=$( awk "BEGIN {print $timer_end - $ph_timer_start }" )
  local sec_time; sec_time=$( echo "$totaltime" | cut -f 1 -d "." )
  test -z "$sec_time" && sec_time=0

  local ms_time; ms_time=$( echo "$totaltime" | cut -f 2 -d "." | cut -c 1-3 )

  echo "$sec_time.$ms_time sec"
}

# ----------------------------------------------------------------------
# exit a check plugin
function ph.exit(){
  # echo ______________________________________________________________________
  # echo "DEBUG: $0 $* -- leaving with _rc = $ph_cfg__EXIT_CODE -- Status $ph_cfg__EXIT_STATUS"
  ph.perfshow
  exit $ph_cfg__EXIT_CODE
}

# ----------------------------------------------------------------------
# detect LINUX DISTRO as lowercase
# returns one of centos|debian|ubuntu|...
function ph.getOS(){
  local distro=

  if [ -z "$distro" ]; then
    # centos7, debian, manjaro, ubuntu
    distro=$( grep "^ID=" /etc/os-release | cut -f 2 -d "=" )
  fi

  if [ -z "$distro" ]; then
    distro=$( grep "^ID=" /etc/*-release | cut -f 2 -d "=" )
  fi

  if [ -z "$distro" ]; then
    # debian6,7, ubuntu 10,12 .. maybe unneeded.
    distro=$( head -1 /etc/issue | grep "^[a-zA-Z]" | cut -f 1 -d " " )
  fi

  # sanitize: lowercase, remove "
  distro=$( echo "$distro" | tr -d '"' | tr [:upper:] [:lower:] )

  if [ -z "$distro" ]; then
    ph.abort "UNKNOWN: distro was not detected."
    
  fi

  echo "$distro"
}

# get OS MajorRelease
# returns an integer, i.e. 7 on CentOS7
function ph.getOSMajor(){
  local _version=
  _version=$( grep -E "^(VERSION_ID|DISTRIB_RELEASE)=" /etc/*-release | head -1 | cut -f 2 -d "=" | sed 's#"##g' | cut -f 1 -d "." )
  if [ -z "$_version" ]; then
    _version="?"
    exit 1
  fi
  echo "$_version"
}

# helper to use the default _value or override it with a found param
# getValueWithParam [default] [parameter] "$@"
function ph.getValueWithParam(){
  local _value=$1
  local _sParam=$2
  local _opt
  shift 2

  # trick I: allows ${_sParam} in case .. esac section
  shopt -s extglob 

  # trick II: allows usage of getopts multiple times
  OPTIND=1

  # trick III: changing getops params with a single param do not work ... 
  # while getopts ":$_sParam:" opt; do
  while getopts ":a:b:c:d:e:f:g:h:i:j:k:l:m:n:o:p:q:r:s:t:u:v:w:x:y:z:" _opt; do
    # echo "DEBUG: testing ${opt} ..."
    case "${_opt}" in
      $_sParam)
        _value=$OPTARG
        ;;
    esac
  done
  echo "$_value"
}

# check if a letter was used as command line option and return as 0 (=no) or 1 (=yes)
# ph.hasParamoption "h" "$@"
# param  string  parameter(letter) to test
# param  string  "$@"
function ph.hasParamoption(){
  local _sParam=$1
  local _opt
  shift 1

  # trick I: allows ${_sParam} in case .. esac section
  shopt -s extglob 

  # trick II: allows usage of getopts multiple times
  OPTIND=1

  # trick III: changing getops params with a single param do not work ... 
  # while getopts ":$_sParam:" opt; do
  while getopts "abcdefghijklmnopqrstuvwxyz" _opt; do
    # echo "DEBUG: testing $_sParam in ${_opt} ..."
    case "${_opt}" in
      "$_sParam")
        # echo "0"
        return 0
        ;;
    esac
  done
  # echo "1"
  return 1
}


# set an exitcode for the plugin
#
# example:
# in your plugin set an exitcode:
#   ph.setStatus "ok"
#
# param  integer|string  0..3 or ok|warning|critical|unknown
function ph.setStatus(){
  case $1 in
    "$ph_cfg__EXIT_OK"|"ok"):
        ph_cfg__EXIT_CODE=$ph_cfg__EXIT_OK
        ph_cfg__EXIT_STATUS="OK"
      ;;
    "$ph_cfg__EXIT_WARNING"|"warning"):
        ph_cfg__EXIT_CODE=$ph_cfg__EXIT_WARNING
        ph_cfg__EXIT_STATUS="WARNING"
      ;;
    "$ph_cfg__EXIT_CRITICAL"|"critical"):
        ph_cfg__EXIT_CODE=$ph_cfg__EXIT_CRITICAL
        ph_cfg__EXIT_STATUS="CRITICAL"
      ;;
    "$ph_cfg__EXIT_UNKNOWN"|"unknown"):
        ph_cfg__EXIT_CODE=$ph_cfg__EXIT_UNKNOWN
        ph_cfg__EXIT_STATUS="UNKNOWN"
      ;;
    *)
        ph.abort "ERROR: wrong usage ... Status code [$1] is unknown ... please fix the plugin $0."
  esac
}


# set exit status by given _value and limits for warning and critical
# it works in both directions with limits on lower end and higher end to
# The mode is detected by given warning and critical limit
# param  integer   _value of a test
# param  integer   warning level
# param  integer   critical level
function ph.setStatusByLimit(){
  typeset -i local _value=$1
  typeset -i local _iWarnLimit=$2
  typeset -i local _iCriticalLimit=$3

  if [ $_iWarnLimit -gt $_iCriticalLimit ]; then
    _iWarnLimit=-$_iWarnLimit
    _iCriticalLimit=-$_iCriticalLimit
    _value=-$_value
  fi

  if [ $_value -lt $_iWarnLimit ]; then
      ph.setStatus "ok"
  else
      if [ $_value -ge $_iCriticalLimit ]; then
          ph.setStatus "critical"
      else
          ph.setStatus "warning"
      fi
  fi
}


# show status as OK|WARNING|CRITICAL|UNKNOWN
# if you add params it will be shown behind with added carriage return
# without additional params there is NO carriage return
#
# Example
#   ph.status 
#   echo what was tested here
# is the same like
#   ph.status "what was tested here"
#
function ph.status(){
  echo -n "${ph_cfg__EXIT_STATUS}"
  test $# -gt 0 && echo ": $*"
}

# ----------------------------------------------------------------------
#
# convert binary units
#
# ----------------------------------------------------------------------

# helper function for ph.toUnit: get scaling factor
# example: 12M returns 2^30 from ending "M"
# param  value  with ending scale [none]=1 K=Kilo M=Mega G=Giga
function ph._getExp(){
    local _unit
    _unit=${1//[0-9\.]/}

    test -z "$_unit" && echo 1

    # binary
    test "$_unit" = "K" && echo 2^10
    test "$_unit" = "M" && echo 2^20
    test "$_unit" = "G" && echo 2^30
    test "$_unit" = "T" && echo 2^40
    test "$_unit" = "P" && echo 2^50

    # phyiscal
    # test "$_unit" = "m" && echo 10^-3
    # test "$_unit" = "c" && echo 10^-2
    # test "$_unit" = "d" && echo 10^-1
    test "$_unit" = "k" && echo 10^3
    test "$_unit" = "Mi" && echo 10^6
    test "$_unit" = "Gi" && echo 10^9
    test "$_unit" = "Ti" && echo 10^9
    test "$_unit" = "Pi" && echo 10^9

    # echo "ERROR: unsupported value: $_value"
    # exit 1
}

# convert a given binary value into another unit
# example $( ph.toUnit 12M K ) converts 12 M(ega) into K(ilo) 
# --> return value will be 12288 (without K as suffix)
#
# param  string  value with optional single letter for unit, i.e. 12M
# param  char    target unit
# param  integer count of digits after "."; default: none (=integer value)
function ph.toUnit(){
    local _value=$1
    local _unit=$2
    local _digits=${3:-0}
    local _dots

    if [ "$_value" -eq "0" ]; then
      echo "0"
      return 0
    fi

    local _multiply; _multiply=$( ph._getExp "$_value" )
    local _divisor;  _divisor=$(  ph._getExp "$_unit"  )

    local _bc
    _bc="bc"


    test $_digits -gt 0 && _dots=$( yes "." 2>/dev/null | head -$_digits | tr -d "\n" )
    test $_digits -gt 0 && _bc+=" -l | grep -o '.*\\.${_dots}'"

    echo "$(echo "$_value" | tr -d "[:alpha:]" )*( ${_multiply}/$_divisor )" | eval "$_bc" | sed "s#^\.#0.#"
    
}

# ----------------------------------------------------------------------
#
# performance data
#
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# PRIVATE FUNCTIONS
# ----------------------------------------------------------------------

# get age of a file in sec
# param  string  filename to test
#
function ph.getFileAge(){
    echo $(($(date +%s) - $(date +%s -r "$1")))
}

# get file for storage of last value
# global  string  dir_data  custom path; default is /tmp/icinga_counter/
# param   string  varName   variable name of a value to store
function ph._getStorefile(){
        local varName=$1

        local mydir="/tmp/icinga_counter_${USER}"
        test -n "$dir_data" && mydir="${dir_data}/_counter_${USER}"
        local _basename
        _basename=$(basename $0)

        test -d "${mydir}" || mkdir -p "${mydir}"
        echo "${mydir}/${_basename}${varName}.lastvalue"
        
}

# save a value - needed for gdAddDeltaData
# param   string  varName   variable name of a value to store
# param   *       value     value as float/ integer
function ph._savecounter() {
        local varName=$1
        local value=$2
        local sStoreFile; sStoreFile=$(ph._getStorefile "${varName}")
        #echo "DEBUG: `date +%s`:${value} \> ${sStoreFile}"
        # echo "`date +%s`:${value}" > "${sStoreFile}"
        echo ${value} > "${sStoreFile}"
}

# get age of last storage of a value
# used in perfdeltaspeed
# param   string  varName   variable name of a value to store
function ph._getageoflastvalue() {
        local varName=$1
        local sStoreFile; sStoreFile=$(ph._getStorefile "${varName}")
        ph.getFileAge "${sStoreFile}"
        # local ilast=`cat "${sStoreFile}" 2>/dev/null | cut -f 1 -d ":" `
        # local inow=`date +%s`
        # echo $(( ${inow}-${ilast}+1 ))
}
# get the last value
# used in perfdeltaspeed
# param   string  varName   variable name of a value to store
function ph._readlastvalue(){
        local varName=$1
        local sStoreFile; sStoreFile=$(ph._getStorefile "${varName}")
        # cat "${sStoreFile}" 2>/dev/null | cut -f 2 -d ":"
        cat "${sStoreFile}" 2>/dev/null
}

# init usage for performance data
# it creates a filename based on the started check script
function ph._perfinit(){
  local _basename
  _basename=$(basename $0)
  ph_perfdatafile="/tmp/perfdata_$(echo $_basename | sed "s#[^a-z0-9\-]#_#g")"
  rm -f "${ph_perfdatafile}" 2>/dev/null
}

# generate label for performance data value
function ph._getperflabel(){
  echo "$1" | tr [:upper:] [:lower:] | sed "s#[^a-z0-9\-]##g"
}

# get speed of change of a counter value
# param  string    variable name
# param  integer   value
# param  string    unit to calculate a speed value per min or per sec; one of s|sec or m|min; default: "s"
# param  string    optional: flag to return a float value
function ph.perfdeltaspeed(){
        local varName=$1
        local value=$2
        local deltaUnit=$3
        local isFloat=$4

        local bcParam=

        typeset -i deltaFactor=1

        test "$deltaUnit" = "s"   && deltaFactor=1
        test "$deltaUnit" = "sec" && deltaFactor=1
        test "$deltaUnit" = "m"   && deltaFactor=60
        test "$deltaUnit" = "min" && deltaFactor=60

        test "$isFloat" = "float" && bcParam="-l"

        # get last value
        local lastvalue=$(ph._readlastvalue "${varName}")
        if [ "$lastvalue" = "" ]; then
                # retvalue="[no last value]"
                retvalue=""
        else
                local f=1
                if [ $value -lt 0 ]; then
                        f=-1
                fi
                if [ $(( $lastvalue * $f )) -gt $(( $value * $f )) ]; then
                        # retvalue="[reset data]"
                        retvalue=""
                else
                        local iage; iage=$(ph._getageoflastvalue "${varName}")
                        test "$iage" = "0" && iage=1

                        local delta; delta=$(echo "${value}-$lastvalue" | bc $bcParam )
                        local deltaspeed; deltaspeed=$(echo "${delta}*${deltaFactor}/(${iage})" | bc $bcParam)
                        retvalue=$deltaspeed
                fi
        fi
        ph._savecounter "${varName}" "${value}"

        #echo DEBUG sData="${sData} '${varName}'=${retvalue}"
        # sGDData="${sGDData} '${varName}'=${retvalue}"
        echo "${retvalue}"

        # DEBUG - wird in Nagios sichtbar
        # gdAddLabel "... ${varName} absolute: ${value} -> delta: ${delta} -> derive: ${retvalue} ($iage sec)"
}

# add performance data
#
# OUTPUT-SYNTAX
# 'label'=value[UOM];[warn];[crit];[min];[max]
# procs=401;200;300;0;
#
# example
# ph.perfadd  
#
# param  string     label
# param  int|float  your value
# param  integer    optional: warning level
# param  integer    optional: critical level
# param  integer    optional: min graph value
# param  integer    optional: max graph value
function ph.perfadd(){
  if [ -z "$ph_perfdatafile" ]; then
    ph._perfinit
  fi
  local _label=$(ph._getperflabel "$1")
  local _value=$2
  local _w=$3
  local _c=$4
  local _min=$5
  local _max=$6

  test -z "$_value" && _value=0

  # adding a generic min and max value did not really help
  # test -z "$_min"   && echo "WARNING from ph.perfadd: missing param 5 for minimum - setting it to 0"
  # test -z "$_min"   && _min=0
  # test -z "$_max"   && echo "WARNING from ph.perfadd: missing param 6 for maxiumun - setting it to 100"
  # test -z "$_max"   && _max=100  
  # ... so we add them if tey exist only
  local _minmax=
  test -n "$_min"   && _minmax+=";$_min"
  test -n "$_max"   && _minmax+=";$_max"

  # echo "${_label}=${_value};${_w};${_c};${_min};${_max}" >>"${ph_perfdatafile}"
  echo "${_label}=${_value};${_w};${_c}${_minmax}" >>"${ph_perfdatafile}"
}


# output of performance data
# remark: this function is called in ph.exit too
function ph.perfshow(){
  if [ ! -z "$ph_perfdatafile" ]; then
    if [ -f "$ph_perfdatafile" ]; then
      echo -n " |"
      cat "${ph_perfdatafile}" | tr "\n" " "
      rm -f "${ph_perfdatafile}"
    fi
  fi
}


# ----------------------------------------------------------------------

# execute a command and repeat it N times before aborting
# param  string    command line to execute
# param  integer   sleeptime in sec before repeating again; default: 5
# param  integer   max number of tries; default: 3
function ph.execIfReady(){
  local _cmd2run=$1
  local _iSleeptime=${2:-5}
  local _iMaxTry=${3:-3}

  typeset -i local _iCount=0
  typeset -i local _rc=1
  local tmpfile=/tmp/execIfRead_out_$$
  while [ $_rc -ne 0 ]; do
    eval "${_cmd2run}" >$tmpfile 2>&1
    _rc=$?
    if [ $_rc -ne 0 ]; then
      _iCount=$_iCount+1
      if [ $_iCount -ge $_iMaxTry ]; then
        rm -f $tmpfile
        ph.setStatus "unknown"
        (
          ph.status "Aborting because command failed $_iMaxTry times: [$_cmd2run] - see $0" 
        ) > /dev/tty
        rm -f $tmpfile
        ph.exit
      fi
      sleep $_iSleeptime
    fi
  done
  cat $tmpfile
  rm -f $tmpfile
}

# ----------------------------------------------------------------------

# show a header in the help for IML checks
ph.showImlHelpHeader(){
  cat <<EOF
______________________________________________________________________

$( basename $0 | tr [:lower:] [:upper:] )
v$self_APPVERSION

(c) Institute for Medical Education - University of Bern
Licence: GNU GPL 3

https://os-docs.iml.unibe.ch/icinga-checks/Checks/$( basename $0 ).html
______________________________________________________________________
EOF
}

# ----------------------------------------------------------------------

# init
ph.setStatus "ok"

# ----------------------------------------------------------------------