Skip to content
Snippets Groups Projects
check_couchdb 9.76 KiB
#!/bin/bash
# ======================================================================
#
# Icinga/ Nagios Check
# COUCHDB
#
# ----------------------------------------------------------------------
#
# REQUIREMENTS:
#   - curl 
#
# SYNTAX:
#   - check_couchdb [-h] [-c CFGFILE] -m MODE
#
# ----------------------------------------------------------------------
# 2023-08-28  v0.1  <axel.hahn@unibe.ch>  first lines
# 2023-08-28  v0.2  <axel.hahn@unibe.ch>  first check "up"
# 2023-08-28  v0.3  <axel.hahn@unibe.ch>  add check "replication"
# 2023-08-28  v0.4  <axel.hahn@unibe.ch>  add check "pending"
# 2023-08-28  v0.5  <axel.hahn@unibe.ch>  add checks "open_databases" + "open_os_files"
# 2023-08-28  v0.6  <axel.hahn@unibe.ch>  add check "httpd_methods"
# 2023-08-29  v0.7  <axel.hahn@unibe.ch>  add check "httpd"
# 2023-08-29  v0.8  <axel.hahn@unibe.ch>  add check "httpd_status_codes"
# ======================================================================

. $(dirname $0)/inc_pluginfunctions

export self_APPVERSION=0.8

cfgfile=/etc/icingaclient/.couchdb
export RESPONSE

# ----------------------------------------------------------------------
# FUNCTIONS
# ----------------------------------------------------------------------

# show help
function showHelp(){
    local _self; _self=$(basename $0)
    cat <<EOF
$( ph.showImlHelpHeader )

Show couchdb status.

SYNTAX:
$_self [-h] [-t FILE] -m MODE

OPTIONS:
  -h or --help        show this help.
  -c CFGFILE          set a custom config file
                      default: /etc/icingaclient/.couchdb
  -m MODE             test a value; for debugging purposes the full json 
                      response will be shown

  MODE is one of
  httpd               Show counters for http request types
  httpd_methods       Show counters for http request methods
  httpd_status_codes  Show counters per http status code
  open_databases      show number of open databases
  open_os_files       show number of file descriptors CouchDB has open
  replication         show last replication status
  pending             show count of pending updates for nodes, dbs and users
  replication         show last replication status
  up                  show general couchdb health status

EXAMPLE:
$_self -m up
  Check if couchdb is up and running

$_self -m httpd_methods
  Show counters and change rate per sec of GET, POST, and other methods

$_self -c /opt/couchdb/myconfig.sh -m up
  Source another config to define COUCH_URL

EOF
}

# get couchdb status by given url and a filter
# The check aborts here if no data were found.
# Response is written into global var RESPONSE
#
# param  string  url to request; the part behind couchdb base url
# param  string  string to search for in the content
function abortOnWrongResponse(){
        _path="$1"
        _filter="$2"

        RESPONSE=$( curl -s "${COUCH_URL}${_path}" )
        if ! grep "$_filter" <<< "$RESPONSE" >/dev/null ; then
                echo "ERROR: Wrong response from $_path - it does not contain $_filter"
                curl -si "${COUCH_URL}${_path}"
                ph.abort
        fi
}

# ----------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------

# --- check param -h
case "$1" in
    "--help"|"-h")
        showHelp
        exit 0
        ;;
    *)
esac

ph.require jq

sMode=$(ph.getValueWithParam '' "m" "$@")
cfgfile=$(ph.getValueWithParam "${cfgfile}" "c" "$@")

if [ ! -f "$cfgfile" ]; then
    echo "ERROR: Config file [${cfgfile}] does not exist."
    ph.abort
fi

. "$cfgfile" || exit 1

if [ -z "$COUCH_URL" ]; then
    echo "ERROR: I have no couchdb url + authentication yet."
    echo "set 'export COUCH_URL=http://USER:PW@localhost:5984' in $cfgfile"
    ph.abort
fi

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

