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