Binary Talks

Apache check plugin for Nagios

logo_nagios

Update: After reading this article I strongly suggest to use the updated version 1.1 of this script described in this article.

Apache is the most widely spread webserver on earth with a market share of over 50% according to Netcraft’s webserver survey for February 2009. Sad to say that almost every Apache related Nagios check has some disadvantages. At least the one’s I know of. Therefore I just started to work on a Nagios plugin that check’s the Apache’s health.

Plugin overview

  • checks whether the Apache is running
  • wgets the server’s server-status page and checks the following data:
    • Requests per second
    • CPU utilization
    • Uptime since last restart
    • Amount of busy workers
    • Amount of idle workers
    • Total requests
    • Total transferred kilobytes
    • Bytes per second
    • Bytes per request

How it works

In general, the plugin first tries to see whether Apache is running via ps, wgets the server’s status page two times and stores it in the home directory of the user that ran the script. After that it parses the two files and generates the output.
To see whether Apache is running, the script uses full paths! Please use the -b/–binary-dir option to set the path of the Apache’s binary when it’s not located in /usr/sbin. If you want to store the data of the two wgets in another directory than the user’s home directory, you’re free to do so via the -o/–output-dir option, but please make sure, that the user has still rights to parse the two files.
In addition you’re also able to limit the runtime of the script with adding a timeout parameter to the wget via the -t/–timeout option which is set to a default value of 3 seconds. Increase or decrease according to your server’s utilization.

Most of the data is generated by the Apache module mod_status which therefore must be enabled in the Apache’s webserver configuration. The script is also in need of permissions to allow requesting the server’s status page. In general, it’s a good idea to limit permissions to localhost only as you can see in the following location directive:

        SetHandler server-status
 
        Order deny,allow
        Deny from all
        Allow from localhost

Besides that the plugin also makes use of ps to check whether Apache is running and to get the Apache’s CPU utilization. This was necessary because mod_status only provides average values for CPU utliziation for its whole runtime. That means, otherwise we won’t be able to see peaks in demands if relying on mod_status because of the different day and night traffic characteristics. The same problem applies to the amount of requests per second. Therefore I’ve decided to make two server-status calls instead of one to get the difference between the two “total request” values as the amount of current Apache requests.

You may also want to have a look on the example usage and output below:

user@host ~ $ ./check_apache2.sh -H localhost -p 2323 -e
OK - Apache serves 172 Requests per second with an average CPU utilization of 4.1% since 79783 seconds. Amount of workers currently busy: 56, currently idle: 44! | 'req_psec'=172 'cpu_load'=4.1 'uptime'=79783 'workers_busy'=56 'workers_idle'=44 'total_req'=7498921 'total_kb'=1404733 'bytes_psec'=18029.5 'bytes_preq'=191.82

The Script

I’ve just finished setting up subversion and redmine, available via http://svn.matejunkie.com/. Please excuse the ssl error for the next couple of days as long as I haven’t regenerated the server’s certificate. It just doesn’t include the svn subdomain yet otherwise it’d work perfectly fine if you’ve integrated the cacert’s root ca in your favorite browser’s certificate store. To checkout the latest stable version please use the following:

user@host ~ $ svn co https://svn.matejunkie.com/svn/nagios-plugins/stable/check_apache/ check_apache/

You’re also free to go over to http://www.nagiosexchange.org/ to download the script directly from there. For people who doesn’t want to use this or the svn way, here’s the possibility to copy’n'paste:

#!/bin/sh
 
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
PROGNAME=`basename $0`
VERSION="Version 1.0,"
AUTHOR="2009, Mike Adolphs (http://www.matejunkie.com/)"
 
print_version() {
    echo "$VERSION $AUTHOR"
}
 
