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;
}
}