Annotation of nagios/check_hw_sensors/check_hw_sensors, Revision 1.32
1.14 andrew 1: #!/usr/bin/perl -T
1.32 ! andrew 2: # $RedRiver: check_hw_sensors,v 1.31 2009/11/10 18:24:56 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.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.28 andrew 48: my $opt_V;
49: my $opt_h;
50: my $ignore_status;
1.2 andrew 51: my $filename;
52: my $sensor;
53: my $warning;
54: my $critical;
55:
1.4 andrew 56: my $CHECK_SENSOR = $BASE;
57: my %CHECKS;
1.32 ! andrew 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.28 andrew 64: "ignore-status|i" => \$ignore_status,
1.26 andrew 65: "filename|f:s" => \$filename,
66: "sensor|s=s" => \$sensor,
67: "warning|w=s" => \$warning,
68: "critical|c=s" => \$critical,
1.2 andrew 69: );
1.18 andrew 70:
1.32 ! andrew 71: if ( $status == 0 ) {
! 72: print_help();
! 73: exit $ERRORS{'OK'};
! 74: }
! 75:
! 76: if ($opt_V) {
! 77: print_revision( $PROGNAME, '$Revision: 1.31 $ ' );
! 78: exit $ERRORS{'OK'};
! 79: }
! 80:
! 81: if ($opt_h) {
! 82: print_help();
! 83: exit $ERRORS{'OK'};
! 84: }
! 85:
1.2 andrew 86: # set the default this way so it only happens if someone typed -f or --filename
1.26 andrew 87: $filename = $DEFAULT_CONFIG if ( defined $filename && $filename eq '' );
1.2 andrew 88:
1.18 andrew 89: # Stuff is output in this file by print_sensor()
90: # http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/sysctl/sysctl.c
91: my @Type_Map = (
1.26 andrew 92: { type => 'temp',
93: regex => qr/\sdegC$/,
94: },
95: { type => 'fanrpm',
96: regex => qr/\sRPM$/,
97: },
98: { type => 'volts_dc',
99: regex => qr/\sV\sDC$/,
100: },
101: { type => 'amps',
102: regex => qr/\sA$/,
103: },
104: { type => 'watthour',
105: regex => qr/\sWh$/,
106: },
107: { type => 'amphour',
108: regex => qr/\sAh$/,
109: },
110: { type => 'indicator',
111: regex => qr/^(On|Off)$/,
112: },
113: { type => 'integer',
114: regex => qr/\sraw$/,
115: },
116: { type => 'percent',
117: regex => qr/\s\%$/,
118: },
119: { type => 'lux',
120: regex => qr/\slx$/,
121: },
122: { type => 'drive',
123: regex => qr/^drive\s/,
124: },
125: { type => 'timedelta',
126: regex => qr/\ssecs$/,
127: },
128: );
129:
130: if ( defined $sensor ) {
131: if ( $sensor !~ /^$BASE/ ) {
132: $sensor = $BASE . '.' . $sensor;
133: }
134: $CHECK_SENSOR = $sensor;
1.2 andrew 135:
1.32 ! andrew 136: $CHECKS{$sensor}{'warn'} = $warning;
! 137: $CHECKS{$sensor}{'crit'} = $critical;
1.2 andrew 138:
1.26 andrew 139: }
140: elsif ( defined $filename ) {
141: %CHECKS = parse_file($filename);
1.2 andrew 142: }
143:
1.26 andrew 144: open my $sysctl, "-|", $SYSCTL, $CHECK_SENSOR
1.2 andrew 145: or die "Couldn't open sysctl: $!";
146: while (<$sysctl>) {
1.26 andrew 147: chomp;
1.32 ! andrew 148: my ( $id, $output ) = split /=/xms;
! 149: my @s = split /\./xms, $id;
! 150: my @o = split /,\s*/xms, $output;
1.26 andrew 151:
152: my ( $type, $source, $descr, $data, $status );
153:
154: $source = $o[0];
155: $descr = $o[1];
156:
157: if ( $OSVer >= 4.1 ) {
158: $data = $o[0];
1.32 ! andrew 159: if ( $data =~ s/\s+\((.*)\).*$//xms ) {
1.26 andrew 160: $descr = $1;
161: }
162: $status = $o[1];
1.32 ! andrew 163: ( $source, $type ) = $id =~ /([^\.]+)\.([^\.]+?)\d+$/xms;
1.26 andrew 164: }
165: elsif ( $OSVer >= 4.0 ) {
166: $data = $o[2];
167: $status = $o[3];
168: foreach my $t (@Type_Map) {
1.32 ! andrew 169: if ( $data =~ /$t->{'regex'}/xms ) {
1.26 andrew 170: $type = $t->{'type'};
171: last;
172: }
173: }
174: }
175: else {
176: $data = $o[-1];
177: $status = $o[2] if @o == 5;
178: $type = $o[-2];
179: }
180:
181: $type ||= 'unknown';
182:
1.32 ! andrew 183: push @SENSORS,
! 184: {
1.26 andrew 185: id => $id,
186: output => $output,
187: source => $source,
188: description => $descr,
189: status => $status,
190: type => $type,
191: data => $data,
1.32 ! andrew 192: };
1.2 andrew 193: }
194: close $sysctl;
195:
1.32 ! andrew 196: foreach my $sensor (@SENSORS) {
1.26 andrew 197: my ( $r, $data );
1.32 ! andrew 198: if ( exists $CHECKS{ $sensor->{id} } ) {
! 199: $r = check_sensor( $sensor, $CHECKS{ $sensor->{id} } );
! 200: $data = $sensor->{id} . '=' . $sensor->{output};
! 201: }
! 202: elsif ( !$ignore_status && $sensor->{status} ) {
! 203: $r = check_sensor( $sensor, { STATUS => 1 } );
! 204: $data = $sensor->{id} . '=' . $sensor->{output};
1.26 andrew 205: }
206: else {
207:
1.32 ! andrew 208: # ignore this sensor, theoretically you could do the check and
! 209: # that would show unknown sensors.
! 210: }
! 211: if ( defined $r ) {
! 212: push @{ $states{$r} }, $data;
1.26 andrew 213: }
1.2 andrew 214: }
215:
1.32 ! andrew 216: my $have_results = 0;
1.2 andrew 217: $state = 'OK';
1.32 ! andrew 218: foreach my $error ( sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS ) {
1.26 andrew 219: if ( exists $states{$error} ) {
220: $have_results++;
1.32 ! andrew 221: $state = $error if $ERRORS{$state} < $ERRORS{$error};
! 222:
1.26 andrew 223: if (NAGIOS_OUTPUT) {
224: print "$error (" . scalar( @{ $states{$error} } ) . ")";
225: if ( $error ne 'OK' ) {
226: print '<br>';
227: print map {" - $_<br>"} @{ $states{$error} };
228: }
229: }
230: else {
231: print "$error (" . scalar( @{ $states{$error} } ) . "):\n";
232: foreach ( @{ $states{$error} } ) {
233: print " $_\n";
234: }
235: }
236: }
1.4 andrew 237: }
1.26 andrew 238: if ( $have_results == 0 ) {
239: print "No results found\n";
1.5 andrew 240: }
1.2 andrew 241: exit $ERRORS{$state};
242:
1.26 andrew 243: sub parse_file {
244: my $filename = shift;
245: my %contents;
1.2 andrew 246:
1.26 andrew 247: die "file '$filename' does not exist." unless -e $filename;
248:
249: open my $fh, '-|', $GETCAP, '-a', '-f', $filename
250: or die "Couldn't open FILE '$GETCAP -a -f $filename': $!";
251: while (<$fh>) {
252: chomp;
253: my ( $key, @c ) = split /\:/;
254: foreach (@c) {
255: my ( $k, $v ) = split /\=/;
256: if ( lc($k) eq 'ignore' ) {
257: $contents{$key}{'IGNORE'} = 1;
258: }
259: elsif ( lc($k) eq 'status' ) {
260: $contents{$key}{'STATUS'} = 1;
261: }
262: else {
263: $contents{$key}{$k} = $v;
264: }
265: }
266: }
267: close $fh;
1.2 andrew 268:
1.26 andrew 269: return %contents;
1.2 andrew 270: }
271:
272: sub parse_check {
1.26 andrew 273: my $check = shift;
1.2 andrew 274:
1.26 andrew 275: return unless $check;
1.32 ! andrew 276: return 'STATUS' if $check->{'STATUS'};
1.26 andrew 277: return 'IGNORE' if $check->{'IGNORE'};
278:
279: foreach my $code ( 'crit', 'warn' ) {
280: if ( defined $check->{$code} && $check->{$code} =~ /:/ ) {
281: if ( my ( $low, $high ) = split /:/, $check->{$code} ) {
282: $check->{ $code . '.low' } = $low if length $low;
283: $check->{ $code . '.high' } = $high if length $high;
284: }
285: delete $check->{$code};
286: }
287:
288: foreach my $severity ( 'low', 'high' ) {
289: if ( defined $check->{$severity} ) {
290: $check->{ $code . '.' . $severity } = $check->{$severity}
291: if !defined $check->{ $code . '.' . $severity };
292: }
293: }
294: no warnings 'uninitialized';
295: $check->{$code} = [ split /,\s*/, $check->{$code} ];
296: }
1.2 andrew 297:
1.26 andrew 298: return $check;
1.2 andrew 299: }
300:
301: sub check_sensor {
1.26 andrew 302: my $sensor = shift;
303: my $check = shift;
304: my $result = 'UNKNOWN';
305: my %errors = (
306: 'warn' => 'WARNING',
307: 'crit' => 'CRITICAL',
308: );
309:
310: return $result unless ref $sensor eq 'HASH';
311: $check = parse_check($check) if $check;
312:
1.27 andrew 313: if ( !$check ) {
1.32 ! andrew 314: return $result;
! 315: }
! 316: elsif ( $check eq 'STATUS' ) {
1.27 andrew 317:
1.26 andrew 318: # It looks like doing this should be safe, from
319: # src/sbin/sysctl/sysctl.c
1.32 ! andrew 320: return ( $sensor->{'status'} || $result );
1.26 andrew 321: }
1.27 andrew 322: elsif ( $check eq 'IGNORE' ) {
1.26 andrew 323: return;
324: }
325:
326: foreach my $code ( 'warn', 'crit' ) {
327: if ( $sensor->{'type'} eq 'fan'
328: || $sensor->{'type'} eq 'fanrpm'
329: || $sensor->{'type'} eq 'volt'
330: || $sensor->{'type'} eq 'volts_dc'
331: || $sensor->{'type'} eq 'amps'
332: || $sensor->{'type'} eq 'watthour'
333: || $sensor->{'type'} eq 'amphour'
334: || $sensor->{'type'} eq 'integer'
335: || $sensor->{'type'} eq 'raw'
336: || $sensor->{'type'} eq 'percent'
337: || $sensor->{'type'} eq 'lux'
338: || $sensor->{'type'} eq 'timedelta' )
339: {
340: my $data = $sensor->{'data'};
1.32 ! andrew 341: $data =~ s/[^\d\.]//gxms;
1.27 andrew 342: if ( !length $data ) {
343: warn
344: "INVALID DATA ($sensor->{'data'}) for '$sensor->{'id'}'\n";
1.26 andrew 345: next;
346: }
347:
348: if ( defined $check->{ $code . ".low" }
349: || defined $check->{ $code . ".high" } )
350: {
351: if ( defined $check->{ $code . ".low" } ) {
352: my $c = $check->{ $code . ".low" };
1.32 ! andrew 353: $c =~ s/[^\d\.]//gxms;
1.26 andrew 354:
1.27 andrew 355: if ( !length $c ) {
1.26 andrew 356: warn "INVALID CHECK ("
357: . $check->{ $code . ".low" }
1.27 andrew 358: . ") for '$sensor->{'id'}:$code.low'\n";
1.26 andrew 359: next;
360: }
361:
362: $result = $errors{$code}
363: if ( $c >= $data );
364: }
365: if ( defined $check->{ $code . ".high" } ) {
366: my $c = $check->{ $code . ".high" };
1.32 ! andrew 367: $c =~ s/[^\d\.]//gxms;
1.27 andrew 368: if ( !length $c ) {
1.26 andrew 369: warn "INVALID CHECK ("
370: . $check->{ $code . ".high" }
1.27 andrew 371: . ") for '$sensor->{'id'}:$code.high'\n";
1.26 andrew 372: next;
373: }
374:
375: $result = $errors{$code}
376: if ( $c <= $data );
377: }
1.32 ! andrew 378: $result = 'OK' if $result eq 'UNKNOWN';
1.26 andrew 379: }
380: elsif ( @{ $check->{$code} } ) {
381: my $matched = 0;
1.27 andrew 382: foreach ( @{ $check->{$code} } ) {
383: my $c = $_;
1.32 ! andrew 384: $c =~ s/[^\d\.]//gxms;
1.27 andrew 385: if ( !length $c ) {
386: warn "INVALID CHECK (" . $_
387: . ") for '$sensor->{'id'}:$code'\n";
1.26 andrew 388: next;
389: }
390:
391: if ( $c eq $data ) {
392: $matched = 1;
393: last;
394: }
395: }
1.32 ! andrew 396: if ($matched) {
! 397: $result = 'OK' if $result eq 'UNKNOWN';
! 398: }
! 399: else {
! 400: $result = $errors{$code};
! 401: }
1.26 andrew 402: }
403: }
404: elsif ( $sensor->{'type'} eq 'temp' ) {
405: my ( $degC, $degF ) = split /\//, $sensor->{'data'};
1.32 ! andrew 406: $degC =~ s/[^\d\.]//gxms;
1.26 andrew 407: $degF ||= $degC * 9 / 5 + 32;
1.32 ! andrew 408: $degF =~ s/[^\d\.]//gxms;
1.26 andrew 409: if ( defined $check->{ $code . ".low" }
410: || defined $check->{ $code . ".high" } )
411: {
412: if ( defined $check->{ $code . ".low" } ) {
413: my $c = $check->{ $code . ".low" };
414: my $data = $degC;
415:
1.32 ! andrew 416: $data = $degF if ( $c =~ /F/ixms );
1.27 andrew 417: if ( !length $data ) {
1.26 andrew 418: warn "INVALID DATA ("
419: . $sensor->{'data'}
1.27 andrew 420: . ") for '$sensor->{'id'}'\n";
1.26 andrew 421: next;
422: }
423:
1.32 ! andrew 424: $c =~ s/[^\d\.]//gxms;
1.27 andrew 425: if ( !length $c ) {
1.26 andrew 426: warn "INVALID CHECK ("
427: . $check->{ $code . ".low" }
1.27 andrew 428: . ") for '$sensor->{'id'}':$code.low\n";
1.26 andrew 429: next;
430: }
431:
432: $result = $errors{$code}
433: if ( $c >= $data );
434: }
435: if ( defined $check->{ $code . ".high" } ) {
436: my $c = $check->{ $code . ".high" };
437:
438: my $data = $degC;
1.32 ! andrew 439: $data = $degF if ( $c =~ /F/ixms );
1.27 andrew 440: if ( !length $data ) {
1.26 andrew 441: warn "INVALID DATA ("
442: . $sensor->{'data'}
1.27 andrew 443: . ") for '$sensor->{'id'}'\n";
1.26 andrew 444: next;
445: }
446:
1.32 ! andrew 447: $c =~ s/[^\d\.]//gxms;
1.27 andrew 448: if ( !length $c ) {
1.26 andrew 449: warn "INVALID CHECK ("
450: . $check->{ $code . ".high" }
1.27 andrew 451: . ") for '$sensor->{'id'}:$code.high'\n";
1.26 andrew 452: next;
453: }
454:
455: $result = $errors{$code}
456: if ( $c <= $data );
457: }
1.32 ! andrew 458: $result = 'OK' if $result eq 'UNKNOWN';
1.26 andrew 459: }
460: elsif ( @{ $check->{$code} } ) {
461: my $matched = 0;
1.27 andrew 462: foreach ( @{ $check->{$code} } ) {
463: my $c = $_;
1.26 andrew 464: my $data = $degC;
465:
1.32 ! andrew 466: $data = $degF if ( $c =~ /F/ixms );
1.27 andrew 467: if ( !length $data ) {
1.26 andrew 468: warn "INVALID DATA ("
469: . $sensor->{'data'}
1.27 andrew 470: . ") for '$sensor->{'id'}'\n";
1.26 andrew 471: next;
472: }
473:
1.32 ! andrew 474: $c =~ s/[^\d\.]//gxms;
1.27 andrew 475: if ( !length $c ) {
476: warn "INVALID CHECK (" . $_
477: . ") for '$sensor->{'id'}':$code\n";
1.26 andrew 478: next;
479: }
480:
481: if ( $c eq $data ) {
482: $matched = 1;
483: last;
484: }
485: }
1.32 ! andrew 486: if ($matched) {
! 487: $result = 'OK' if $result eq 'UNKNOWN';
! 488: }
! 489: else {
! 490: $result = $errors{$code};
! 491: }
1.26 andrew 492: }
493: }
494: elsif ($sensor->{'type'} eq 'drive'
495: || $sensor->{'type'} eq 'indicator' )
496: {
497: $sensor->{'data'} =~ s/^drive\s+//;
498: if ( @{ $check->{$code} } ) {
499: my $matched = 0;
500: foreach ( @{ $check->{$code} } ) {
501: if ( $_ eq $sensor->{'data'} ) {
502: $matched = 1;
503: last;
504: }
505: }
1.32 ! andrew 506: if ($matched) {
! 507: $result = 'OK' if $result eq 'UNKNOWN';
! 508: }
! 509: else {
! 510: $result = $errors{$code};
! 511: }
1.26 andrew 512: }
513:
514: }
515: else {
1.27 andrew 516: warn 'Unknown Sensor Type: ',
1.26 andrew 517: $sensor->{'id'},
518: '=',
519: $sensor->{'type'},
520: "\n";
521: }
1.2 andrew 522:
1.26 andrew 523: }
1.2 andrew 524:
1.26 andrew 525: return $result;
1.2 andrew 526: }
527:
528: sub print_help {
1.26 andrew 529: print <<"EOL";
530: $PROGNAME - monitors sysctl hw.sensors on OpenBSD
1.30 andrew 531: $PROGNAME [-i] [-f [<FILENAME>]|(-s <hw.sensors id> [-w limit] [-c limit])]
1.2 andrew 532:
533: Usage:
1.15 andrew 534: -i, --ignore-status
1.26 andrew 535: Don't automatically check the status of sensors that report it.
1.12 andrew 536: -f, --filename=FILE
537: FILE to load checks from (defaults to /etc/sensorsd.conf)
538: -s, --sensor=ID
1.26 andrew 539: ID of a single sensor. "-s kate0.temp0" means hw.sensors.kate0.temp0
1.27 andrew 540: Overrides --filename.
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.32 ! andrew 591: print_revision( $PROGNAME, '$Revision: 1.31 $' );
1.26 andrew 592:
593: print $License;
1.2 andrew 594: }
1.26 andrew 595:
596: sub print_revision {
1.27 andrew 597: my ( $prog, $rev ) = @_;
1.26 andrew 598: $rev =~ s/^\D+([\d\.]+)\D+$/v$1/xms;
599:
600: print "$prog $rev\n";
601: }
1.2 andrew 602:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>