[BACK]Return to check_rrd CVS log [TXT][DIR] Up to [local] / nagios / check_rrd / bin

File: [local] / nagios / check_rrd / bin / check_rrd (download)

Revision 1.12, Sat May 24 21:34:05 2008 UTC (15 years, 11 months ago) by andrew
Branch: MAIN
CVS Tags: HEAD
Changes since 1.11: +19 -13 lines

Change the name of a iterator
fix some logic that was somehow busted.

#!/usr/bin/perl -T
# $RedRiver: check_rrd,v 1.11 2008/05/24 13:13:15 andrew Exp $
########################################################################
# check_rrd *** A nagios check for changing averages in rrds
# 
# 2006.05.01 #*#*# andrew fresh <andrew@mad-techies.org>
########################################################################
# Want to make this do 3 checks
#  * check percentage difference on day, hour, 5 min
#  * check actual number high/low
#  * check last time
########################################################################
use strict;
use warnings;
use RRDs;

#use Data::Dumper;

%ENV = ();

use constant NAGIOS_OUTPUT => 1;

use POSIX;
use lib "/usr/local/libexec/nagios";
use utils qw($TIMEOUT %ERRORS &print_revision &support);

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

my $PROGNAME = "check_rrd";

my %TIMES = (
	FIVE_MINUTES =>           5 * 60,
	HOUR         =>      1 * 60 * 60,
	DAY          => 1 * 24 * 60 * 60,
	WEEK         => 7 * 24 * 60 * 60
);

my %Checks = (
	default => {
		abs  => {
			warn  => { min => undef, max => undef, },
			crit  => { min => undef, max => undef, },
		},
		change => {
			warn  => { max =>  5 },
			crit  => { max => 10 },
		},
		time => {
			warn => 15 * 60, # 15 minutes
			crit => 60 * 60, # 1 hour
		},
	},
);

my $state = 'UNKNOWN'; # tells whether the it is warning, critical, or OK
my %states; # This stores the count of states;
my $filename;
my @rras;
my @warning;
my @critical;
my $time;
my @time = (
	'WEEK',
	'DAY',
	'HOUR',
);
my $opt_V;


#Option checking
my $status = GetOptions(
	"version|V"       => \$opt_V,
	"filename|f=s"    => \$filename,
	"rra|r=s"         => \@rras,
	"time|t=s"        => \$time,
	"warning|w=s"     => \@warning,
	"critical|c=s"    => \@critical,
);
@rras     = split(/,/,join(',',@rras));
@critical = split(/,/,join(',',@critical));
@warning  = split(/,/,join(',',@warning));

if ($time) {
	@time = split /[:,]/, $time;
	for (0..2) {
		unless ($time[$_]) {
			die "Time usage needs 3 entries, like 'WEEK:DAY:HOUR'";
		}
		unless (exists $TIMES{ $time[$_] }) {
			die "Times need to be one of WEEK, DAY, HOUR or FIVE_MINUTES";
		}
		#print $TIMES{ $time[$_] }, "\n";
	}
}

#print Dumper $time, \@time;

update_checks(\%Checks, 'crit', \@critical);
update_checks(\%Checks, 'warn', \@warning);

#print Dumper \%Checks;

if ($status == 0) {
	print_help() ;
	exit $ERRORS{'OK'};
}

if ($opt_V) {
	print_revision($PROGNAME,'$Revision: 1.12 $ ');
	exit $ERRORS{'OK'};
}

unless (defined $filename) {
	print_help();
	exit $ERRORS{'OK'};
}

my $info = RRDs::info($filename) or die "Problem with info from '$filename': $!";

my $resolution = $info->{'step'};
my $last = $info->{'last_update'};

my $age = time - $last;
my $max_age_crit = $Checks{'time'}{'abs'}{'crit'} || 
    $Checks{'default'}{'time'}{'crit'};
my $max_age_warn = $Checks{'time'}{'abs'}{'warn'} || 
    $Checks{'default'}{'time'}{'warn'};

if ($age > $max_age_crit) {
	push @{ $states{'CRITICAL'} }, 
		"Last update was too long ago";
} elsif ($age > $max_age_warn) {
	push @{ $states{'WARNING'} }, 
		"Last update was too long ago";
} else {
	push @{ $states{'OK'} }, 
		"Last update was within time limits";
}

my $end = int($last / $resolution) * $resolution;
my $start = int( ($end - $TIMES{ $time[0] }) / $resolution) * $resolution;

my ($first, $step, $names, $data) = RRDs::fetch(
	$filename, 
	'AVERAGE', 
	'-r', ($resolution / 2), 
	'-s', $start, 
	'-e', $end
) or die "Problem fetching from '$filename': $!";

#print Dumper $data;
#print Dumper \%TIMES;

my %totals;
foreach my $line (@$data) {
	foreach my $i (0 .. $#{ $line }) {
		next unless defined $line->[$i];
		foreach my $time (keys %TIMES) {
			#print "[$end] - [$start] = [" . ($end - $start) . "] <= [$TIMES{$time}]\n";
			if (($end - $start) <= $TIMES{$time}) {
				foreach ('max', 'min') {
					$totals{ $names->[$i] }{$time}{$_} = $line->[$i]
						unless defined $totals{ $names->[$i] }{$time}{$_};
				}
				no warnings q/uninitialized/;
				$totals{ $names->[$i] }{$time}{'count'}++;
				$totals{ $names->[$i] }{$time}{'total'} += $line->[$i];
				$totals{ $names->[$i] }{$time}{'max'} = $line->[$i]
					if $totals{ $names->[$i] }{$time}{'max'} < $line->[$i];
				$totals{ $names->[$i] }{$time}{'min'} = $line->[$i]
					if $totals{ $names->[$i] }{$time}{'min'} > $line->[$i];
			}
		}
	}
	$start += $step;
}

