=================================================================== RCS file: /cvs/nagios/check_hw_sensors/check_hw_sensors,v retrieving revision 1.1 retrieving revision 1.14 diff -u -r1.1 -r1.14 --- nagios/check_hw_sensors/check_hw_sensors 2006/05/01 19:11:23 1.1 +++ nagios/check_hw_sensors/check_hw_sensors 2006/05/04 02:30:29 1.14 @@ -1,11 +1,458 @@ -#!/usr/bin/perl -# $RedRiver$ -######################################################################## -# check_sensors *** A nagios check for OpenBSD sensors -# -# 2006.05.01 #*#*# andrew fresh -######################################################################## -use strict; -use warnings; - - +#!/usr/bin/perl -T +# $RedRiver: check_hw_sensors,v 1.13 2006/05/03 22:16:42 andrew Exp $ +######################################################################## +# check_hw_sensors *** A nagios check for OpenBSD hw.sensors +# +# 2006.05.01 #*#*# andrew fresh +######################################################################## +# TODO: +# Really need real documentation. +# +# I want the ability to just check the "status" entry that is in +# some output. For example the OK here: +# hw.sensors.1=esm0, CPU 1, OK, temp, 31.00 degC / 87.80 degF +######################################################################## +use strict; +use warnings; + +#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_hw_sensors"; + +my $SYSCTL = '/sbin/sysctl'; +my $GETCAP = '/usr/bin/getcap'; +my $BASE = 'hw.sensors'; +my $DEFAULT_CONFIG = '/etc/sensorsd.conf'; + +my $state = 'UNKNOWN'; # tells whether the it is warning, critical, or OK +my %states; # This stores the count of states; +my $filename; +my $sensor; +my $warning; +my $critical; +my $opt_h; +my $opt_V; + +my $CHECK_SENSOR = $BASE; +my %CHECKS; +my %SENSORS; + +#Option checking +my $status = GetOptions( + "version|V" => \$opt_V, + "help|h" => \$opt_h, + "filename|f:s" => \$filename, + "sensor|s=s" => \$sensor, + "warning|w=s" => \$warning, + "critical|c=s" => \$critical, +); + +# set the default this way so it only happens if someone typed -f or --filename +$filename = $DEFAULT_CONFIG if (defined $filename && $filename eq ''); + +if ($status == 0) { + print_help() ; + exit $ERRORS{'OK'}; +} + +if ($opt_V) { + print_revision($PROGNAME,'$Revision: 1.14 $ '); + exit $ERRORS{'OK'}; +} + +if ($opt_h) { + print_help(); + exit $ERRORS{'OK'}; +} + +unless ( ( + defined $filename || + (defined $sensor && ($warning || $critical)) + ) && + ( (!defined $filename) || (!defined $sensor) ) +) { + print_help(); + exit $ERRORS{'OK'}; +} + + +if (defined $sensor) { + if ($sensor !~ /^$BASE/) { + $sensor = $BASE . '.' . $sensor; + } + $CHECK_SENSOR = $sensor; + + $CHECKS{$sensor} = { + 'warn' => $warning, + 'crit' => $critical, + }; +} elsif (defined $filename) { + %CHECKS = parse_file($filename); +} + +open my $sysctl, "-|", $SYSCTL, $CHECK_SENSOR + or die "Couldn't open sysctl: $!"; +while (<$sysctl>) { +#while (<>) { + chomp; + my ($id, $output) = split /=/; + my @o = split /,\s*/, $output; + + my $source = $o[0]; + my $descr = $o[1]; + my $status = $o[2] if @o == 5; + my $type = $o[-2]; + my $data = $o[-1]; + + $SENSORS{$id} = { + id => $id, + output => $output, + source => $source, + description => $descr, + status => $status, + type => $type, + data => $data, + }; + +} +close $sysctl; + +sub as_if_numeric { + my $_a = $a; + my $_b = $b; + $_a =~ s/\D//g; + $_b =~ s/\D//g; + $_a <=> $_b; +} + +foreach my $check (sort as_if_numeric keys %CHECKS) { + if (exists $SENSORS{$check}) { + my $r = check_sensor($CHECKS{$check}, $SENSORS{$check}); + push @{ $states{ $r } }, + $check . '=' . $SENSORS{$check}{'output'}; + } else { + push @{ $states{'UNKNOWN'} }, $check . '=No sensor with this id'; + } +} + +#print Dumper \%states; + +$state = 'OK'; +my $have_results = 0; +foreach my $error (sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS) { + if (exists $states{$error}) { + $have_results++; + $state = $error; + } +} +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 parse_file { + my $filename = shift; + my %contents; + + open my $fh, '-|', $GETCAP, '-a', '-f', $filename + or die "Couldn't open FILE '$GETCAP -a -f $filename': $!"; + while (<$fh>) { + chomp; + my ($key, @c) = split /\:/; + foreach (@c) { + my ($k, $v) = split /\=/; + $contents{$key}{$k} = $v; + } + } + close $fh; + + return %contents; +} + +sub parse_check { + my $type = shift; + my $check = shift; + + foreach my $code ('crit', 'warn') { + if (defined $check->{$code} && $check->{$code} =~ /:/) { + if (my ($low, $high) = split /:/, $check->{$code}) { + $check->{$code . '.low'} = $low; + $check->{$code . '.high'} = $high; + } + delete $check->{$code}; + } + + foreach my $severity ('low', 'high') { + if (defined $check->{$severity}) { + $check->{ $code . '.' . $severity } = $check->{$severity} + unless defined $check->{ $code . '.' . $severity }; + } + } + no warnings 'uninitialized'; + $check->{$code} = [ split /,\s*/, $check->{$code} ]; + } + + return $check; +} + +sub check_sensor { + my $check = shift; + my $sensor = shift; + my $result = 'UNKNOWN'; + my %errors = ( + 'warn' => 'WARNING', + 'crit' => 'CRITICAL', + ); + + return $result unless ref $sensor eq 'HASH'; + + $check = parse_check($sensor->{'type'}, $check); + #print Dumper $check, $sensor; + + $result = 'OK'; + + foreach my $code ('warn', 'crit') { + if ( + $sensor->{'type'} eq 'volts_dc' || + $sensor->{'type'} eq 'fanrpm' || + $sensor->{'type'} eq 'raw' + ) { + my $data = $sensor->{'data'}; + $data =~ s/[^\d\.]//g; + unless (length $data) { + warn "INVALID DATA ($sensor->{'data'}) for '$sensor->{'id'}'"; + next; + } + + if ( + defined $check->{$code . ".low"} || + defined $check->{$code . ".high"} + ) { + if (defined $check->{$code . ".low"}) { + my $c = $check->{$code . ".low"}; + $c =~ s/[^\d\.]//g; + + unless (length $c) { + warn "INVALID CHECK (" . $check->{$code . ".low"} . + ") for '$sensor->{'id'}:$code.low'"; + next; + } + + $result = $errors{$code} + if ($c >= $data); + } + if (defined $check->{$code . ".high"}) { + my $c = $check->{$code . ".high"}; + $c =~ s/[^\d\.]//g; + unless (length $c) { + warn "INVALID CHECK (" . $check->{$code . ".high"} . + ") for '$sensor->{'id'}:$code.high'"; + next; + } + + $result = $errors{$code} + if ($c <= $data); + } + } elsif (@{ $check->{$code} }) { + my $matched = 0; + foreach my $c (@{ $check->{$code} }) { + $c =~ s/[^\d\.]//g; + unless (length $c) { + warn "INVALID CHECK (" . $c . + ") for '$sensor->{'id'}:$code'"; + next; + } + + if ($_ eq $data) { + $matched = 1; + last; + } + } + $result = $errors{$code} unless $matched; + } + + } elsif ($sensor->{'type'} eq 'temp') { + my ($degC, $degF) = split /\//, $sensor->{'data'}; + $degC =~ s/[^\d\.]//g; + $degF =~ s/[^\d\.]//g; + if ( + defined $check->{$code . ".low"} || + defined $check->{$code . ".high"} + ) { + if (defined $check->{$code . ".low"}) { + my $c = $check->{$code . ".low"}; + my $data = $degC; + + $data = $degF if ($c =~ /F/i); + unless (length $data) { + warn "INVALID DATA (" . $sensor->{'data'} . + ") for '$sensor->{'id'}'"; + next; + } + + $c =~ s/[^\d\.]//g; + unless (length $c) { + warn "INVALID CHECK (" . $check->{$code . ".low"} . + ") for '$sensor->{'id'}':$code.low"; + next; + } + + $result = $errors{$code} + if ($c >= $data); + } + if (defined $check->{$code . ".high"}) { + my $c = $check->{$code . ".high"}; + + my $data = $degC; + $data = $degF if ($c =~ /F/i); + unless (length $data) { + warn "INVALID DATA (" . $sensor->{'data'} . + ") for '$sensor->{'id'}'"; + next; + } + + $c =~ s/[^\d\.]//g; + unless (length $c) { + warn "INVALID CHECK (" . $check->{$code . ".high"} . + ") for '$sensor->{'id'}:$code.high'"; + next; + } + + $result = $errors{$code} + if ($c <= $data); + } + } elsif (@{ $check->{$code} }) { + + my $matched = 0; + foreach my $c (@{ $check->{$code} }) { + my $data = $degC; + + $data = $degF if ($c =~ /F/i); + unless (length $data) { + warn "INVALID DATA (" . $sensor->{'data'} . + ") for '$sensor->{'id'}'"; + next; + } + + $c =~ s/[^\d\.]//g; + unless (length $c) { + warn "INVALID CHECK (" . $c . + ") for '$sensor->{'id'}':$code"; + next; + } + + if ($_ eq $data) { + $matched = 1; + last; + } + } + $result = $errors{$code} unless $matched; + } + + } elsif ( + $sensor->{'type'} eq 'drive' || + $sensor->{'type'} eq 'indicator' + ) { + if (@{ $check->{$code} }) { + my $matched = 0; + foreach (@{ $check->{$code} }) { + if ($_ eq $sensor->{'data'}) { + $matched = 1; + last; + } + } + $result = $errors{$code} unless $matched; + } + + } else { + $result = 'UNKNOWN'; + } + + } + + return $result; +} + +sub print_help { + print <]|(-s -w limit -c limit)) + +Usage: + -f, --filename=FILE + FILE to load checks from (defaults to /etc/sensorsd.conf) + -s, --sensor=ID + ID of a single sensor. "-s 0" means hw.sensors.0. + -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 + + -h (--help) usage help + +FILE is in the same format as sensorsd.conf(5) plus some additional +entries. These additional entries in the file are ignored by +sensorsd(8). + +$PROGNAME understands the following entries: + + low, high, crit, warn, crit.low, crit.high, warn.low, warn.high + +An ENTRY depends on the type. The descriptions in sensorsd.conf(5) +can be used when appropriate, or you can use the following: + + volts_dc, fanrpm or raw - Anything that includes digits. + Both the value of the check and the value of the sensor + response that are not either a digit or period are stripped + and then the two resultant values are compared. + + temp - Can be as above, but if the entry has an F in it, + it compares farenheit, otherwise it uses celcius. + + indicator or drive - does a case sensitive match of each + entry in the comma separated list and if it does not match + any of the entries, it matches the status. + +The entries 'crit' or 'warn' (or the -c or -w on the command line) +may be a RANGE or a comma separated list of acceptable values. +The comma separated list of values contains a list of things that +will NOT cause the status. This is possibly counterintuitive, but +you are more likely to know good values than bad values. + +A RANGE is a low ENTRY and a high ENTRY separated by a colon (:). +It can also be low: or :high with the other side left blank to only +make the single check.. + +EOL + + print_revision($PROGNAME, '$Revision: 1.14 $'); +} +