#!/usr/bin/perl
#
# (c)2001 Sebastian Hetze, Linux Information Systems AG
# send bug reports to <S.Hetze@Linux-AG.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; 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 (or with Nagios);  if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA
#
#
# Check apache status information provided by mod_status to find
# out about the load (number of servers working) and the
# performance (average response time for recent requests).
#
# Usage:
# check_apache -H <host> [-lhV] [-w <warn>] [-c <crit>] [-u <url>]
#
# check_apache <host> <warn> <crit> <url> (if you cannot avoid it)
#
# Should have the username/password facility added to allow challenges.
#

BEGIN {
  if ($0 =~ m/^(.*?)[\/\\]([^\/\\]+)$/) {
    $runtimedir = $1;
    $PROGNAME = $2;
  }
}

require 5.004;
use POSIX;
use strict;
use LWP::UserAgent;
use URI::URL;
use Getopt::Long;
use lib $main::runtimedir;
use vars qw($opt_V $opt_h $opt_l $opt_w $opt_c $opt_H $opt_u $verbose $xml $PROGNAME);
use utils qw(%ERRORS &print_revision &support &usage &print_usage &print_help);


my %hostnameparameter = ( "name" => "hostname", "optional" => "no",
			  "flagparameter" => { "flags" => [ { "form" => "short", "id" => "H" },
							    { "form" => "long", "id" => "hostname" } ],
					       "optional" => "no",
					       "argument" => { "type" => "ADDRESS", "optional" => "no" },
					       "description" => "Host name or address for server" } );
my %loadparameter = ( "name" => "load", "optional" => "yes",
		      "flagparameter" => { "flags" => [ { "form" => "short", "id" => "l" },
							{ "form" => "long", "id" => "load" } ],
					   "optional" => "no",
					   "description" => "Check load instead of performance" } );
my %warningparameter = ( "name" => "warning", "optional" => "yes",
			 "flagparameter" => { "flags" => [ { "form" => "short", "id" => "w" },
							   { "form" => "long", "id" => "warning" } ],
					      "optional" => "no",
					      "argument" => { "type" => "INTEGER", "optional" => "no", "default" => "Performance=500 | Load=20" },
					      "description" => "load / performance level at which a warning message will be generated" } );
my %criticalparameter = ( "name" => "critical", "optional" => "yes",
			  "flagparameter" => { "flags" => [ { "form" => "short", "id" => "c" },
							    { "form" => "long", "id" => "critical" } ],
					       "optional" => "no",
					       "argument" => { "type" => "INTEGER", "optional" => "no", "default" => "Performance=1000 | Load=30" },
					       "description" => "load / performance level at which a critical message will be generated" } );
my %urlparameter = ( "name" => "url", "optional" => "yes",
		     "flagparameter" => { "flags" => [ { "form" => "short", "id" => "u" },
						       { "form" => "long", "id" => "url" } ],
					  "optional" => "no",
					  "argument" => { "type" => "PATH", "optional" => "no", "default" => "/server-status" },
					  "description" => "Location of mod_status interface on server" } );
my %xmlparameter = ( "name" => "xml", "optional" => "yes",
		     "flagparameter" => { "flags" => [ { "form" => "long", "id" => "xml" } ],
					  "optional" => "no",
					  "description" => "Output data in XML format" } );
my @commandparameterlist = ( \%hostnameparameter,
			     \%loadparameter,
			     \%warningparameter,
			     \%criticalparameter,
			     \%urlparameter,
			     \%xmlparameter );

my %versionparameter = ( "name" => "version", "optional"     => "no",
			 "flagparameter" => { "flags" => [ { "form" => "short", "id" => "v" },
							   { "form" => "long", "id" => "version" } ],
					      "optional"       => "no",
					      "description"    => "Print the program version" } );
my @versionparameterlist = ( \%versionparameter, \%xmlparameter );

my %helpparameter = ( "name" => "help", "optional"        => "no",
                      "flagparameter"   => { "flags" => [ { "form" => "short", "id" => "h" },
							  { "form"    => "long", "id"       => "help" } ],
					     "optional"       => "no",
					     "description"    => "Print a long help listing" } );
