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