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