Annotation of nagios/check_hw_sensors/check_hw_sensors, Revision 1.26
1.14 andrew 1: #!/usr/bin/perl -T
1.26 ! andrew 2: # $RedRiver: check_hw_sensors,v 1.25 2008/03/10 16:21:53 andrew Exp $
1.2 andrew 3: ########################################################################
1.26 ! andrew 4: # check_hw_sensors *** A nagios check for OpenBSD sysctl hw.sensors
! 5: #
1.2 andrew 6: # 2006.05.01 #*#*# andrew fresh <andrew@mad-techies.org>
7: ########################################################################
8: use strict;
9: use warnings;
10:
1.14 andrew 11: %ENV = ();
12:
1.24 andrew 13: use constant NAGIOS_OUTPUT => 1;
1.4 andrew 14:
1.26 ! andrew 15: my $License = <<'EOL';
! 16: Copyright (c) 2009 Andrew Fresh <andrew@afresh1.com>
! 17: Permission to use, copy, modify, and distribute this software for any
! 18: purpose with or without fee is hereby granted, provided that the above
! 19: copyright notice and this permission notice appear in all copies.
! 20:
! 21: THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
! 22: WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
! 23: MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
! 24: ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
! 25: WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
! 26: ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
! 27: OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
! 28: EOL
! 29:
1.2 andrew 30: use POSIX;
1.20 andrew 31: use Config;
1.4 andrew 32: use lib "/usr/local/libexec/nagios";
1.26 ! andrew 33: use utils qw($TIMEOUT %ERRORS &support);
1.2 andrew 34:
35: use Getopt::Long;
36: Getopt::Long::Configure('bundling');
37:
38: my $PROGNAME = "check_hw_sensors";
39:
1.26 ! andrew 40: my $SYSCTL = '/sbin/sysctl';
! 41: my $GETCAP = '/usr/bin/getcap';
! 42: my $BASE = 'hw.sensors';
1.4 andrew 43: my $DEFAULT_CONFIG = '/etc/sensorsd.conf';
1.26 ! andrew 44: my $OSVer = $Config{'osvers'} || 0;
1.2 andrew 45:
1.26 ! andrew 46: my $state = 'UNKNOWN'; # tells whether the it is warning, critical, or OK
! 47: my %states; # This stores the count of states;
1.2 andrew 48: my $filename;
1.15 andrew 49: my $ignore_status;
1.2 andrew 50: my $sensor;
51: my $warning;
52: my $critical;
1.10 andrew 53: my $opt_h;
54: my $opt_V;
1.2 andrew 55:
1.4 andrew 56: my $CHECK_SENSOR = $BASE;
57: my %CHECKS;
58: my %SENSORS;
1.2 andrew 59:
60: #Option checking
61: my $status = GetOptions(
1.26 ! andrew 62: "version|V" => \$opt_V,
! 63: "filename|f:s" => \$filename,
! 64: "ignore-status|i" => \$ignore_status,
! 65: "sensor|s=s" => \$sensor,
! 66: "warning|w=s" => \$warning,
! 67: "critical|c=s" => \$critical,
1.2 andrew 68: );
1.18 andrew 69:
1.2 andrew 70: # set the default this way so it only happens if someone typed -f or --filename
1.26 ! andrew 71: $filename = $DEFAULT_CONFIG if ( defined $filename && $filename eq '' );
1.2 andrew 72:
1.18 andrew 73: # Stuff is output in this file by print_sensor()
74: # http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/sysctl/sysctl.c
75: my @Type_Map = (
1.26 ! andrew 76: { type => 'temp',
! 77: regex => qr/\sdegC$/,
! 78: },
! 79: { type => 'fanrpm',
! 80: regex => qr/\sRPM$/,
! 81: },
! 82: { type => 'volts_dc',
! 83: regex => qr/\sV\sDC$/,
! 84: },
! 85: { type => 'amps',
! 86: regex => qr/\sA$/,
! 87: },
! 88: { type => 'watthour',
! 89: regex => qr/\sWh$/,
! 90: },
! 91: { type => 'amphour',
! 92: regex => qr/\sAh$/,
! 93: },
! 94: { type => 'indicator',
! 95: regex => qr/^(On|Off)$/,
! 96: },
! 97: { type => 'integer',
! 98: regex => qr/\sraw$/,
! 99: },
! 100: { type => 'percent',
! 101: regex => qr/\s\%$/,
! 102: },
! 103: { type => 'lux',
! 104: regex => qr/\slx$/,
! 105: },
! 106: { type => 'drive',
! 107: regex => qr/^drive\s/,
! 108: },
! 109: { type => 'timedelta',
! 110: regex => qr/\ssecs$/,
! 111: },
! 112: );
! 113:
! 114: if ( $status == 0 ) {
! 115: print_help();
! 116: exit $ERRORS{'OK'};
1.2 andrew 117: }
118:
119: if ($opt_V) {
1.26 ! andrew 120: print_revision( $PROGNAME, '$Revision: 1.25 $ ' );
! 121: exit $ERRORS{'OK'};
1.2 andrew 122: }
123:
1.26 ! andrew 124: if (
! 125: $opt_h
! 126: || ( ( !defined $filename ) && ( !defined $sensor ) )
! 127: #|| ( defined $sensor && !( $warning || $critical ) )
! 128: )
! 129: {
! 130: print_help();
! 131: exit $ERRORS{'OK'};
1.2 andrew 132: }
133:
1.26 ! andrew 134: if ( defined $sensor ) {
! 135: if ( $sensor !~ /^$BASE/ ) {
! 136: $sensor = $BASE . '.' . $sensor;
! 137: }
! 138: $CHECK_SENSOR = $sensor;
1.2 andrew 139:
1.26 ! andrew 140: $CHECKS{$sensor}{'warn'} = $warning if defined $warning;
! 141: $CHECKS{$sensor}{'crit'} = $critical if defined $critical;
1.2 andrew 142:
1.26 ! andrew 143: }
! 144: elsif ( defined $filename ) {
! 145: %CHECKS = parse_file($filename);
1.2 andrew 146: }
147:
1.26 ! andrew 148: open my $sysctl, "-|", $SYSCTL, $CHECK_SENSOR
1.2 andrew 149: or die "Couldn't open sysctl: $!";
150: while (<$sysctl>) {
1.26 ! andrew 151:
! 152: #while (<>) {
! 153: chomp;
! 154: my ( $id, $output ) = split /=/;
! 155: my @s = split /\./, $id;
! 156: my @o = split /,\s*/, $output;
! 157:
! 158: my ( $type, $source, $descr, $data, $status );
! 159:
! 160: $source = $o[0];
! 161: $descr = $o[1];
! 162:
! 163: if ( $OSVer >= 4.1 ) {
! 164: $data = $o[0];
! 165: if ( $data =~ s/\s+\((.*)\).*$// ) {
! 166: $descr = $1;
! 167: }
! 168: $status = $o[1];
! 169: ( $source, $type ) = $id =~ /([^\.]+)\.([^\.]+?)\d+$/;
! 170: }
! 171: elsif ( $OSVer >= 4.0 ) {
! 172: $data = $o[2];
! 173: $status = $o[3];
! 174: foreach my $t (@Type_Map) {
! 175: if ( $data =~ /$t->{'regex'}/ ) {
! 176: $type = $t->{'type'};
! 177: last;
! 178: }
! 179: }
! 180: }
! 181: else {
! 182: $data = $o[-1];
! 183: $status = $o[2] if @o == 5;
! 184: $type = $o[-2];
! 185: }
! 186:
! 187: $type ||= 'unknown';
! 188:
! 189: $SENSORS{$id} = {
! 190: id => $id,
! 191: output => $output,
! 192: source => $source,
! 193: description => $descr,
! 194: status => $status,
! 195: type => $type,
! 196: data => $data,
! 197: };
! 198:
1.2 andrew 199: }
200: close $sysctl;
201:
202: sub as_if_numeric {
1.26 ! andrew 203: my $_a = $a;
! 204: my $_b = $b;
! 205: $_a =~ s/\D//g;
! 206: $_b =~ s/\D//g;
! 207: $_a <=> $_b;
! 208: }
! 209:
! 210: foreach my $s ( sort as_if_numeric keys %SENSORS ) {
! 211: my ( $r, $data );
! 212: if ( exists $CHECKS{$s} ) {
! 213: $r = check_sensor( $SENSORS{$s}, $CHECKS{$s} );
! 214: $data = $s . '=' . $SENSORS{$s}{'output'};
! 215: }
! 216: elsif ( not $ignore_status ) {
! 217: $r = check_sensor( $SENSORS{$s} );
! 218: $data = $s . '=' . $SENSORS{$s}{'output'};
! 219: }
! 220: else {
! 221:
! 222: # ignore this sensor
! 223: }
! 224: next unless defined $r;
! 225: push @{ $states{$r} }, $data;
1.2 andrew 226: }
227:
228: $state = 'OK';
1.5 andrew 229: my $have_results = 0;
1.26 ! andrew 230: foreach my $error ( sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS ) {
! 231: if ( exists $states{$error} ) {
! 232: $have_results++;
! 233: $state = $error;
! 234: }
! 235: }
! 236: foreach my $error ( sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS ) {
! 237: if ( exists $states{$error} ) {
! 238: if (NAGIOS_OUTPUT) {
! 239: print "$error (" . scalar( @{ $states{$error} } ) . ")";
! 240: if ( $error ne 'OK' ) {
! 241: print '<br>';
! 242: print map {" - $_<br>"} @{ $states{$error} };
! 243: }
! 244: }
! 245: else {
! 246: print "$error (" . scalar( @{ $states{$error} } ) . "):\n";
! 247: foreach ( @{ $states{$error} } ) {
! 248: print " $_\n";
! 249: }
! 250: }
! 251: }
1.4 andrew 252: }
1.26 ! andrew 253: if ( $have_results == 0 ) {
! 254: print "No results found\n";
1.5 andrew 255: }
1.2 andrew 256: exit $ERRORS{$state};
257:
1.26 ! andrew 258: sub parse_file {
! 259: my $filename = shift;
! 260: my %contents;
1.2 andrew 261:
1.26 ! andrew 262: die "file '$filename' does not exist." unless -e $filename;
! 263:
! 264: open my $fh, '-|', $GETCAP, '-a', '-f', $filename
! 265: or die "Couldn't open FILE '$GETCAP -a -f $filename': $!";
! 266: while (<$fh>) {
! 267: chomp;
! 268: my ( $key, @c ) = split /\:/;
! 269: foreach (@c) {
! 270: my ( $k, $v ) = split /\=/;
! 271: if ( lc($k) eq 'ignore' ) {
! 272: $contents{$key}{'IGNORE'} = 1;
! 273: }
! 274: elsif ( lc($k) eq 'status' ) {
! 275: $contents{$key}{'STATUS'} = 1;
! 276: }
! 277: else {
! 278: $contents{$key}{$k} = $v;
! 279: }
! 280: }
! 281: }
! 282: close $fh;
1.2 andrew 283:
1.26 ! andrew 284: return %contents;
1.2 andrew 285: }
286:
287: sub parse_check {
1.26 ! andrew 288: my $check = shift;
1.2 andrew 289:
1.26 ! andrew 290: return unless $check;
! 291: return if $check->{'STATUS'};
! 292: return 'IGNORE' if $check->{'IGNORE'};
! 293:
! 294: foreach my $code ( 'crit', 'warn' ) {
! 295: if ( defined $check->{$code} && $check->{$code} =~ /:/ ) {
! 296: if ( my ( $low, $high ) = split /:/, $check->{$code} ) {
! 297: $check->{ $code . '.low' } = $low if length $low;
! 298: $check->{ $code . '.high' } = $high if length $high;
! 299: }
! 300: delete $check->{$code};
! 301: }
! 302:
! 303: foreach my $severity ( 'low', 'high' ) {
! 304: if ( defined $check->{$severity} ) {
! 305: $check->{ $code . '.' . $severity } = $check->{$severity}
! 306: if !defined $check->{ $code . '.' . $severity };
! 307: }
! 308: }
! 309: no warnings 'uninitialized';
! 310: $check->{$code} = [ split /,\s*/, $check->{$code} ];
! 311: }
1.2 andrew 312:
1.26 ! andrew 313: return $check;
1.2 andrew 314: }
315:
316: sub check_sensor {
1.26 ! andrew 317: my $sensor = shift;
! 318: my $check = shift;
! 319: my $result = 'UNKNOWN';
! 320: my %errors = (
! 321: 'warn' => 'WARNING',
! 322: 'crit' => 'CRITICAL',
! 323: );
! 324:
! 325: return $result unless ref $sensor eq 'HASH';
! 326: $check = parse_check($check) if $check;
! 327:
! 328: if (! $check) {
! 329: # It looks like doing this should be safe, from
! 330: # src/sbin/sysctl/sysctl.c
! 331: return $sensor->{'status'} if $sensor->{'status'};
! 332:
! 333: return;
! 334: }
! 335: elsif ($check eq 'IGNORE') {
! 336: return;
! 337: }
! 338:
! 339: use YAML;
! 340: print Dump $check;
! 341:
! 342: $result = 'OK';
! 343: foreach my $code ( 'warn', 'crit' ) {
! 344: if ( $sensor->{'type'} eq 'fan'
! 345: || $sensor->{'type'} eq 'fanrpm'
! 346: || $sensor->{'type'} eq 'volt'
! 347: || $sensor->{'type'} eq 'volts_dc'
! 348: || $sensor->{'type'} eq 'amps'
! 349: || $sensor->{'type'} eq 'watthour'
! 350: || $sensor->{'type'} eq 'amphour'
! 351: || $sensor->{'type'} eq 'integer'
! 352: || $sensor->{'type'} eq 'raw'
! 353: || $sensor->{'type'} eq 'percent'
! 354: || $sensor->{'type'} eq 'lux'
! 355: || $sensor->{'type'} eq 'timedelta' )
! 356: {
! 357: my $data = $sensor->{'data'};
! 358: $data =~ s/[^\d\.]//g;
! 359: if (! length $data ) {
! 360: warn "INVALID DATA ($sensor->{'data'}) for '$sensor->{'id'}'";
! 361: next;
! 362: }
! 363:
! 364: if ( defined $check->{ $code . ".low" }
! 365: || defined $check->{ $code . ".high" } )
! 366: {
! 367: if ( defined $check->{ $code . ".low" } ) {
! 368: my $c = $check->{ $code . ".low" };
! 369: $c =~ s/[^\d\.]//g;
! 370:
! 371: if (! length $c ) {
! 372: warn "INVALID CHECK ("
! 373: . $check->{ $code . ".low" }
! 374: . ") for '$sensor->{'id'}:$code.low'";
! 375: next;
! 376: }
! 377:
! 378: $result = $errors{$code}
! 379: if ( $c >= $data );
! 380: }
! 381: if ( defined $check->{ $code . ".high" } ) {
! 382: my $c = $check->{ $code . ".high" };
! 383: $c =~ s/[^\d\.]//g;
! 384: if (! length $c ) {
! 385: warn "INVALID CHECK ("
! 386: . $check->{ $code . ".high" }
! 387: . ") for '$sensor->{'id'}:$code.high'";
! 388: next;
! 389: }
! 390:
! 391: $result = $errors{$code}
! 392: if ( $c <= $data );
! 393: }
! 394: }
! 395: elsif ( @{ $check->{$code} } ) {
! 396: my $matched = 0;
! 397: foreach my $c ( @{ $check->{$code} } ) {
! 398: $c =~ s/[^\d\.]//g;
! 399: if (! length $c ) {
! 400: warn "INVALID CHECK (" . $c
! 401: . ") for '$sensor->{'id'}:$code'";
! 402: next;
! 403: }
! 404:
! 405: if ( $c eq $data ) {
! 406: $matched = 1;
! 407: last;
! 408: }
! 409: }
! 410: $result = $errors{$code} unless $matched;
! 411: }
! 412:
! 413: }
! 414: elsif ( $sensor->{'type'} eq 'temp' ) {
! 415: my ( $degC, $degF ) = split /\//, $sensor->{'data'};
! 416: $degC =~ s/[^\d\.]//g;
! 417: $degF ||= $degC * 9 / 5 + 32;
! 418: $degF =~ s/[^\d\.]//g;
! 419: if ( defined $check->{ $code . ".low" }
! 420: || defined $check->{ $code . ".high" } )
! 421: {
! 422: if ( defined $check->{ $code . ".low" } ) {
! 423: my $c = $check->{ $code . ".low" };
! 424: my $data = $degC;
! 425:
! 426: $data = $degF if ( $c =~ /F/i );
! 427: if (! length $data ) {
! 428: warn "INVALID DATA ("
! 429: . $sensor->{'data'}
! 430: . ") for '$sensor->{'id'}'";
! 431: next;
! 432: }
! 433:
! 434: $c =~ s/[^\d\.]//g;
! 435: if (! length $c ) {
! 436: warn "INVALID CHECK ("
! 437: . $check->{ $code . ".low" }
! 438: . ") for '$sensor->{'id'}':$code.low";
! 439: next;
! 440: }
! 441:
! 442: $result = $errors{$code}
! 443: if ( $c >= $data );
! 444: }
! 445: if ( defined $check->{ $code . ".high" } ) {
! 446: my $c = $check->{ $code . ".high" };
! 447:
! 448: my $data = $degC;
! 449: $data = $degF if ( $c =~ /F/i );
! 450: if (! length $data ) {
! 451: warn "INVALID DATA ("
! 452: . $sensor->{'data'}
! 453: . ") for '$sensor->{'id'}'";
! 454: next;
! 455: }
! 456:
! 457: $c =~ s/[^\d\.]//g;
! 458: if (! length $c ) {
! 459: warn "INVALID CHECK ("
! 460: . $check->{ $code . ".high" }
! 461: . ") for '$sensor->{'id'}:$code.high'";
! 462: next;
! 463: }
! 464:
! 465: $result = $errors{$code}
! 466: if ( $c <= $data );
! 467: }
! 468: }
! 469: elsif ( @{ $check->{$code} } ) {
! 470:
! 471: my $matched = 0;
! 472: foreach my $c ( @{ $check->{$code} } ) {
! 473: my $data = $degC;
! 474:
! 475: $data = $degF if ( $c =~ /F/i );
! 476: if (! length $data ) {
! 477: warn "INVALID DATA ("
! 478: . $sensor->{'data'}
! 479: . ") for '$sensor->{'id'}'";
! 480: next;
! 481: }
! 482:
! 483: $c =~ s/[^\d\.]//g;
! 484: if (! length $c ) {
! 485: warn "INVALID CHECK (" . $c
! 486: . ") for '$sensor->{'id'}':$code";
! 487: next;
! 488: }
! 489:
! 490: if ( $c eq $data ) {
! 491: $matched = 1;
! 492: last;
! 493: }
! 494: }
! 495: $result = $errors{$code} unless $matched;
! 496: }
! 497:
! 498: }
! 499: elsif ($sensor->{'type'} eq 'drive'
! 500: || $sensor->{'type'} eq 'indicator' )
! 501: {
! 502: $sensor->{'data'} =~ s/^drive\s+//;
! 503: if ( @{ $check->{$code} } ) {
! 504: my $matched = 0;
! 505: foreach ( @{ $check->{$code} } ) {
! 506: if ( $_ eq $sensor->{'data'} ) {
! 507: $matched = 1;
! 508: last;
! 509: }
! 510: }
! 511: $result = $errors{$code} unless $matched;
! 512: }
! 513:
! 514: }
! 515: else {
! 516: print STDERR 'Unknown Sensor Type: ',
! 517: $sensor->{'id'},
! 518: '=',
! 519: $sensor->{'type'},
! 520: "\n";
! 521: $result = 'UNKNOWN';
! 522: }
1.2 andrew 523:
1.26 ! andrew 524: }
1.2 andrew 525:
1.26 ! andrew 526: return $result;
1.2 andrew 527: }
528:
529: sub print_help {
1.26 ! andrew 530: print <<"EOL";
! 531: $PROGNAME - monitors sysctl hw.sensors on OpenBSD
1.15 andrew 532: $PROGNAME [-i] (-f [<FILENAME>]|(-s <hw.sensors id> [-w limit] [-c limit]))
1.2 andrew 533:
534: Usage:
1.15 andrew 535: -i, --ignore-status
1.26 ! andrew 536: Don't automatically check the status of sensors that report it.
1.12 andrew 537: -f, --filename=FILE
538: FILE to load checks from (defaults to /etc/sensorsd.conf)
539: -s, --sensor=ID
1.26 ! andrew 540: ID of a single sensor. "-s kate0.temp0" means hw.sensors.kate0.temp0
1.12 andrew 541: -w, --warning=RANGE or single ENTRY
542: Exit with WARNING status if outside of RANGE or if != ENTRY
1.13 andrew 543: -c, --critical=RANGE or single ENTRY
1.12 andrew 544: Exit with CRITICAL status if outside of RANGE or if != ENTRY
545:
1.13 andrew 546: FILE is in the same format as sensorsd.conf(5) plus some additional
547: entries. These additional entries in the file are ignored by
1.26 ! andrew 548: sensorsd(8) this means you can use the same config file for $PROGNAME
! 549: as well as sensorsd(8).
1.12 andrew 550:
551: $PROGNAME understands the following entries:
552:
1.15 andrew 553: low, high, crit, warn, crit.low, crit.high, warn.low, warn.high,
554: ignore, status
1.12 andrew 555:
556: An ENTRY depends on the type. The descriptions in sensorsd.conf(5)
557: can be used when appropriate, or you can use the following:
558:
1.20 andrew 559: fanrpm, volts_dc, amps, watthour, amphour, integer (raw), percent,
560: lux or timedelta - Anything that includes digits. Both the value of
561: the check and the value of the sensor response that are not either a
562: digit or period are stripped and then the two resultant values are
563: compared.
1.12 andrew 564:
565: temp - Can be as above, but if the entry has an F in it,
566: it compares farenheit, otherwise it uses celcius.
567:
568: indicator or drive - does a case sensitive match of each
569: entry in the comma separated list and if it does not match
1.20 andrew 570: any of the entries, it sets the status.
1.12 andrew 571:
572: The entries 'crit' or 'warn' (or the -c or -w on the command line)
573: may be a RANGE or a comma separated list of acceptable values.
574: The comma separated list of values contains a list of things that
575: will NOT cause the status. This is possibly counterintuitive, but
576: you are more likely to know good values than bad values.
577:
578: A RANGE is a low ENTRY and a high ENTRY separated by a colon (:).
579: It can also be low: or :high with the other side left blank to only
1.26 ! andrew 580: make the single check.
1.2 andrew 581:
1.15 andrew 582: An entry marked "ignore" will cause that sensor to be skipped.
583: Generally used with state checking of all sensors to ignore sensors you
584: don't care about or that report incorrectly.
585:
586: If you are using --ignore-status, you can still check the status of
587: individual sensors with a status entry.
588:
1.2 andrew 589: EOL
1.15 andrew 590:
1.26 ! andrew 591: print_revision( $PROGNAME, '$Revision: 1.25 $' );
! 592:
! 593: print $License;
1.2 andrew 594: }
1.26 ! andrew 595:
! 596:
! 597: sub print_revision {
! 598: my ($prog, $rev) = @_;
! 599: $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
! 600:
! 601: print "$prog $rev\n";
! 602: }
! 603:
! 604:
1.2 andrew 605:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>