Binary Talks

Apache check plugin for Nagios in Python

logo_nagiosAs I’ve already said yesterday I’m currently getting warm with Python and for the beginning I’ve ported my sh compliant Apache check plugin for Nagios to a Python script which is now ready for first tests in production.

With options to set a hostname, a port and to define warning/critical thresholds for requests per second only, it got less features than the sh script at the moment. In future releases I’ll integrate more features which are already available in the sh script, but for now that’ll do the job. Feel free to visit NagiosExchange to download the script, check it out via svn or simply get along with the copy’n'paste possibility below.

user@host ~ $ svn co https://svn.matejunkie.com/svn/pp-nagios-plugins/stable/check_apache2/ check_apache2/
A    check_apache2/check_apache2.py
Checked out revision 14.

The script

#!/usr/bin/env python
 
#   Author: Mike Adolphs, 2009
#   Blog: http://www.matejunkie.com/
#
#   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; version 2 of the License only!
#
#   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
 
import sys
import urllib
 
from optparse import OptionParser, OptionGroup
 
# Nagios return codes
OK = 0
WARNING = 1
CRITICAL = 2
UNKNOWN = 3
 
usage = "Usage: %prog -H HOSTNAME -p PORT [-w] [-c]"
parser = OptionParser(usage, version="%prog 1.0")
parser.add_option( "-H",
                   "--hostname",
                   type="string",
                   dest="hostname",
                   default="localhost",
                   help="You may define a hostname with the -H option. \
                         Default is: localhost.")
 
parser.add_option( "-p",
                   "--port",
                   type="int",
                   dest="port",
                   default=80,
                   help="You may define a port with the -p option. \
                         Default is: 80.")
 
group = OptionGroup(parser, "Warning/critical thresholds",
                    "Use these options to set warning/critical thresholds \
                     for requests per second served by your Apache.")
group.add_option( "-w",
                  "--warning",
                  type="int",
                  dest="warning",
                  default=-2,
                  help="Use this option if you want to use warning/critical \
                        thresholds. Make sure to set a critical value as \
                        well. Default is: -1.")
group.add_option( "-c",
                  "--critical",
                  type="int",
                  dest="critical",
                  default=-1,
                  help="Use this option if you want to use warning/critical \
                        thresholds. Make sure to set a warning value as \
                        well. Default is: -2.")
parser.add_option_group(group)
 
(options, args) = parser.parse_args()
 
hostname = options.hostname
port = options.port
warning = options.warning
critical = options.critical
 
def end(status, message):
    """Exits the script with the first argument as the return code and the
       second as the message to generate output."""
 
    if status == OK:
        print "OK: %s" % message
        sys.exit(0)
    elif status == WARNING:
        print "WARNING: %s" % message
        sys.exit(1)
    elif status == CRITICAL:
        print "CRITICAL: %s" % message
        sys.exit(2)
    else:
        print "UNKNOWN: %s" % message
        sys.exit(3)
 
def validate_thresholds(warning, critical):
    """Validates warning and critical thresholds in several ways."""
 
# This is already done by OptionParser.
#    try:
#        warning = int(warning)
#    except ValueError:
#        end(stateUNK, "Warning threshold must be an integer value.")
#    try:
#        critical = int(critical)
#    except ValueError:
#        end(stateUNK, "Critical threshold must be an integer value.")
    if critical != -1 and warning == -2:
        end(UNKNOWN, "Please also set a warning value when using warning/" +
                     "critical thresholds!")
    if critical == -1 and warning != -2:
        end(UNKNOWN, "Please also set a critical value when using warning/" +
                     "critical thresholds!")
    if critical <= warning:
        end(UNKNOWN, "When using thresholds the critical value has to be " +
                      "higher than the warning value. Please adjust your " +
                      "thresholds.")
 
def retrieve_status_page():
    """Get's the server's status page and raises an exception if it's not
       accessible."""
 
    statusPage = "http://%s:%s/server-status?auto" % (hostname, port)
    try:
        retrPage = urllib.urlretrieve(statusPage, '/tmp/server-status.log')
    except:
        end(CRITICAL, "Couldn't fetch the server's status page. Please " +
                      "check given hostname, port or Apache's " +
                      "configuration. We might not be allowed to access " +
                      "server-status due to your server's configuration.")
 
