=================================================================== RCS file: /cvs/nagios/check_hw_sensors/check_hw_sensors,v retrieving revision 1.32 retrieving revision 1.33 diff -u -r1.32 -r1.33 --- nagios/check_hw_sensors/check_hw_sensors 2009/11/10 19:46:06 1.32 +++ nagios/check_hw_sensors/check_hw_sensors 2009/11/10 23:11:18 1.33 @@ -1,5 +1,5 @@ #!/usr/bin/perl -T -# $RedRiver: check_hw_sensors,v 1.31 2009/11/10 18:24:56 andrew Exp $ +# $RedRiver: check_hw_sensors,v 1.32 2009/11/10 19:46:06 andrew Exp $ ######################################################################## # check_hw_sensors *** A nagios check for OpenBSD sysctl hw.sensors # @@ -8,11 +8,11 @@ use strict; use warnings; -%ENV = (); +local %ENV = (); -use constant NAGIOS_OUTPUT => 1; +my $NAGIOS_OUTPUT => 1; -my $License = <<'EOL'; +my $LICENSE = <<'EOL'; Copyright (c) 2009 Andrew Fresh Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -29,13 +29,13 @@ use POSIX; use Config; -use lib "/usr/local/libexec/nagios"; +use lib '/usr/local/libexec/nagios'; use utils qw($TIMEOUT %ERRORS &support); use Getopt::Long; Getopt::Long::Configure('bundling'); -my $PROGNAME = "check_hw_sensors"; +my $PROGNAME = 'check_hw_sensors'; my $SYSCTL = '/sbin/sysctl'; my $GETCAP = '/usr/bin/getcap'; @@ -44,37 +44,32 @@ my $OSVer = $Config{'osvers'} || 0; my $state = 'UNKNOWN'; # tells whether the it is warning, critical, or OK -my %states; # This stores the count of states; my $opt_V; my $opt_h; -my $ignore_status; -my $filename; -my $sensor; -my $warning; -my $critical; +my $IGNORE_STATUS; +my $FILENAME; +my $SENSOR; +my $WARNING; +my $CRITICAL; -my $CHECK_SENSOR = $BASE; -my %CHECKS; -my @SENSORS; - #Option checking -my $status = GetOptions( - "version|V" => \$opt_V, - "help|h" => \$opt_h, - "ignore-status|i" => \$ignore_status, - "filename|f:s" => \$filename, - "sensor|s=s" => \$sensor, - "warning|w=s" => \$warning, - "critical|c=s" => \$critical, +my $getopt_status = GetOptions( + 'version|V' => \$opt_V, + 'help|h' => \$opt_h, + 'ignore-status|i' => \$IGNORE_STATUS, + 'filename|f:s' => \$FILENAME, + 'sensor|s=s' => \$SENSOR, + 'warning|w=s' => \$WARNING, + 'critical|c=s' => \$CRITICAL, ); -if ( $status == 0 ) { +if ( $getopt_status == 0 ) { print_help(); exit $ERRORS{'OK'}; } if ($opt_V) { - print_revision( $PROGNAME, '$Revision: 1.32 $ ' ); + print_revision( $PROGNAME, '$Revision: 1.33 $ ' ); exit $ERRORS{'OK'}; } @@ -84,68 +79,119 @@ } # set the default this way so it only happens if someone typed -f or --filename -$filename = $DEFAULT_CONFIG if ( defined $filename && $filename eq '' ); +$FILENAME = $DEFAULT_CONFIG if ( defined $FILENAME && $FILENAME eq q{} ); # Stuff is output in this file by print_sensor() # http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/sysctl/sysctl.c -my @Type_Map = ( +my @TYPE_MAP = ( { type => 'temp', - regex => qr/\sdegC$/, + regex => qr/\sdegC$/xms, }, { type => 'fanrpm', - regex => qr/\sRPM$/, + regex => qr/\sRPM$/xms, }, { type => 'volts_dc', - regex => qr/\sV\sDC$/, + regex => qr/\sV\sDC$/xms, }, { type => 'amps', - regex => qr/\sA$/, + regex => qr/\sA$/xms, }, { type => 'watthour', - regex => qr/\sWh$/, + regex => qr/\sWh$/xms, }, { type => 'amphour', - regex => qr/\sAh$/, + regex => qr/\sAh$/xms, }, { type => 'indicator', - regex => qr/^(On|Off)$/, + regex => qr/^(On|Off)$/xms, }, { type => 'integer', - regex => qr/\sraw$/, + regex => qr/\sraw$/xms, }, { type => 'percent', - regex => qr/\s\%$/, + regex => qr/\s\%$/xms, }, { type => 'lux', - regex => qr/\slx$/, + regex => qr/\slx$/xms, }, { type => 'drive', - regex => qr/^drive\s/, + regex => qr/^drive\s/xms, }, { type => 'timedelta', - regex => qr/\ssecs$/, + regex => qr/\ssecs$/xms, }, ); -if ( defined $sensor ) { - if ( $sensor !~ /^$BASE/ ) { - $sensor = $BASE . '.' . $sensor; +my $CHECK_SENSOR = $BASE; +my %CHECKS; +if ( defined $SENSOR ) { + if ( $SENSOR !~ /^$BASE/xms ) { + $SENSOR = join q{.}, $BASE, $SENSOR; } - $CHECK_SENSOR = $sensor; + $CHECK_SENSOR = $SENSOR; - $CHECKS{$sensor}{'warn'} = $warning; - $CHECKS{$sensor}{'crit'} = $critical; + $CHECKS{$SENSOR}{'warn'} = $WARNING; + $CHECKS{$SENSOR}{'crit'} = $CRITICAL; } -elsif ( defined $filename ) { - %CHECKS = parse_file($filename); +elsif ( defined $FILENAME ) { + %CHECKS = read_file($FILENAME); } -open my $sysctl, "-|", $SYSCTL, $CHECK_SENSOR - or die "Couldn't open sysctl: $!"; -while (<$sysctl>) { - chomp; - my ( $id, $output ) = split /=/xms; +my @SENSORS = read_sensors($CHECK_SENSOR); +my %STATES = check_sensors( \@SENSORS, \%CHECKS, + { IGNORE_STATUS => $IGNORE_STATUS } ); + +my $have_results = 0; +$state = 'OK'; +foreach my $error ( sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS ) { + if ( exists $STATES{$error} ) { + $have_results++; + $state = $error if $ERRORS{$state} < $ERRORS{$error}; + + if ($NAGIOS_OUTPUT) { + print "$error (" . scalar( @{ $STATES{$error} } ) . ")"; + if ( $error ne 'OK' ) { + s/\s+/ /gxms; + 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 read_sensors { + my ($sensor) = @_; + my @S; + open my $sysctl, '-|', $SYSCTL, $sensor + or die "Couldn't open sysctl: $!\n"; + while (<$sysctl>) { + chomp; + push @S, parse_sensor($_); + } + ## no critic 'die' + close $sysctl + or die $! + ? "Error closing sysctl pipe: $!\n" + : "Exit status $? from sysctl\n"; + + return @S; +} + +sub parse_sensor { + my ($sensor) = @_; + + my ( $id, $output ) = split /=/xms, $sensor; my @s = split /\./xms, $id; my @o = split /,\s*/xms, $output; @@ -165,7 +211,7 @@ elsif ( $OSVer >= 4.0 ) { $data = $o[2]; $status = $o[3]; - foreach my $t (@Type_Map) { + foreach my $t (@TYPE_MAP) { if ( $data =~ /$t->{'regex'}/xms ) { $type = $t->{'type'}; last; @@ -180,8 +226,7 @@ $type ||= 'unknown'; - push @SENSORS, - { + return { id => $id, output => $output, source => $source, @@ -189,86 +234,43 @@ status => $status, type => $type, data => $data, - }; + }; } -close $sysctl; -foreach my $sensor (@SENSORS) { - my ( $r, $data ); - if ( exists $CHECKS{ $sensor->{id} } ) { - $r = check_sensor( $sensor, $CHECKS{ $sensor->{id} } ); - $data = $sensor->{id} . '=' . $sensor->{output}; - } - elsif ( !$ignore_status && $sensor->{status} ) { - $r = check_sensor( $sensor, { STATUS => 1 } ); - $data = $sensor->{id} . '=' . $sensor->{output}; - } - else { - - # ignore this sensor, theoretically you could do the check and - # that would show unknown sensors. - } - if ( defined $r ) { - push @{ $states{$r} }, $data; - } -} - -my $have_results = 0; -$state = 'OK'; -foreach my $error ( sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS ) { - if ( exists $states{$error} ) { - $have_results++; - $state = $error if $ERRORS{$state} < $ERRORS{$error}; - - if (NAGIOS_OUTPUT) { - print "$error (" . scalar( @{ $states{$error} } ) . ")"; - if ( $error ne '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 { +sub read_file { my $filename = shift; my %contents; - die "file '$filename' does not exist." unless -e $filename; + die "file '$filename' does not exist.\n" unless -e $filename; open my $fh, '-|', $GETCAP, '-a', '-f', $filename - or die "Couldn't open FILE '$GETCAP -a -f $filename': $!"; + or die "Couldn't open '$GETCAP -a -f $filename': $!\n"; while (<$fh>) { chomp; - my ( $key, @c ) = split /\:/; - foreach (@c) { - my ( $k, $v ) = split /\=/; - if ( lc($k) eq 'ignore' ) { - $contents{$key}{'IGNORE'} = 1; - } - elsif ( lc($k) eq 'status' ) { - $contents{$key}{'STATUS'} = 1; - } - else { - $contents{$key}{$k} = $v; - } - } + my ( $s, @c ) = split /\:/xms; + $contents{$s} = parse_line(@c); } - close $fh; + ## no critic 'die' + close $fh + or die $! + ? "Error closing getcap pipe: $!\n" + : "Exit status $? from getcap\n"; return %contents; } +sub parse_line { + my (@c) = @_; + my %c; + foreach (@c) { + my ( $k, $v ) = split /\=/xms; + if ( lc($k) eq 'ignore' ) { $c{'IGNORE'} = 1; } + elsif ( lc($k) eq 'status' ) { $c{'STATUS'} = 1; } + else { $c{$k} = $v; } + } + return \%c; +} + sub parse_check { my $check = shift; @@ -277,249 +279,191 @@ return 'IGNORE' if $check->{'IGNORE'}; foreach my $code ( 'crit', 'warn' ) { - if ( defined $check->{$code} && $check->{$code} =~ /:/ ) { - if ( my ( $low, $high ) = split /:/, $check->{$code} ) { + if ( defined $check->{$code} && $check->{$code} =~ /:/xms ) { + if ( my ( $low, $high ) = split /:/xms, $check->{$code} ) { $check->{ $code . '.low' } = $low if length $low; $check->{ $code . '.high' } = $high if length $high; } delete $check->{$code}; } - foreach my $severity ( 'low', 'high' ) { - if ( defined $check->{$severity} ) { - $check->{ $code . '.' . $severity } = $check->{$severity} - if !defined $check->{ $code . '.' . $severity }; + foreach my $direction ( 'low', 'high' ) { + my $c = $code . '.' . $direction; + if ( defined $check->{$direction} ) { + $check->{$c} ||= $check->{$direction}; } + + if ( defined $check->{$c} ) { + my $old = $check->{$c}; + $check->{$c} =~ s/[^\d\.]//gxms; + if ( !length $check->{$c} ) { + warn "INVALID CHECK ($old)\n"; + delete $check->{$c}; + } + } } - no warnings 'uninitialized'; - $check->{$code} = [ split /,\s*/, $check->{$code} ]; + + if ( defined $check->{$code} ) { + $check->{$code} = [ split /,\s*/xms, $check->{$code} ]; + } + else { + $check->{$code} = []; + } } return $check; } +sub check_sensors { + my ( $S, $C, $O ) = @_; + + my %states; + foreach my $sensor ( @{$S} ) { + my ( $r, $data ); + if ( exists $C->{ $sensor->{id} } ) { + $r = check_sensor( $sensor, $C->{ $sensor->{id} } ); + $data = $sensor->{id} . '=' . $sensor->{output}; + } + elsif ( !$O->{ignore_status} && $sensor->{status} ) { + $r = check_sensor( $sensor, { STATUS => 1 } ); + $data = $sensor->{id} . '=' . $sensor->{output}; + } + else { + + # ignore this sensor, theoretically you could do the check and + # that would show unknown sensors. + } + if ( defined $r ) { + push @{ $states{$r} }, $data; + } + } + + return %states; +} + sub check_sensor { - my $sensor = shift; - my $check = shift; + my ( $sensor, $check ) = @_; my $result = 'UNKNOWN'; - my %errors = ( - 'warn' => 'WARNING', - 'crit' => 'CRITICAL', - ); return $result unless ref $sensor eq 'HASH'; $check = parse_check($check) if $check; - if ( !$check ) { - return $result; - } + if ( !$check ) { return $result; } elsif ( $check eq 'STATUS' ) { - # It looks like doing this should be safe, from + # It looks like returning $sensor->{status} should be safe, from # src/sbin/sysctl/sysctl.c return ( $sensor->{'status'} || $result ); } - elsif ( $check eq 'IGNORE' ) { - return; + elsif ( $check eq 'IGNORE' ) { return; } + + my $type = $sensor->{'type'}; + if (grep { $type eq $_ } + qw( + fan fanrpm + volt volts_dc + amps watthour amphour + integer raw percent + lux temp timedelta + ) + ) + { + $result = check_sensor_numeric( $sensor->{'data'}, $check ); } + elsif ( grep { $type eq $_ } qw( drive indicator ) ) { + my $data = $sensor->{'data'}; + $data =~ s/^drive\s+//xms; + $result = check_sensor_list( $data, $check ); + } + else { + warn 'Unknown Sensor Type: ', $sensor->{'id'}, '=', $type, "\n"; + } - foreach my $code ( 'warn', 'crit' ) { - if ( $sensor->{'type'} eq 'fan' - || $sensor->{'type'} eq 'fanrpm' - || $sensor->{'type'} eq 'volt' - || $sensor->{'type'} eq 'volts_dc' - || $sensor->{'type'} eq 'amps' - || $sensor->{'type'} eq 'watthour' - || $sensor->{'type'} eq 'amphour' - || $sensor->{'type'} eq 'integer' - || $sensor->{'type'} eq 'raw' - || $sensor->{'type'} eq 'percent' - || $sensor->{'type'} eq 'lux' - || $sensor->{'type'} eq 'timedelta' ) - { - my $data = $sensor->{'data'}; - $data =~ s/[^\d\.]//gxms; - if ( !length $data ) { - warn - "INVALID DATA ($sensor->{'data'}) for '$sensor->{'id'}'\n"; - next; - } + return $result; +} - if ( defined $check->{ $code . ".low" } - || defined $check->{ $code . ".high" } ) - { - if ( defined $check->{ $code . ".low" } ) { - my $c = $check->{ $code . ".low" }; - $c =~ s/[^\d\.]//gxms; +sub check_sensor_numeric { + my ( $data, $check ) = @_; - if ( !length $c ) { - warn "INVALID CHECK (" - . $check->{ $code . ".low" } - . ") for '$sensor->{'id'}:$code.low'\n"; - next; - } + my $result = 'UNKNOWN'; + my %errors = ( + 'warn' => 'WARNING', + 'crit' => 'CRITICAL', + ); - $result = $errors{$code} - if ( $c >= $data ); + $data =~ s/[^\d\.]//gxms; + if ( !length $data ) { + warn "INVALID DATA ($data)\n"; + return $result; + } + + foreach my $code ( 'warn', 'crit' ) { + if ( defined $check->{ $code . ".low" } + || defined $check->{ $code . ".high" } ) + { + if (( defined $check->{ $code . ".low" } + && $check->{ $code . ".low" } >= $data + ) + || ( defined $check->{ $code . ".high" } + && $check->{ $code . ".high" } <= $data ) + ) + { + $result = $errors{$code}; + } + $result = 'OK' if $result eq 'UNKNOWN'; + } + elsif ( @{ $check->{$code} } ) { + my $matched = 0; + NUMERIC_CHECK: foreach ( @{ $check->{$code} } ) { + my $c = $_; + $c =~ s/[^\d\.]//gxms; + if ( !length $c ) { + warn "INVALID CHECK (" . $_ . ") for '$code'\n"; + next; } - if ( defined $check->{ $code . ".high" } ) { - my $c = $check->{ $code . ".high" }; - $c =~ s/[^\d\.]//gxms; - if ( !length $c ) { - warn "INVALID CHECK (" - . $check->{ $code . ".high" } - . ") for '$sensor->{'id'}:$code.high'\n"; - next; - } - $result = $errors{$code} - if ( $c <= $data ); + if ( $c eq $data ) { + $matched = 1; + last NUMERIC_CHECK; } + } + if ($matched) { $result = 'OK' if $result eq 'UNKNOWN'; } - elsif ( @{ $check->{$code} } ) { - my $matched = 0; - foreach ( @{ $check->{$code} } ) { - my $c = $_; - $c =~ s/[^\d\.]//gxms; - if ( !length $c ) { - warn "INVALID CHECK (" . $_ - . ") for '$sensor->{'id'}:$code'\n"; - next; - } - - if ( $c eq $data ) { - $matched = 1; - last; - } - } - if ($matched) { - $result = 'OK' if $result eq 'UNKNOWN'; - } - else { - $result = $errors{$code}; - } + else { + $result = $errors{$code}; } } - elsif ( $sensor->{'type'} eq 'temp' ) { - my ( $degC, $degF ) = split /\//, $sensor->{'data'}; - $degC =~ s/[^\d\.]//gxms; - $degF ||= $degC * 9 / 5 + 32; - $degF =~ s/[^\d\.]//gxms; - 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/ixms ); - if ( !length $data ) { - warn "INVALID DATA (" - . $sensor->{'data'} - . ") for '$sensor->{'id'}'\n"; - next; - } + return $result; +} - $c =~ s/[^\d\.]//gxms; - if ( !length $c ) { - warn "INVALID CHECK (" - . $check->{ $code . ".low" } - . ") for '$sensor->{'id'}':$code.low\n"; - next; - } +sub check_sensor_list { + my ( $data, $check ) = @_; - $result = $errors{$code} - if ( $c >= $data ); - } - if ( defined $check->{ $code . ".high" } ) { - my $c = $check->{ $code . ".high" }; + my $result = 'UNKNOWN'; + my %errors = ( + 'warn' => 'WARNING', + 'crit' => 'CRITICAL', + ); - my $data = $degC; - $data = $degF if ( $c =~ /F/ixms ); - if ( !length $data ) { - warn "INVALID DATA (" - . $sensor->{'data'} - . ") for '$sensor->{'id'}'\n"; - next; - } - - $c =~ s/[^\d\.]//gxms; - if ( !length $c ) { - warn "INVALID CHECK (" - . $check->{ $code . ".high" } - . ") for '$sensor->{'id'}:$code.high'\n"; - next; - } - - $result = $errors{$code} - if ( $c <= $data ); + foreach my $code ( 'warn', 'crit' ) { + if ( @{ $check->{$code} } ) { + my $matched = 0; + LIST_CHECK: foreach ( @{ $check->{$code} } ) { + if ( $_ eq $data ) { + $matched = 1; + last LIST_CHECK; } + } + if ($matched) { $result = 'OK' if $result eq 'UNKNOWN'; } - elsif ( @{ $check->{$code} } ) { - my $matched = 0; - foreach ( @{ $check->{$code} } ) { - my $c = $_; - my $data = $degC; - - $data = $degF if ( $c =~ /F/ixms ); - if ( !length $data ) { - warn "INVALID DATA (" - . $sensor->{'data'} - . ") for '$sensor->{'id'}'\n"; - next; - } - - $c =~ s/[^\d\.]//gxms; - if ( !length $c ) { - warn "INVALID CHECK (" . $_ - . ") for '$sensor->{'id'}':$code\n"; - next; - } - - if ( $c eq $data ) { - $matched = 1; - last; - } - } - if ($matched) { - $result = 'OK' if $result eq 'UNKNOWN'; - } - else { - $result = $errors{$code}; - } + else { + $result = $errors{$code}; } } - elsif ($sensor->{'type'} eq 'drive' - || $sensor->{'type'} eq 'indicator' ) - { - $sensor->{'data'} =~ s/^drive\s+//; - if ( @{ $check->{$code} } ) { - my $matched = 0; - foreach ( @{ $check->{$code} } ) { - if ( $_ eq $sensor->{'data'} ) { - $matched = 1; - last; - } - } - if ($matched) { - $result = 'OK' if $result eq 'UNKNOWN'; - } - else { - $result = $errors{$code}; - } - } - - } - else { - warn 'Unknown Sensor Type: ', - $sensor->{'id'}, - '=', - $sensor->{'type'}, - "\n"; - } - } return $result; @@ -557,14 +501,11 @@ can be used when appropriate, or you can use the following: fanrpm, volts_dc, amps, watthour, amphour, integer (raw), percent, - lux or timedelta - 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. + lux, temp or timedelta - 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 sets the status. @@ -588,9 +529,11 @@ EOL - print_revision( $PROGNAME, '$Revision: 1.32 $' ); + print_revision( $PROGNAME, '$Revision: 1.33 $' ); - print $License; + print $LICENSE; + + return; } sub print_revision { @@ -598,5 +541,7 @@ $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms; print "$prog $rev\n"; + + return; }