my @helpparameterlist = ( \%helpparameter, \%xmlparameter );

my %helpdata = ( "progname" => $PROGNAME,
		 "revision" => '$Revision: 1.1.1.1 $',
		 "copyright" => "Copyright (c) 2001 Sebastian Hetze  Linux Information Systems AG",
		 "shortcommentary" => "This plugin checks the apache HTTP service on the specified host. It uses the mod_status facilities provided by the apache server. The monitoring server must be authorized in httpd.conf.",
		 "parameterlists" => [ \@commandparameterlist,
				       \@helpparameterlist,
				       \@versionparameterlist ] );

Getopt::Long::Configure('no_ignore_case');
Getopt::Long::Configure('bundling');

my $version=0.01;


#
#              some default values
#
my $perf_w=500;
my $perf_c=1000;
my $load_w=20;
my $load_c=30;
my $TIMEOUT=15;

#
#              get command line options the regular way
#
my $result = GetOptions("V|version"        => \$opt_V,
			"h|help"           => \$opt_h,
			"l|load"           => \$opt_l,
			"v|verbose"        => \$verbose,
			"w|warning=s"      => \$opt_w,
			"c|critical=s"     => \$opt_c,
			"H|hostname=s"     => \$opt_H,
			"u|url=s"          => \$opt_u,
		        "xml"              => \$xml);

#
#              handle the verbose stuff first
#
if ($verbose) {
  print "\n";
  print "check_apache nagios plugin version $version\n";
  print "\n";
  print "The nagios plugins come with ABSOLUTELY NO WARRANTY.  You may redistribute\n";
  print "copies of the plugins under the terms of the GNU General Public License.\n";
  print "For more information about these matters, see the file named COPYING.\n";
  print "\n";
  print "Copyright (c) 2001 Sebastian Hetze  Linux Information Systems AG\n";
  print "\n";
  print "\n";
  exit $ERRORS{'UNKNOWN'};
}

if ($opt_h) {
  print_help(\%helpdata, $xml);
  exit $ERRORS{'UNKNOWN'};
}

#
#              now get options the weired way and set the defaults
#              if nothing else is provided
#
$opt_H = shift unless ($opt_H);
unless ($opt_H) {
  print_usage(\%helpdata, $xml);
  exit $ERRORS{'UNKNOWN'};
}

my $autostring = "";
my ($warn, $alert);

if($opt_l) {
  $autostring="?auto";
  ($opt_w) || ($opt_w = shift) || ($opt_w = $load_w);
  $warn = $1 if ($opt_w =~ /([0-9]+)/);
  ($opt_c) || ($opt_c = shift) || ($opt_c = $load_c);
  $alert = $1 if ($opt_c =~ /([0-9]+)/);
} else {
  $autostring="";
  ($opt_w) || ($opt_w = shift) || ($opt_w = $perf_w);
  $warn = $1 if ($opt_w =~ /([0-9]+)/);
  ($opt_c) || ($opt_c = shift) || ($opt_c = $perf_c);
  $alert = $1 if ($opt_c =~ /([0-9]+)/);
}

($opt_u) || ($opt_u = shift) || ($opt_u = "/server-status");

#
#              dont let us wait forever...
#
$SIG{'ALRM'} = sub {
  print ("ERROR: No response from HTTP server (alarm)\n");
  exit $ERRORS{"UNKNOWN"};
};
alarm($TIMEOUT);

#
#              now we set things up for the real work
#              and fire up the request
#
my $ua = new LWP::UserAgent;
$ua->agent("Nagios/0.1 " . $ua->agent);

my $urlstring = "http://" . $opt_H . $opt_u . $autostring;
my $url = url($urlstring);

my $req = new HTTP::Request 'GET', $url;
my $res = $ua->request($req);

