#!/usr/bin/perl -T # $RedRiver: check_rrd,v 1.5 2007/01/10 01:17:44 andrew Exp $ ######################################################################## # check_rrd *** A nagios check for changing averages in rrds # # 2006.05.01 #*#*# andrew fresh ######################################################################## # 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 => 0; 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, ONE_HOUR => 1 * 60 * 60, ONE_DAY => 1 * 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 $opt_V; #Option checking my $status = GetOptions( "version|V" => \$opt_V, "filename|f=s" => \$filename, "rra|r=s" => \@rras, "warning|w=s" => \@warning, "critical|c=s" => \@critical, ); @rras = split(/,/,join(',',@rras)); @critical = split(/,/,join(',',@critical)); @warning = split(/,/,join(',',@warning)); 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.6 $ '); 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{'ONE_DAY'}) / $resolution) * $resolution; my ($first, $step, $names, $data) = RRDs::fetch( $filename, 'AVERAGE', '-r', $resolution, '-s', $start, '-e', $end ) or die "Problem fetching from '$filename': $!"; my %totals; foreach my $line (@$data) { foreach my $i (0 .. $#{ $line }) { next unless defined $line->[$i]; foreach my $key (keys %TIMES) { if ($end - $TIMES{$key} < $start) { foreach ('max', 'min') { $totals{ $names->[$i] }{$key}{$_} = $line->[$i] unless defined $totals{ $names->[$i] }{$key}{$_}; } no warnings q/uninitialized/; $totals{ $names->[$i] }{$key}{'count'}++; $totals{ $names->[$i] }{$key}{'total'} += $line->[$i]; $totals{ $names->[$i] }{$key}{'max'} = $line->[$i] if $totals{ $names->[$i] }{$key}{'max'} < $line->[$i]; $totals{ $names->[$i] }{$key}{'min'} = $line->[$i] if $totals{ $names->[$i] }{$key}{'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'}; } } my %check_status; foreach my $key (keys %totals) { next if @rras and not grep { /^$key$/ } @rras; my %w; $w{'Minute'} = $totals{$key}{'FIVE_MINUTES'}{'average'}; $w{'Hour'} = $totals{$key}{'ONE_HOUR'}{'average'}; $w{'Day'} = $totals{$key}{'ONE_DAY'}{'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 = ( Hour => [ $w{'Day'}, $w{'Hour'} ], Minute => [ $w{'Hour'}, $w{'Minute'} ], ); foreach my $d (keys %deltas) { my $delta = calc_delta(@{ $deltas{$d} }); if ((not $check_status{$key}{$d . ' change'}) && ( $res eq 'OK' || ( not range_ok(abs($delta), $min, $max) ) )) { $check_status{$key}{$d . ' change'} = { status => $res, message => $delta . '%', }; } } } else { foreach my $time ('Day', 'Hour', 'Minute') { 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 '
'; print map {" - $_
"} @{ $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 < [-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.6 $'); } 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; } }