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