case "${sMode}" in
    # ............................................................
    "httpd")
        REQ=/_node/_local/_stats/couchdb/httpd
        abortOnWrongResponse "${REQ}" '"value":'

        typeset -i _iValue
        typeset -i _iDelta

        _status=$( jq 'with_entries(.value |= .value)' <<< "${RESPONSE}" | grep '^  "' | grep -v '\{' | sed 's#[", ]##g' )
        # this returns:
        # aborted_requests:0
        # bulk_requests:0
        # requests:185531
        # ...

        ph.status "Couchdb :: Http request methods"
        printf "%30s %10s %10s\n" "Property" "Counter" "Delta" | tr ' ' '_'
        for myVar in $( grep "^[a-z\_]" <<< "$_status" | cut -f 1 -d ':' )
        do  
            _iValue=$( grep "^${myVar}:" <<< "$_status" | cut -f 2 -d ':' )
            _label="couchdb-hp-${myVar//_/-}" # underscrore to minus
            _iDelta=$( ph.perfdeltaspeed "${_label}" $_iValue )
            printf "%30s %10s %10s per sec\n" "$myVar" $_iValue $_iDelta
            ph.perfadd "${myVar}" "$_iDelta"    "" ""
        done
        echo
        ;;
    # ............................................................
    "httpd_methods")
        REQ=/_node/_local/_stats/couchdb/httpd_request_methods
        abortOnWrongResponse "${REQ}" '"value":'

        typeset -i _iValue
        typeset -i _iDelta

        _status=$( jq 'with_entries(.value |= .value)' <<< "${RESPONSE}" | sed 's#[", ]##g' | grep '^[A-Z]' )
        # this returns:
        # COPY:0
        # DELETE:1
        # GET:188183
        # HEAD:0
        # ...

        ph.status "Couchdb :: Http request methods"
        printf "%10s %10s %10s\n" "Method" "Counter" "Delta" | tr ' ' '_'
        for myVar in $( grep "^[A-Z]" <<< "$_status" | cut -f 1 -d ':' )
        do            
            _iValue=$( grep "^$myVar:" <<< "$_status" | cut -f 2 -d ':' )
            _label="couchdb-hm-${myVar//_/-}" # underscrore to minus
            _iDelta=$( ph.perfdeltaspeed "${_label}" $_iValue)
            printf "%10s %10s %10s per sec\n" "$myVar" $_iValue $_iDelta
            ph.perfadd "${myVar}" "$_iDelta"    "" ""
        done

        # echo "${_status}" | jq
        ;;
    # ............................................................
    "httpd_status_codes")
        REQ=/_node/_local/_stats/couchdb/httpd_status_codes
        abortOnWrongResponse "${REQ}" '"value":'

        typeset -i _iValue
        typeset -i _iDelta

        _status=$( jq 'with_entries(.value |= .value)' <<< "${RESPONSE}" | sed 's#[", ]##g' | grep '^[1-9]' )
        # this returns:
        # 200:199460
        # 201:0
        # 202:0
        # 204:0
        # ...

        ph.status "Couchdb :: Http status codes"
        printf "%10s %10s %10s\n" "Status" "Counter" "Delta" | tr ' ' '_'
        for myVar in $( grep "^[1-9]" <<< "$_status" | cut -f 1 -d ':' )
        do            
            _iValue=$( grep "^$myVar:" <<< "$_status" | cut -f 2 -d ':' )
            _label="couchdb-hs-${myVar//_/-}" # underscrore to minus
            _iDelta=$( ph.perfdeltaspeed "${_label}" $_iValue)
            printf "%10s %10s %10s per sec\n" "$myVar" $_iValue $_iDelta
            ph.perfadd "http${myVar}" "$_iDelta"    "" ""
        done
        ;;

    # ............................................................
    "open_databases"|"open_os_files")
        REQ=/_node/_local/_stats/couchdb/${sMode}
        abortOnWrongResponse "${REQ}" '"value":'

        typeset -i _iValue

        # descr=$( jq '.desc'  <<< "${RESPONSE}" | tr -d '"')
        _iValue=$( jq '.value' <<< "${RESPONSE}" )
        ph.perfadd "${sMode}" "$_iValue"    "" ""
        ph.status "Couchdb :: ${sMode} = $_iValue"
        # echo "$descr"
        # echo "Reponse: of ${REQ}"; 
        # echo "${RESPONSE}" | jq
        ;;
    # ............................................................
    "pending")
        REQ=/_up
        abortOnWrongResponse "${REQ}" '"status":"'

        _status=$( jq '.seeds[] | .pending_updates' <<< "${RESPONSE}" | grep -v "null" | tr -d '"')
        
        typeset -i _iSumme
        typeset -i _iTotal

        _iTotal=0
        for myvar in _nodes _dbs _users
        do
            _iSumme=0
            for myvalue in $( grep "$myvar" <<< "$_status" | cut -f 2 -d ':' | tr -d ',')
            do
                _iSumme+=$myvalue
                test "$myvalue" -gt 0  && ph.setStatus warning
            done
            ph.perfadd "$myvar" "$_iSumme"    "" ""
            _iTotal+=$_iSumme
        done

        ph.status "Couchdb :: pending updates: $_iTotal (values below 'pending_updates' in ${REQ})"
        echo "Reponse: of ${REQ}"; 
        echo "${RESPONSE}" | jq
        ;;
    # ............................................................
    "replication")
        REQ=/_up
        abortOnWrongResponse "${REQ}" '"status":"'

        _status=$( jq '.seeds[] | .last_replication_status' <<< "${RESPONSE}" | grep -v "null" | tr -d '"')

        # there can be multiple sections "seeds" and multiple line responses.
        # remove all lines with "ok" and check if there is any "bad content" left
        _nonok=$( echo "$_status" | grep -v "ok" )
        if [ -n "$_nonok" ]; then
                ph.setStatus critical
        fi

        ph.status "Couchdb :: replication (values 'last_replication_status' in ${REQ} are '$_status')"
        echo "Reponse: of ${REQ}"; 
        echo "${RESPONSE}" | jq
        ;;
    # ............................................................
    "up")
        REQ=/_up
        abortOnWrongResponse "${REQ}" '"status":"'

        _status=$( jq '.status' <<< "${RESPONSE}" | tr -d '"')
        if ! echo "$_status" | grep "ok" >/dev/null; then
                ph.setStatus critical
        fi
        ph.status "Couchdb :: health status"
        echo "Reponse of ${REQ}: "; 
        echo "${RESPONSE}" | jq
        ;;


    # ............................................................
    *)
        echo "ERRROR: [${sMode}] is an INVALID mode"
        showHelp
        ph.abort

esac

ph.exit

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