def parse_status_page():
    """Main parsing function to put the server-status file's content into
       a dictionary."""
 
    file = open('/tmp/server-status.log', 'r')
    line = file.readline()
    dictStatus = {}
    counter = 1
 
    while line:
        if "Total Accesses:" in line:
            key = "totalAcc"
        elif "Total kBytes:" in line:
            key = "totalKb"
        elif "Uptime:" in line:
            key = "uptime"
        elif "ReqPerSec:" in line:
            key = "reqPSec"
        elif "BytesPerSec:" in line:
            key = "bytesPSec"
        elif "BytesPerReq:" in line:
            key = "bytesPReq"
        elif "BusyWorkers:" in line:
            key = "busyWkrs"
        elif "IdleWorkers:" in line:
            key = "idleWkrs"
        else:
            key = str(counter)
 
        line = line.strip()
        dictStatus[key] = line
        counter = counter + 1
        line = file.readline()
 
    return dictStatus
 
def transform_dict(resParse):
    """Transforms the dictionary to a list and converts variables to proper
       types."""
 
    totalAcc  = int(resParse['totalAcc'].strip(" Total Accesses:"))
    totalKb   = float(resParse['totalKb'].strip(" Total kBytes:"))
    uptime    = int(resParse['uptime'].strip(" Uptime:"))
    reqPSec   = float(resParse['reqPSec'].strip(" ReqPerSec:")) + 0
    bytesPSec = float(resParse['bytesPSec'].strip(" BytesPerSec:"))
    if resParse.has_key('bytesPReq'):
        bytesPReq = float(resParse['bytesPReq'].strip(" BytesPerReq:"))
 
    busyWkrs  = int(resParse['busyWkrs'].strip(" BusyWorkers:"))
    idleWkrs  = int(resParse['idleWkrs'].strip(" IdleWorkers:"))
 
    return [reqPSec, busyWkrs, idleWkrs]
 
# main
if __name__ == "__main__":
    if critical != -1 or warning != -2:
        validate_thresholds(warning, critical)
 
    retrieve_status_page()
    resParse = parse_status_page()
    result = transform_dict(resParse)
 
    if critical != -1 and warning != -2:
        if result[0] >= critical:
            end(CRITICAL, "Apache serves %f requests per second, exceeding \
critical threshold! %i busy workers, %i idle workers." % (result[0], \
result[1], result[2]))
        elif result[0] >= warning and result[0] <= critical:
            end(WARNING, "Apache serves %f requests per second, exceeding \
warning threshold! %i busy workers, %i idle workers." % (result[0], \
result[1], result[2]))
        else:
            end(OK, "Apache serves %f requests per second. %i busy workers, \
%i idle workers." % (result[0], result[1], result[2]))
    else:
        end(OK, "Apache serves %f requests per second. %i busy workers, %i \
idle workers." % (result[0], result[1], result[2]))

Example Output

user@host $ ./check_apache2.py --version
check_apache2.py 1.0
user@host $ ./check_apache2.py -H localhost -p 7001
OK: Apache serves 9.093710 requests per second. 1 busy workers, 49 idle workers.
user@host $ ./check_apache2.py -H localhost -p 7001 -w 2 -c 5
CRITICAL: Apache serves 9.082950 requests per second, exceeding critical threshold! 1 busy workers, 49 idle workers.
user@host $ ./check_apache2.py -H localhost -p 7001 -w 2 -c 10
WARNING: Apache serves 9.077650 requests per second, exceeding warning threshold! 1 busy workers, 49 idle workers.
user@host $ ./check_apache2.py -H localhost -p 7001 -w 10 -c 20
OK: Apache serves 9.073430 requests per second. 1 busy workers, 49 idle workers.
user@host $ ./check_apache2.py -H localhost -p 7001 -w 10 -c 5
UNKNOWN: When using thresholds the critical value has to be higher than the warning value. Please adjust your thresholds.
user@host $ ./check_apache2.py -H localhost -p 7001 -w 5
UNKNOWN: Please also set a critical value when using warning/critical thresholds!
user@host $ ./check_apache2.py -H localhost -p 7001 -c 5
UNKNOWN: Please also set a warning value when using warning/critical thresholds!
user@host $ ./check_apache2.py -H localhost -p 7002
CRITICAL: Couldn't fetch the server's status page. Please check given hostname, port or Apache's configuration. We might not be allowed to access server-status due to your server's configuration.

The 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. More options will be added in the future.

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

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