[BACK]Return to check_hw_sensors CVS log [TXT][DIR] Up to [local] / nagios / check_hw_sensors

File: [local] / nagios / check_hw_sensors / check_hw_sensors (download)

Revision 1.25, Mon Mar 10 16:21:53 2008 UTC (16 years, 2 months ago) by andrew
Branch: MAIN
Changes since 1.24: +5 -5 lines

Fix it for sensors with more than 1 digit at the end.

#!/usr/bin/perl -T
# $RedRiver: check_hw_sensors,v 1.24 2007/02/14 21:59:10 andrew Exp $
########################################################################
# check_hw_sensors *** A nagios check for OpenBSD hw.sensors
# 
# 2006.05.01 #*#*# andrew fresh <andrew@mad-techies.org>
########################################################################
# TODO: 
#   Really need real documentation.
#   allow checking of hw.sensors on a remote host with ssh somehow
########################################################################
use strict;
use warnings;

%ENV = ();

use constant NAGIOS_OUTPUT => 1;

use POSIX;
use Config;
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 $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 $filename;
my $ignore_status;
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,
	"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 = $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  => 'percent',
		regex => qr/\s\%$/,
	},
	{
		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.25 $ ');
	exit $ERRORS{'OK'};
}

unless ( 
	( 
         	defined $filename ||
         	(not defined $ignore_status) ||
         	(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  if defined $warning;
	$CHECKS{$sensor}{'crit'} = $critical if defined $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 @s = split /\./, $id;
	my @o = split /,\s*/, $output;

	my ($type, $source, $descr, $data, $status);

	$source = $o[0];
	$descr  = $o[1];

	if ($OSVer >= 4.1) {
		$data   = $o[0];
		if ($data =~ s/\s+\((.*)\).*$//) {
			$descr = $1;
		}
		$status = $o[1];
		($source, $type) = $id =~ /([^\.]+)\.([^\.]+?)\d+$/;
	} elsif ($OSVer >= 4.0) {
		$data   = $o[2];
		$status = $o[3];
		foreach my $t (@Type_Map) {
			if ($data =~ /$t->{'regex'}/) {
				$type = $t->{'type'};
				last;
			}
		}
	}  else {
		$data   = $o[-1];
		$status = $o[2] if @o == 5;
		$type   = $o[-2];
	}

	$type ||= 'unknown';

	$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 $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 {
		# ignore this sensor
	}
	next unless defined $r;
	push @{ $states{ $r } }, $data;
}

$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 '<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 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': $!";
	LINE: 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;
			}
		}
	}
	close $fh;

	return %contents;
}

sub parse_check {
	my $type  = shift;
	my $check = shift;

	return undef unless $check;
	return undef    if $check->{'STATUS'};
	return 'IGNORE' if $check->{'IGNORE'};

	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 $sensor = shift;
	my $check  = shift;
	my $result = 'UNKNOWN';
	my %errors = (
		'warn' => 'WARNING',
		'crit' => 'CRITICAL',
	);

	return $result unless ref $sensor eq 'HASH';
	$check = parse_check($sensor->{'type'}, $check) if $check;

	# It looks like doing this should be safe, from 
	# src/sbin/sysctl/sysctl.c
	return $sensor->{'status'} unless $check;

	return undef if $check eq 'IGNORE';

	$result = 'OK';
	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\.]//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 ($c 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 ||= $degC * 9 / 5 + 32;
			$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 ($c eq $data) {
						$matched = 1;
						last;
					}
				}
				$result = $errors{$code} unless $matched;
			}

		} 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;
					}
				}
				$result = $errors{$code} unless $matched;
			}

		} else {
			print STDERR 'Unknown Sensor Type: ',
			    $sensor->{'id'},
			    '=',
			    $sensor->{'type'},
			    "\n";
			$result = 'UNKNOWN';
		}

	}

	return $result;
}

sub print_help {
	print <<EOL;
$PROGNAME plugin for Nagios monitors sysctl hw.sensors on OpenBSD
    $PROGNAME [-i] (-f [<FILENAME>]|(-s <hw.sensors id> [-w limit] [-c limit]))

Usage:
    -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

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,
    ignore, status

An ENTRY depends on the type.  The descriptions in sensorsd.conf(5)
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.

    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.

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.25 $');
}