print_help() {
    print_version $PROGNAME $VERSION
    echo ""
    echo "Description"
    echo "$PROGNAME is a Nagios plugin to check the Apache's server status"
    echo "via the famous mod_status. Make sure, that you have permissions"
    echo "to access http://yourserver:port/server-status and that the Nagios"
    echo "User has the right to call 'ps ax' and 'ps -Ao pcpu,args', otherwise"
    echo "the script refuses to work. This will likely occur only on hardened"
    echo "systems."
    echo ""
    echo "How it works:"
    echo "The script first checks whether Apache is running and then gets the"
    echo "server's status page. Req/sec are generated by a diff from the amount"
    echo "of total requests being served by the Apache due to the fact that"
    echo "mod_status only provides an average value for the server's runtime."
    echo "The same applies to the CPU utilization value. Therefore we use ps"
    echo "to get more realistic data."
    echo ""
    echo "The script itself is written sh-compliant and free software under"
    echo "the terms of the GPLv2. There's a default value for each variable. It"
    echo "probably works just out of the box?"
    echo ""
    echo "$PROGNAME -H/--hostname localhost -p/--port 80 -t/--timeout 3 -o $HOME"
    echo "-b /usr/sbin -e"
    echo ""
    echo "Options:"
    echo "  -b/--binary-dir)"
    echo "     You might need to choose the directory where your apache2 binary is"
    echo "     located. Otherwise the check will fail. Default is: /usr/sbin"
    echo "  -H/--hostname)"
    echo "     You may define a hostname. Default is: localhost"
    echo "  -p/--port)"
    echo "     You may define a port. Default is: 80"
    echo "  -t/--timeout)"
    echo "     You might want to define a timeout (in sec) for the wget call. Default"
    echo "     is: 3"
    echo "  -o/--output-directory)"
    echo "     You may define a directory where a local copy of server-status"
    echo "     is being stored to spare the Apache. Default is: $HOME"
    echo "  -e/--extended-info)"
    echo "     Provides additional performance data: Total amount of requests, total"
    echo "     amount of transferred bytes, bytes per second and bytes per request."
    echo "     Default is off."
    exit $ST_UK
}
 
ST_OK=0
ST_WR=1
ST_CR=2
ST_UK=3
 
hostname="localhost"
port=80
timeout=3
output_dir=$HOME
binary_dir="/usr/sbin"
ext_info=0
running=0
 
while test -n "$1"; do
    case "$1" in
        --help|-h)
            print_help
            exit $ST_UK
            ;;
        --version|-v)
            print_version $PROGNAME $VERSION
            exit $ST_UK
            ;;
        --hostname|-H)
             hostname=$2
             shift
             ;;
        --port|-p)
             port=$2
             shift
             ;;
        --timeout|-t)
             timeout=$2
             shift
             ;;
        --output_directory|-o)
             output_dir=$2
             shift
             ;;
        --binary_dir|-b)
             binay_dir=$2
             shift
             ;;
        --extendd-info|-e)
             ext_info=1
             ;;
        *)
            echo "Unknown argument: $1"
            print_help
            exit $ST_UK
            ;;
    esac
    shift
done
 
get_processes() {
    ps ax | grep -c ${binary_dir}/[a]pache2
}
 
get_status() {
    wget -q -t 3 -T ${timeout} http://${hostname}:${port}/server-status?auto -O ${output_dir}/server-status
    sleep 1
    wget -q -t 3 -T ${timeout} http://${hostname}:${port}/server-status?auto -O ${output_dir}/server-status.1
}
 
get_total_req() {
    total_req=`cat ${output_dir}/server-status | grep 'Total Accesses:' | awk '{print $3}'`
}
 
get_total_kb() {
    total_kb=`cat ${output_dir}/server-status | grep 'Total kBytes:' | awk '{print $3}'`
}
 
get_uptime() {
    uptime=`cat ${output_dir}/server-status | grep 'Uptime:' | awk '{print $2}'`
}
 