foreach my $key (keys %totals) {
	foreach my $length (keys %{ $totals{$key} }) {
		$totals{$key}{$length}{'average'} = 
		     $totals{$key}{$length}{'total'} / 
	         $totals{$key}{$length}{'count'}
		  if $totals{$key}{$length}{'count'};
	}
}

#print Dumper \%totals;

my %check_status;
foreach my $key (keys %totals) {
	next if @rras and not grep { /^$key$/ } @rras;

	my %w;
	$w{ $time[0] } = $totals{$key}{ $time[0] }{'average'};
	$w{ $time[1] } = $totals{$key}{ $time[1] }{'average'};
	$w{ $time[2] } = $totals{$key}{ $time[2] }{'average'};


	TYPE: foreach my $type ('change', 'abs') {
		foreach my $sev ('crit', 'warn', 'ok') {
			my $min = $Checks{$key}{$type}{$sev}{'min'} ||
			    $Checks{'default'}{$type}{$sev}{'min'};
			my $max = $Checks{$key}{$type}{$sev}{'max'} ||
			    $Checks{'default'}{$type}{$sev}{'max'};

			my $res = 'UNKNOWN';
			if ($sev eq 'crit') {
				$res = 'CRITICAL';
			} elsif ($sev eq 'warn') {
				$res = 'WARNING';
			} elsif ($sev eq 'ok') {
				$res = 'OK';
			}

			if ($type eq 'change') {
				my %deltas = (
					$time[1] => [ $w{ $time[0] }, $w{ $time[1] } ],
					$time[2] => [ $w{ $time[1] }, $w{ $time[2] } ],
				);

				foreach my $d (keys %deltas) {
					my $delta = calc_delta(@{ $deltas{$d} });
					if ((not $check_status{$key}{$d . ' change'}) &&
						( $res eq 'OK' ||
					    	( not range_ok($delta, $min, $max) )
				    )) {
						$check_status{$key}{$d . ' change'} = {
							status  => $res,
							message => $delta . '%',
						};
					}
				}

			} else {
				foreach my $time (@time) {
					if ((not $check_status{$key}{$time}) &&
						( $res eq 'OK' ||
					    	( not range_ok($w{$time}, $min, $max) )
				    	)) {
						$check_status{$key}{$time} = {
							status  => $res,
							message => $w{$time},
						};
					}
				}
			}
		}
	}
}

my $have_results = 0;
foreach my $s (sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS) {
	foreach my $DS (sort keys %check_status) {
		foreach my $key (sort keys %{ $check_status{$DS} }) {
			next unless $check_status{$DS}{$key}{'status'} eq $s;
			$have_results++;
			$state = $s;
			push @{ $states{$s} }, 
				"$key ($DS) " . $check_status{$DS}{$key}{'message'};
		}
	}
}

#print Dumper \%check_status, \%states, $state;

foreach my $error (sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS) {
	if (exists $states{$error}) {
		if (NAGIOS_OUTPUT) {
			print "$error (" . scalar(@{ $states{ $error } }) . ")";
			unless ($error eq 'OK') {
				print '<br>';
				print map {" - $_<br>"} @{ $states{ $error } };
			}
		} else {
			print "$error (" . scalar(@{ $states{ $error } }) . "):\n";
			foreach (@{ $states{ $error } }) {
				print "   $_\n";
			}
		}
	}
}
if ($have_results == 0) {
	print "No results found\n";
}

exit $ERRORS{$state};


sub print_help {
	print <<EOL;
$PROGNAME plugin for Nagios checks averages in rrds
    $PROGNAME -f <FILENAME> [-w limit] [-c limit]

Usage:
    -f, --filename=FILE
        FILE to load checks from (defaults to /etc/sensorsd.conf)
    -w, --warning=RANGE or single ENTRY
        Exit with WARNING status if outside of RANGE or if != ENTRY
    -c, --critical=RANGE or single ENTRY
        Exit with CRITICAL status if outside of RANGE or if != ENTRY

EOL
        print_revision($PROGNAME, '$Revision: 1.12 $');
}


sub range_ok
{
	my ($num, $min, $max) = @_;

	if (defined $min && defined $max &&
		length  $min && length  $max) {
		if ($num < $min || $num > $max) {
			return 0;
		}
	} elsif (defined $min && length $min) {
		if ($num < $min) {
			return 0;
		}
	} elsif (defined $max && length $max) {
		if ($num > $max) {
			return 0;
		}
	}

	return 1;
}

sub calc_delta
{
	my ($primary, $secondary) = @_;

	my $delta = 0;
	$delta = ($primary - $secondary) / $primary * 100 if $primary != 0;

	return $delta;
}

sub update_checks
{
	my $checks = shift;
	my $type   = shift;
	my $new    = shift;

	foreach (@{ $new }) {
		my ($check, $vals);
		if ($_ =~ /=/) {
			($check, $vals) = split /=/, $_, 2;
		} else {
			$vals 	= $_;
			$check = 'default';
		}

		my ($DS, $key)  = split /:/, $check, 2;
		$DS  ||= 'default';
		$key ||= 'abs';

		my ($min, $max) = split /:/, $vals;

		$checks->{$DS}->{$key}->{$type}->{'max'} = $max;
		$checks->{$DS}->{$key}->{$type}->{'min'} = $min;
	}
}