#!/usr/bin/perl -w ####################### check_httpd_status.pl ####################### # Version : 1.3 # Date : 06 Aug 2010 # # V1.3 Rewrite using Nagios::Plugin, rename check_httpd_status.pl (Stéphane Urbanovski) # V1.2 Updated perf data to be PNP compliant, added proxy option (Gerhard Lausser) # V1.1 Works with lighttpd server-status as well, added accesses perfdata # V1.0 Inital Release # # # Authors : Dennis D. Spreen (dennis at spreendigital.de) # Based on check_apachestatus.pl v1.4 by # De Bodt Lieven (Lieven dot DeBodt at gmail.com) # Updated by # Karsten Behrens (karsten at behrens dot in) # Geoff McQueen (geoff dot mcqueen at hiivesystems dot com ) # Dave Steinberg (dave at redterror dot net) # Updated by # Gerhard Lausser (gerhard dot lausser at consol dot de) # Licence : GPL - http://www.fsf.org/licenses/gpl.txt ############################################################# # # use strict; use warnings; #use Data::Dumper; use File::Basename; # get basename() use POSIX qw(setlocale); use Locale::gettext; use Nagios::Plugin ; use LWP::UserAgent; use HTTP::Status; # get status_message() use Time::HiRes qw(gettimeofday tv_interval); use Digest::MD5 qw(md5 md5_hex); # Globals my $VERSION = '1.3'; my $TIMEOUT = 10; my $PROGNAME = basename($0); # my $PROGNAME = 'check_httpd_status.pl'; # Retention files path (save previous values) : # FIXME: Make this configurable my $TempPath = '/tmp/'; # Check freshness og previous values my $MaxUptimeDif = 60*30; # Maximum uptime difference (seconds), default 30 minutes # i18n : setlocale(LC_MESSAGES, ''); textdomain('nagios-plugins-perl'); # Translate Apache / lighthttpd scoreboard status my %TranslationTable = ( 'APACHE' => { '.' => 'OpenSLot', '_' => 'Waiting', 'I' => 'Idle', 'S' => 'Starting', 'R' => 'Reading', 'W' => 'Sending', 'K' => 'Keepalive', 'D' => 'DNS', 'C' => 'Closing', 'L' => 'Logging', 'G' => 'Finishing', }, 'LIGHTTPD' => { '.' => 'Connect', 'C' => 'Close', 'E' => 'HardError', 'r' => 'Read', 'R' => 'ReadPost', 'W' => 'Write', 'h' => 'HandleRequest', 'q' => 'RequestStart', 'Q' => 'ReqestEnd', 's' => 'ResponseStart', 'S' => 'ResponseEnd', }, ); my $np = Nagios::Plugin->new( version => $VERSION, blurb => _gt('Apache / Lighthttpd server status monitor for Nagios'), usage => "Usage: %s [ -H [-p ] [-t ] [-w -c ] [-V] [-u ] [-U user -P pass -r realm]", extra => &showExtra(), timeout => $TIMEOUT+1 ); $np->add_arg ( spec => 'hostname|H=s', help => _gt('Name or IP address of host to check'), required => 1, ); $np->add_arg ( spec => 'port|p=i', help => _gt('Http port'), ); $np->add_arg ( spec => 'url|u=s', help => _gt('Specific URL to use, instead of the default http:///server-status?auto'), default => '/server-status?auto', ); $np->add_arg ( spec => 'user|U=s', help => _gt('Username for basic auth'), ); $np->add_arg ( spec => 'pass|P=s', help => _gt('Password for basic auth'), ); $np->add_arg ( spec => 'realm|r=s', help => _gt('Realm for basic auth'), ); $np->add_arg ( spec => 'proxy|X=s', help => _gt('Proxy-URL for http and https (mandatory)'), ); $np->add_arg ( spec => 'warn|w=s', help => _gt('Number of available slots that will cause a warning (Range format).'), ); $np->add_arg ( spec => 'crit|c=s', help => _gt('Number of available slots that will cause an error (Range format).'), ); $np->getopts; my $o_host = $np->opts->get('hostname'); my $o_port = $np->opts->get('port'); my $o_url = $np->opts->get('url'); my $o_user = $np->opts->get('user'); my $o_pass = $np->opts->get('pass'); my $o_realm = $np->opts->get('realm'); my $o_proxy = $np->opts->get('proxy'); my $o_warn_level = $np->opts->get('warn'); my $o_crit_level = $np->opts->get('crit'); # if (((defined($o_warn_level) && !defined($o_crit_level)) || # (!defined($o_warn_level) && defined($o_crit_level))) || # ((defined($o_warn_level) && defined($o_crit_level)) && (($o_warn_level != -1) && ($o_warn_level <= $o_crit_level))) # ) { # $np->nagios_exit(UNKNOWN, _gt("Check warn and crit!") ); # } my $o_proto = 'http'; my $url = undef; my $httpserver = 'APACHE'; #assume it is apache by default if (($o_url =~ m/^http(s?)\:\/\//i) ){ $url = $o_url; if ($1 eq 's') { $o_proto = 'https'; } } else { $url = $o_proto.'://' . $o_host; if ( defined($o_port)) { $url .= ':' . $o_port; } if ( $o_url !~ /^\// ) { $url .= '/'; } $url .= $o_url; } if ( $url !~ /\?auto$/ ) { $url .= '/server-status?auto'; } my $ua = LWP::UserAgent->new( protocols_allowed => ['http', 'https'], timeout => $TIMEOUT); $ua->agent($PROGNAME.'-'.$VERSION); logD("Web URL : $url"); my $req = HTTP::Request->new( GET => $url ); if ( defined($o_user) ) { $req->authorization_basic($o_user, $o_pass); } if ( defined($o_proxy) ) { if ($o_proto eq 'https') { if ($o_proxy =~ /^http:\/\/(.*?)\/?$/) { $o_proxy = $1; } $ENV{HTTPS_PROXY} = $o_proxy; } else { $ua->proxy(['http'], $o_proxy); } } my $timing0 = [gettimeofday]; my $response = $ua->request($req); my $timeelapsed = tv_interval($timing0, [gettimeofday]); if ( $response->is_error() ) { my $err = $response->code." ".status_message($response->code)." (".$response->message.")"; my $status = CRITICAL; $np->add_message(CRITICAL, ); if (defined($o_warn_level) || defined($o_crit_level)) { $status = UNKNOWN; } $np->nagios_exit($status, _gt("HTTP error: ").$err ); } elsif ( ! $response->is_success() ) { my $err = $response->code." ".status_message($response->code)." (".$response->message.")"; $np->add_message(CRITICAL, _gt("Internal error: ").$err ); } my $webcontent = $response->content; logD("Web content :\n----------------------------\n".$webcontent."\n----------------------------"); my $patternFound = 0; my $Uptime = 0; # if ( $webcontent =~ m/Uptime: (.*?)\n/) { # $Uptime = $1; # $patternFound++; # } ### FIXME XH 20171204 catch ServerUptimeSeconds, not [Server]Uptime. Change in server-status between 2.2 & 2.4 if ( $webcontent =~ m/\b(ServerUptimeSeconds|Uptime): (\d+)\n/) { $Uptime = $2; $patternFound++; } my $TotalAccesses = 0; if ( $webcontent =~ m/Total Accesses: (.*?)\n/) { $TotalAccesses = $1; $patternFound++; } else { $np->add_message(WARNING, _gt('"Total Accesses" not set ! (need extented status ?)') ); } my $TotalKbytes = 0; if ( $webcontent =~ m/Total kBytes: (.*?)\n/) { $TotalKbytes = $1; $patternFound++; } my $ScoreBoard = ''; if ( $webcontent =~ m/Scoreboard: (.*?)\n/) { $ScoreBoard = $1; $patternFound++; } else { $np->add_message(WARNING, _gt("Scoreboard not found in reponse !") ); } my $BusyWorkers = 0; if ( $webcontent =~ m/(BusyWorkers|BusyServers): (.*?)\n/) { $BusyWorkers = $2; $patternFound++; if ($1 eq 'BusyServers') { $httpserver = 'LIGHTTPD'; } } my $IdleWorkers = 0; if ( $webcontent =~ m/(IdleWorkers|IdleServers): (.*?)\n/) { $IdleWorkers = $2; $patternFound++; } if ( $patternFound <= 1) { $np->nagios_exit(CRITICAL, _gt("Server-status informations not found !") ); } my $TempFile = $TempPath.$o_host.'_check_httpd_status'.md5_hex($url); my $LastUptime = 0; my $LastTotalAccesses = 0; my $LastTotalKbytes = 0; if ((-e $TempFile) && (-r $TempFile) && (-w $TempFile)) { if ( !open (RETENTION_FILE, '<',$TempFile) ) { $np->nagios_exit(CRITICAL, sprintf(_gt('Error while trying to read %s !'),$TempFile) ); } $LastUptime = ; $LastTotalAccesses = ; $LastTotalKbytes = ; close (RETENTION_FILE); chomp($LastUptime); chomp($LastTotalAccesses); chomp($LastTotalKbytes); logD("LastUptime=$LastUptime LastTotalAccesses=$LastTotalAccesses LastTotalKbytes=$LastTotalKbytes (from $TempFile)"); } else { logD("Retention file '$TempFile' not found"); } if ( !open (RETENTION_FILE, '>',$TempFile) ) { $np->nagios_exit(CRITICAL, sprintf(_gt('Error while trying to write to %s !'),$TempFile) ); } print RETENTION_FILE "$Uptime\n"; print RETENTION_FILE "$TotalAccesses\n"; print RETENTION_FILE "$TotalKbytes\n"; close (RETENTION_FILE); my $ReqPerSec = 0; my $BytesPerReq = 0; my $BytesPerSec = 0; my $DiffTime = $Uptime-$LastUptime; if ( ($DiffTime > 0) && ($DiffTime < $MaxUptimeDif) && ($TotalAccesses >= $LastTotalAccesses) && ($TotalKbytes >= $LastTotalKbytes) ) { $ReqPerSec = ($TotalAccesses-$LastTotalAccesses)/$DiffTime; $np->add_perfdata( 'label' => 'ReqPerSec', 'value' => sprintf('%.3f',$ReqPerSec), 'uom' => 'req/s', ); $BytesPerSec = (($TotalKbytes-$LastTotalKbytes)*1024)/$DiffTime; $np->add_perfdata( 'label' => 'BytesPerSec', 'value' => sprintf('%.2f',$BytesPerSec), 'uom' => 'B/s', ); my $Accesses = ($TotalAccesses-$LastTotalAccesses); if ( $Accesses > 0 ) { $BytesPerReq = (($TotalKbytes-$LastTotalKbytes)*1024)/$Accesses; $np->add_perfdata( 'label' => 'BytesPerReq', 'value' => sprintf('%.2f',$BytesPerReq), 'uom' => 'B/req', ); } } my $CountOpenSlots = ($ScoreBoard =~ tr/\.//); my $TotalSlots = $CountOpenSlots+$IdleWorkers+$BusyWorkers; my $InfoData = ''; my %WorkerStates = (); map( $WorkerStates{$_}++ , split(//,$ScoreBoard) ); foreach my $slotState ( sort(keys(%{$TranslationTable{$httpserver}})) ) { my $val = 0 ; if ( defined($WorkerStates{$slotState}) ) { $val = $WorkerStates{$slotState}; } $np->add_perfdata( 'label' => $TranslationTable{$httpserver}{$slotState}, 'value' => $val, ); } if ( $httpserver eq 'APACHE' ) { $InfoData = sprintf ("%.3f s - %d/%d Busy/Idle, %d/%d Open/Total, %.1f requests/s, %.1f B/s, %d B/request", $timeelapsed, $BusyWorkers, $IdleWorkers, $CountOpenSlots, $TotalSlots, $ReqPerSec, $BytesPerSec, $BytesPerReq); } else { $InfoData = sprintf ("%.3f sec. response time, Busy/Idle %d/%d, slots %d, ReqPerSec %.1f, BytesPerReq %d, BytesPerSec %d", $timeelapsed, $BusyWorkers, $IdleWorkers, $TotalSlots, $ReqPerSec, $BytesPerReq, $BytesPerSec); } $np->add_message(OK, $InfoData); my $fw = $CountOpenSlots + $IdleWorkers; logD("FreeWorker = ".$fw); my $tmp_status = $np->check_threshold( check => $fw, warning => $o_warn_level, critical => $o_crit_level, ); $np->add_perfdata( 'label' => 'FreeWorker', 'value' => $fw, 'min' => 0, 'threshold' => $np->threshold() ); if ( $tmp_status ) { $np->add_message($tmp_status, sprintf(_gt(" Not enough free worker (%d) !"),$fw) ); } my ($status, $message) = $np->check_messages('join' => ' '); $np->nagios_exit($status, $message ); sub logD { print STDERR 'DEBUG: '.$_[0]."\n" if ($np->opts->verbose); } sub logW { print STDERR 'WARNING: '.$_[0]."\n" if ($np->opts->verbose); } # Gettext wrapper sub _gt { return gettext($_[0]); } sub showExtra { return <