get_cpu_load() {
    cpu_load="$(cpu_load=0; ps -Ao pcpu,args | grep '/usr/sbin/apache2' | awk '{print $1}' | while read line
    do
        cpu_load=`echo "scale=3; $cpu_load + $line" | bc -l`
    echo $cpu_load
    done)"
    cpu_load=`echo $cpu_load | awk '{print $NF}' | sed 's/^./0./'`
}
 
get_req_psec() {
    tmp1_req_psec=`cat ${output_dir}/server-status | grep 'Total Accesses:' | awk '{print $3}'`
    tmp2_req_psec=`cat ${output_dir}/server-status.1 | grep 'Total Accesses:' | awk '{print $3}'`
    req_psec=`echo "scale=2; ${tmp2_req_psec} - ${tmp1_req_psec}" | bc -l`
}
 
get_bytes_psec() {
    bytes_psec=`cat ${output_dir}/server-status | grep 'BytesPerSec:' | awk '{print $2}' | sed 's/^./0./'`
}
 
get_bytes_preq() {
    bytes_preq=`cat ${output_dir}/server-status | grep 'BytesPerReq:' | awk '{print $2}' | sed 's/^./0./'`
}
 
get_wkrs_busy() {
    wkrs_busy=`cat ${output_dir}/server-status | grep 'BusyWorkers:' | awk '{print $2}'`
}
 
get_wkrs_idle() {
    wkrs_idle=`cat ${output_dir}/server-status | grep 'IdleWorkers:' | awk '{print $2}'`
}
 
check_processes() {
    if [ $1 -lt 1 ]
    then
        echo "UNKNOWN - Your Apache server seems not to run. Is your Nagios privileged to run 'ps ax' and is the Apache2 binary really located in $binary_dir?"
        exit $ST_UK
    fi
}
 
check_output() {
    stat_output=`stat -c %s ${output_dir}/server-status`
    if [ "$stat_output" = 0 ]
    then
        echo "UNKNOWN - Local copy of server-status is empty. Are we allowed to access http://${hostname}:${port}/server-status?"
        exit $ST_UK
    fi
}
 
running=`get_processes`
check_processes $running
 
get_status
check_output
 
case $ext_info in
    1)
        get_req_psec; get_cpu_load; get_uptime; get_wkrs_busy; get_wkrs_idle;
        get_total_req; get_total_kb; get_bytes_psec; get_bytes_preq;
        perfdata="'req_psec'=$req_psec 'cpu_load'=$cpu_load 'uptime'=$uptime 'workers_busy'=$wkrs_busy 'workers_idle'=$wkrs_idle 'total_req'=$total_req 'total_kb'=$total_kb 'bytes_psec'=$bytes_psec 'bytes_preq'=$bytes_preq"
        ;;
    *)
        get_req_psec; get_cpu_load; get_uptime; get_wkrs_busy; get_wkrs_idle;
        perfdata="'req_psec'=$req_psec 'cpu_load'=$cpu_load 'uptime'=$uptime 'workers_busy'=$wkrs_busy 'workers_idle'=$wkrs_idle"
        ;;
esac
 
echo "OK - Apache serves $req_psec Requests per second with an average CPU utilization of $cpu_load% since $uptime seconds. Amount of workers currently busy: $wkrs_busy, currently idle: $wkrs_idle! | $perfdata"
 
exit $ST_OK

License

As always this little script is ment to be sh-compliant and released under the terms of the GPL Version 2 only. Feel free to subscribe via rss to get updates on this one. I’ll add different warning/critical threshold possibilities and PNP templates to the script in the near future. And let me know if you have any questions.

Share and Enjoy:
  • del.icio.us
  • Digg
  • Slashdot
  • Google Bookmarks
  • LinkedIn
  • StumbleUpon
  • Reddit
  • Yigg
  • Netvibes
  • MisterWong
  • Facebook
  • HackerNews
  • Identi.ca
  • FriendFeed
  • NewsVine

3 Comments

speak up

Add your comment below, or trackback from your own site.

Subscribe to these comments.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

*Required Fields