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