=================================================================== RCS file: /cvs/nagios/check_hw_sensors/check_hw_sensors,v retrieving revision 1.3 retrieving revision 1.18 diff -u -r1.3 -r1.18 --- nagios/check_hw_sensors/check_hw_sensors 2006/05/02 02:39:23 1.3 +++ nagios/check_hw_sensors/check_hw_sensors 2006/12/02 02:15:17 1.18 @@ -1,18 +1,22 @@ -#!/usr/bin/perl -# $RedRiver: check_hw_sensors,v 1.2 2006/05/02 01:29:33 andrew Exp $ +#!/usr/bin/perl -T +# $RedRiver: check_hw_sensors,v 1.17 2006/10/25 23:30:23 andrew Exp $ ######################################################################## # check_hw_sensors *** A nagios check for OpenBSD hw.sensors # # 2006.05.01 #*#*# andrew fresh ######################################################################## +# TODO: +# Really need real documentation. +######################################################################## use strict; use warnings; -#use Data::Dumper; +%ENV = (); +use constant NAGIOS_OUTPUT => 1; + use POSIX; -#use lib "/usr/local/libexec/nagios"; -use lib $ENV{'HOME'}; +use lib "/usr/local/libexec/nagios"; use utils qw($TIMEOUT %ERRORS &print_revision &support); use Getopt::Long; @@ -20,57 +24,104 @@ my $PROGNAME = "check_hw_sensors"; -my $SYSCTL = 'sysctl'; -my $GETCAP = 'getcap'; +my $SYSCTL = '/sbin/sysctl'; +my $GETCAP = '/usr/bin/getcap'; my $BASE = 'hw.sensors'; +my $DEFAULT_CONFIG = '/etc/sensorsd.conf'; -my $CHECK_SENSOR = $BASE; -my %CHECKS; -my %SENSORS; - my $state = 'UNKNOWN'; # tells whether the it is warning, critical, or OK my %states; # This stores the count of states; my $filename; +my $ignore_status; my $sensor; my $warning; my $critical; -my $opt_h ; -my $opt_V ; +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, + "version|V" => \$opt_V, + "filename|f:s" => \$filename, + "ignore-status|i" => \$ignore_status, + "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 = '/etc/sensorsd.conf' if (defined $filename && $filename eq ''); +$filename = $DEFAULT_CONFIG if (defined $filename && $filename eq ''); +# Stuff is output in this file by print_sensor() +# http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/sysctl/sysctl.c +my @Type_Map = ( + { + type => 'temp', + regex => qr/\sdegC$/, + }, + { + type => 'fanrpm', + regex => qr/\sRPM$/, + }, + { + type => 'volts_dc', + regex => qr/\sV\sDC$/, + }, + { + type => 'amps', + regex => qr/\sA$/, + }, + { + type => 'watthour', + regex => qr/\sWh$/, + }, + { + type => 'amphour', + regex => qr/\sAh$/, + }, + { + type => 'indicator', + regex => qr/^(On|Off)$/, + }, + { + type => 'integer', + regex => qr/\sraw$/, + }, + { + type => 'lux', + regex => qr/\slx$/, + }, + { + type => 'drive', + regex => qr/^drive\s/, + }, + { + type => 'timedelta', + regex => qr/\ssecs$/, + }, +); + if ($status == 0) { print_help() ; exit $ERRORS{'OK'}; } if ($opt_V) { - print_revision($PROGNAME,'$Revision: 1.3 $ '); + print_revision($PROGNAME,'$Revision: 1.18 $ '); exit $ERRORS{'OK'}; } -if ($opt_h) { - print_help(); - exit $ERRORS{'OK'}; -} - -unless ( ( - defined $filename || - (defined $sensor && ($warning || $critical)) - ) && - ( (!defined $filename) || (!defined $sensor) ) +unless ( + ( + defined $filename || + (not defined $ignore_status) || + (defined $sensor && ($warning || $critical)) + ) && + ( (!defined $filename) || (!defined $sensor) ) ) { print_help(); exit $ERRORS{'OK'}; @@ -83,10 +134,9 @@ } $CHECK_SENSOR = $sensor; - $CHECKS{$sensor} = { - 'warning' => $warning, - 'critical' => $critical, - }; + $CHECKS{$sensor}{'warn'} = $warning if defined $warning; + $CHECKS{$sensor}{'crit'} = $critical if defined $critical; + } elsif (defined $filename) { %CHECKS = parse_file($filename); } @@ -99,12 +149,26 @@ 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]; + my ($type, $source, $descr, $data, $status); + $source = $o[0]; + $descr = $o[1]; + $type = $o[-2] if @o >= 4; + $data = $o[-1]; + + $status = $o[2] if ($type && @o == 5) || @o == 4; + + unless ($type) { + foreach my $t (@Type_Map) { + if ($data =~ /$t->{'regex'}/) { + $type = $t->{'type'}; + last; + } + } + } + + $type ||= 'unknown'; + $SENSORS{$id} = { id => $id, output => $output, @@ -126,44 +190,71 @@ $_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'}; +foreach my $s (sort as_if_numeric keys %SENSORS) { + my ($r, $data); + if (exists $CHECKS{$s}) { + $r = check_sensor($SENSORS{$s}, $CHECKS{$s}); + $data = $s . '=' . $SENSORS{$s}{'output'}; + } elsif (not $ignore_status) { + $r = check_sensor($SENSORS{$s}); + $data = $s . '=' . $SENSORS{$s}{'output'}; } else { - # XXX Error missing sensor - push @{ $states{'UNKNOWN'} }, $check . '=No sensor with this id'; + # ignore this sensor } + next unless defined $r; + push @{ $states{ $r } }, $data; } -#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; - print "$error (" . scalar(@{ $states{ $error } }) . "):\n"; - foreach (@{ $states{ $error } }) { - print " $_\n"; + } +} +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; + + die "file '$filename' does not exist." unless -e $filename; open my $fh, '-|', $GETCAP, '-a', '-f', $filename or die "Couldn't open FILE '$GETCAP -a -f $filename': $!"; - while (<$fh>) { + LINE: while (<$fh>) { chomp; my ($key, @c) = split /\:/; foreach (@c) { my ($k, $v) = split /\=/; - $contents{$key}{$k} = $v; + if (lc($k) eq 'ignore') { + $contents{$key}{'IGNORE'} = 1; + } elsif (lc($k) eq 'status') { + $contents{$key}{'STATUS'} = 1; + } else { + $contents{$key}{$k} = $v; + } } } close $fh; @@ -175,47 +266,35 @@ my $type = shift; my $check = shift; - $check->{'warn'} = $check->{'warning'} if (!defined $check->{'warn'}); - $check->{'crit'} = $check->{'critical'} if (!defined $check->{'crit'}); + return undef unless $check; + return undef if $check->{'STATUS'}; + return 'IGNORE' if $check->{'IGNORE'}; - if (defined $check->{'warn'} && $check->{'warn'} =~ /:/) { - if (my ($low, $high) = split /:/, $check->{'warn'}) { - $check->{'warn.low'} = $low; - $check->{'warn.high'} = $high; + 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}; } - delete $check->{'warn'}; - } - if (defined $check->{'crit'} && $check->{'crit'} =~ /:/) { - if (my ($low, $high) = split /:/, $check->{'crit'}) { - $check->{'crit.low'} = $low; - $check->{'crit.high'} = $high; + + foreach my $severity ('low', 'high') { + if (defined $check->{$severity}) { + $check->{ $code . '.' . $severity } = $check->{$severity} + unless defined $check->{ $code . '.' . $severity }; + } } - delete $check->{'crit'}; + no warnings 'uninitialized'; + $check->{$code} = [ split /,\s*/, $check->{$code} ]; } - if (defined $check->{'low'}) { - $check->{'warn.low'} = $check->{'low'} - unless defined $check->{'warn.low'}; - $check->{'crit.low'} = $check->{'low'} - unless defined $check->{'crit.low'}; - } - if (defined $check->{'high'}) { - $check->{'warn.high'} = $check->{'high'} - unless defined $check->{'warn.high'}; - $check->{'crit.high'} = $check->{'high'} - unless defined $check->{'crit.high'}; - } - - no warnings 'uninitialized'; - $check->{'warn'} = [ split /,\s*/, $check->{'warn'} ]; - $check->{'crit'} = [ split /,\s*/, $check->{'crit'} ]; - return $check; } sub check_sensor { - my $check = shift; my $sensor = shift; + my $check = shift; my $result = 'UNKNOWN'; my %errors = ( 'warn' => 'WARNING', @@ -223,12 +302,22 @@ ); return $result unless ref $sensor eq 'HASH'; + $check = parse_check($sensor->{'type'}, $check) if $check; - $check = parse_check($sensor->{'type'}, $check); - #print Dumper $check, $sensor; + unless ($check) { + if ($sensor->{'status'}) { + # It looks like doing this should be safe, from + # src/sbin/sysctl/sysctl.c + $result = $sensor->{'status'} + } else { + return undef; + } + return $result; + } - $result = 'OK'; + return undef if $check eq 'IGNORE'; + $result = 'OK'; foreach my $code ('warn', 'crit') { if ( $sensor->{'type'} eq 'volts_dc' || @@ -271,17 +360,17 @@ $result = $errors{$code} if ($c <= $data); } - } elsif (defined $check->{$code}) { + } elsif (@{ $check->{$code} }) { my $matched = 0; foreach my $c (@{ $check->{$code} }) { $c =~ s/[^\d\.]//g; unless (length $c) { - warn "INVALID CHECK (" . $check->{$code} . + warn "INVALID CHECK (" . $c . ") for '$sensor->{'id'}:$code'"; next; } - if ($_ eq $data) { + if ($c eq $data) { $matched = 1; last; } @@ -292,6 +381,7 @@ } elsif ($sensor->{'type'} eq 'temp') { my ($degC, $degF) = split /\//, $sensor->{'data'}; $degC =~ s/[^\d\.]//g; + $degF ||= $degC * 9 / 5 + 32; $degF =~ s/[^\d\.]//g; if ( defined $check->{$code . ".low"} || @@ -339,7 +429,7 @@ $result = $errors{$code} if ($c <= $data); } - } elsif (defined $check->{$code}) { + } elsif (@{ $check->{$code} }) { my $matched = 0; foreach my $c (@{ $check->{$code} }) { @@ -354,12 +444,12 @@ $c =~ s/[^\d\.]//g; unless (length $c) { - warn "INVALID CHECK (" . $check->{$code} . + warn "INVALID CHECK (" . $c . ") for '$sensor->{'id'}':$code"; next; } - if ($_ eq $data) { + if ($c eq $data) { $matched = 1; last; } @@ -371,7 +461,8 @@ $sensor->{'type'} eq 'drive' || $sensor->{'type'} eq 'indicator' ) { - if (defined $check->{$code}) { + $sensor->{'type'} =~ s/^drive\s+//; + if (@{ $check->{$code} }) { my $matched = 0; foreach (@{ $check->{$code} }) { if ($_ eq $sensor->{'data'}) { @@ -394,34 +485,63 @@ sub print_help { print <]|(-s -w limit -c limit)) + $PROGNAME [-i] (-f []|(-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=INTEGER - Exit with CRITICAL status if outside of RANGE or if != ENTRY + -i, --ignore-status + Don't check the status of sensors that report it. + -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). -FILE is in the same format as sensorsd.conf(5). 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 +$PROGNAME understands the following entries: -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. Anything that isn't a digit or period is stripped from the entry and the sensor output and they 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. + low, high, crit, warn, crit.low, crit.high, warn.low, warn.high, + ignore, 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. +An ENTRY depends on the type. The descriptions in sensorsd.conf(5) +can be used when appropriate, or you can use the following: -A RANGE is a low ENTRY and a high ENTRY separated by a colon (:). + 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.. + +An entry marked "ignore" will cause that sensor to be skipped. +Generally used with state checking of all sensors to ignore sensors you +don't care about or that report incorrectly. + +If you are using --ignore-status, you can still check the status of +individual sensors with a status entry. + EOL - - print_revision($PROGNAME, '$Revision: 1.3 $'); + + print_revision($PROGNAME, '$Revision: 1.18 $'); }