#
#              hopefully we´ve got something usefull
#
if ($res->is_success) {
  if($opt_l) {
    my ($accesses, $kbytes, $load, $uptime, $rps, $bps, $bpr, $busy, $idle, $score);
    my %scores = ();
    foreach $_ (split /^/m, $res->content) {
      next if /^\s*$/;
      #
      #              this is the load checking section
      #              we parse the whole content, just in case someone
      #              wants to use this some day in the future
      #
      if (/^Total Accesses:\s+([0-9.]+)/) { $accesses = $1; next; }
      if (/^Total kBytes:\s+([0-9.]+)/) { $kbytes = $1; next; }
      if (/^CPULoad:\s+([0-9.]+)\s+/) { $load = $1; next; }
      if (/^Uptime:\s+([0-9.]+)\s+/) { $uptime = $1; next; }
      if (/^ReqPerSec:\s+([0-9.]+)\s+/) { $rps = $1; next; }
      if (/^BytesPerSec:\s+([0-9.]+)\s+/) { $bps = $1; next; }
      if (/^BytesPerReq:\s+([0-9.]+)\s+/) { $bpr = $1; next; }
      if (/^BusyServers:\s+([0-9.]+)\s+/) { $busy = $1; next; }
      if (/^IdleServers:\s+([0-9.]+)\s+/) { $idle = $1; next; }
      if (/^Scoreboard:\s+([SRWKDLG_.]+)\s+/) { $score = $1; next; }
      print "Unknown Status\n";
      exit $ERRORS{"UNKNOWN"};
    }
    #
    #              now we even parse the whole scoreboard, just for fun
    #
    foreach my $scorepoint (split //m, $score) {
      if($scorepoint eq '.') { $scores{'.'}+=1; next; }  # Unused
      if($scorepoint eq '_') { $scores{'_'}+=1; next; }  # Waiting
      if($scorepoint eq 'S') { $scores{'S'}+=1; next; }  # Starting
      if($scorepoint eq 'R') { $scores{'R'}+=1; next; }  # Reading
      if($scorepoint eq 'W') { $scores{'W'}+=1; next; }  # Writing
      if($scorepoint eq 'K') { $scores{'K'}+=1; next; }  # Keepalive
      if($scorepoint eq 'D') { $scores{'D'}+=1; next; }  # DNS Lookup
      if($scorepoint eq 'L') { $scores{'L'}+=1; next; }  # Logging
      if($scorepoint eq 'G') { $scores{'G'}+=1; next; }  # Going
    }

    if($busy>$alert) {
      printf "HTTPD CRITICAL: %.0f servers running\n", $busy;
      exit $ERRORS{"CRITICAL"};
    }
    if($busy>$warn) {
      printf "HTTPD WARNING: %.0f servers running\n", $busy;
      exit $ERRORS{"WARNING"};
    }
    printf "HTTPD ok: %.0f servers running, %d idle\n", $busy, $idle;
    exit $ERRORS{"OK"};
  } else {
    #
    #              this is the performance check section
    #              We are a bit lazy here, no parsing of the initial data
    #              block and the scoreboard.
    #              However, you have the whole set of per server
    #              information to play with ;-)
    #              The actual performance is measured by adding up the
    #              milliseconds required to process the most recent
    #              requests of all instances and then taking the average.
    #
    my $lines = 0;
    my $req_sum = 0;
    foreach my $tablerow (split /<tr>/m, $res->content) {
      my ($empty,$Srv,$PID,$Acc,$M,$CPU,$SS,$Req,$Conn,$Child,$Slot,$Client,$VHost,$Request)
	= split /<td>/, $tablerow;
      if($Req) {
	$lines+=1;
	$req_sum+=$Req;
      }
      undef $Req;
    }
    my $average=$req_sum/$lines;
    if($average>$alert) {
      printf "HTTPD CRITICAL: average response time %.0f
	milliseconds\n", $average;
      exit $ERRORS{"CRITICAL"};
    }
    if($average>$warn) {
      printf "HTTPD WARNING: average response time %.0f
	milliseconds\n", $average;
      exit $ERRORS{"WARNING"};
    }
    if($average>0) {
      printf "HTTPD ok: average response time %.0f milliseconds\n",
        $average;
      exit $ERRORS{"OK"};
    }
    print "Unknown Status\n";
    exit $ERRORS{"UNKNOWN"};
  }
} else {
  print "HTTP request failed\n";
  exit $ERRORS{"CRITICAL"};
}

#
#              the end
#
