#!/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 # 2024-05-30 v1.16 <axel.hahn@unibe.ch> updatate link to online help (Daux replaces "-"" to "_") # ====================================================================== # 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 string; one of ... # almalinux # centos # debian # manjaro # rocky # 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 # including a link to the online help ph.showImlHelpHeader(){ local _html; _html="$( basename "$0" )" _html=${_html//\-/_}.html 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/${_html} ______________________________________________________________________ EOF } # ---------------------------------------------------------------------- # init ph.setStatus "ok" # ----------------------------------------------------------------------