Annotation of nagios/check_hw_sensors/check_hw_sensors, Revision 1.18
1.14 andrew 1: #!/usr/bin/perl -T
1.18 ! andrew 2: # $RedRiver: check_hw_sensors,v 1.17 2006/10/25 23:30:23 andrew Exp $
1.2 andrew 3: ########################################################################
4: # check_hw_sensors *** A nagios check for OpenBSD hw.sensors
5: #
6: # 2006.05.01 #*#*# andrew fresh <andrew@mad-techies.org>
7: ########################################################################
1.4 andrew 8: # TODO:
1.12 andrew 9: # Really need real documentation.
1.4 andrew 10: ########################################################################
1.2 andrew 11: use strict;
12: use warnings;
13:
1.14 andrew 14: %ENV = ();
15:
1.16 andrew 16: use constant NAGIOS_OUTPUT => 1;
1.4 andrew 17:
1.2 andrew 18: use POSIX;
1.4 andrew 19: use lib "/usr/local/libexec/nagios";
1.2 andrew 20: use utils qw($TIMEOUT %ERRORS &print_revision &support);
21:
22: use Getopt::Long;
23: Getopt::Long::Configure('bundling');
24:
25: my $PROGNAME = "check_hw_sensors";
26:
1.4 andrew 27: my $SYSCTL = '/sbin/sysctl';
28: my $GETCAP = '/usr/bin/getcap';
1.2 andrew 29: my $BASE = 'hw.sensors';
1.4 andrew 30: my $DEFAULT_CONFIG = '/etc/sensorsd.conf';
1.2 andrew 31:
32: my $state = 'UNKNOWN'; # tells whether the it is warning, critical, or OK
33: my %states; # This stores the count of states;
34: my $filename;
1.15 andrew 35: my $ignore_status;
1.2 andrew 36: my $sensor;
37: my $warning;
38: my $critical;
1.10 andrew 39: my $opt_h;
40: my $opt_V;
1.2 andrew 41:
1.4 andrew 42: my $CHECK_SENSOR = $BASE;
43: my %CHECKS;
44: my %SENSORS;
1.2 andrew 45:
46: #Option checking
47: my $status = GetOptions(
1.15 andrew 48: "version|V" => \$opt_V,
49: "filename|f:s" => \$filename,
50: "ignore-status|i" => \$ignore_status,
51: "sensor|s=s" => \$sensor,
52: "warning|w=s" => \$warning,
53: "critical|c=s" => \$critical,
1.2 andrew 54: );
1.18 ! andrew 55:
1.2 andrew 56: # set the default this way so it only happens if someone typed -f or --filename
1.4 andrew 57: $filename = $DEFAULT_CONFIG if (defined $filename && $filename eq '');
1.2 andrew 58:
1.18 ! andrew 59: # Stuff is output in this file by print_sensor()
! 60: # http://www.openbsd.org/cgi-bin/cvsweb/src/sbin/sysctl/sysctl.c
! 61: my @Type_Map = (
! 62: {
! 63: type => 'temp',
! 64: regex => qr/\sdegC$/,
! 65: },
! 66: {
! 67: type => 'fanrpm',
! 68: regex => qr/\sRPM$/,
! 69: },
! 70: {
! 71: type => 'volts_dc',
! 72: regex => qr/\sV\sDC$/,
! 73: },
! 74: {
! 75: type => 'amps',
! 76: regex => qr/\sA$/,
! 77: },
! 78: {
! 79: type => 'watthour',
! 80: regex => qr/\sWh$/,
! 81: },
! 82: {
! 83: type => 'amphour',
! 84: regex => qr/\sAh$/,
! 85: },
! 86: {
! 87: type => 'indicator',
! 88: regex => qr/^(On|Off)$/,
! 89: },
! 90: {
! 91: type => 'integer',
! 92: regex => qr/\sraw$/,
! 93: },
! 94: {
! 95: type => 'lux',
! 96: regex => qr/\slx$/,
! 97: },
! 98: {
! 99: type => 'drive',
! 100: regex => qr/^drive\s/,
! 101: },
! 102: {
! 103: type => 'timedelta',
! 104: regex => qr/\ssecs$/,
! 105: },
! 106: );
! 107:
1.2 andrew 108: if ($status == 0) {
109: print_help() ;
110: exit $ERRORS{'OK'};
111: }
112:
113: if ($opt_V) {
1.18 ! andrew 114: print_revision($PROGNAME,'$Revision: 1.17 $ ');
1.2 andrew 115: exit $ERRORS{'OK'};
116: }
117:
1.15 andrew 118: unless (
119: (
1.10 andrew 120: defined $filename ||
1.15 andrew 121: (not defined $ignore_status) ||
1.10 andrew 122: (defined $sensor && ($warning || $critical))
123: ) &&
124: ( (!defined $filename) || (!defined $sensor) )
1.2 andrew 125: ) {
126: print_help();
127: exit $ERRORS{'OK'};
128: }
129:
130:
131: if (defined $sensor) {
132: if ($sensor !~ /^$BASE/) {
133: $sensor = $BASE . '.' . $sensor;
134: }
135: $CHECK_SENSOR = $sensor;
136:
1.15 andrew 137: $CHECKS{$sensor}{'warn'} = $warning if defined $warning;
138: $CHECKS{$sensor}{'crit'} = $critical if defined $critical;
139:
1.2 andrew 140: } elsif (defined $filename) {
141: %CHECKS = parse_file($filename);
142: }
143:
144: open my $sysctl, "-|", $SYSCTL, $CHECK_SENSOR
145: or die "Couldn't open sysctl: $!";
146: while (<$sysctl>) {
147: #while (<>) {
148: chomp;
149: my ($id, $output) = split /=/;
150: my @o = split /,\s*/, $output;
151:
1.18 ! andrew 152: my ($type, $source, $descr, $data, $status);
! 153:
! 154: $source = $o[0];
! 155: $descr = $o[1];
! 156: $type = $o[-2] if @o >= 4;
! 157: $data = $o[-1];
! 158:
! 159: $status = $o[2] if ($type && @o == 5) || @o == 4;
! 160:
! 161: unless ($type) {
! 162: foreach my $t (@Type_Map) {
! 163: if ($data =~ /$t->{'regex'}/) {
! 164: $type = $t->{'type'};
! 165: last;
! 166: }
! 167: }
! 168: }
! 169:
! 170: $type ||= 'unknown';
1.2 andrew 171:
172: $SENSORS{$id} = {
173: id => $id,
174: output => $output,
175: source => $source,
176: description => $descr,
177: status => $status,
178: type => $type,
179: data => $data,
180: };
181:
182: }
183: close $sysctl;
184:
185: sub as_if_numeric {
186: my $_a = $a;
187: my $_b = $b;
188: $_a =~ s/\D//g;
189: $_b =~ s/\D//g;
190: $_a <=> $_b;
191: }
192:
1.15 andrew 193: foreach my $s (sort as_if_numeric keys %SENSORS) {
194: my ($r, $data);
195: if (exists $CHECKS{$s}) {
196: $r = check_sensor($SENSORS{$s}, $CHECKS{$s});
197: $data = $s . '=' . $SENSORS{$s}{'output'};
198: } elsif (not $ignore_status) {
199: $r = check_sensor($SENSORS{$s});
200: $data = $s . '=' . $SENSORS{$s}{'output'};
1.2 andrew 201: } else {
1.15 andrew 202: # ignore this sensor
1.2 andrew 203: }
1.15 andrew 204: next unless defined $r;
205: push @{ $states{ $r } }, $data;
1.2 andrew 206: }
207:
208: $state = 'OK';
1.5 andrew 209: my $have_results = 0;
210: foreach my $error (sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS) {
211: if (exists $states{$error}) {
212: $have_results++;
213: $state = $error;
214: }
215: }
1.4 andrew 216: foreach my $error (sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS) {
217: if (exists $states{$error}) {
218: if (NAGIOS_OUTPUT) {
1.8 andrew 219: print "$error (" . scalar(@{ $states{ $error } }) . ")";
1.5 andrew 220: unless ($error eq 'OK') {
1.9 andrew 221: print '<br>';
1.15 andrew 222: print map {" - $_<br>"} @{ $states{ $error } };
1.4 andrew 223: }
224: } else {
225: print "$error (" . scalar(@{ $states{ $error } }) . "):\n";
226: foreach (@{ $states{ $error } }) {
227: print " $_\n";
228: }
229: }
230: }
231: }
1.5 andrew 232: if ($have_results == 0) {
233: print "No results found\n";
234: }
1.2 andrew 235: exit $ERRORS{$state};
236:
237:
238: sub parse_file {
239: my $filename = shift;
240: my %contents;
1.15 andrew 241:
242: die "file '$filename' does not exist." unless -e $filename;
1.2 andrew 243:
244: open my $fh, '-|', $GETCAP, '-a', '-f', $filename
245: or die "Couldn't open FILE '$GETCAP -a -f $filename': $!";
1.15 andrew 246: LINE: while (<$fh>) {
1.2 andrew 247: chomp;
248: my ($key, @c) = split /\:/;
249: foreach (@c) {
250: my ($k, $v) = split /\=/;
1.15 andrew 251: if (lc($k) eq 'ignore') {
252: $contents{$key}{'IGNORE'} = 1;
253: } elsif (lc($k) eq 'status') {
254: $contents{$key}{'STATUS'} = 1;
255: } else {
256: $contents{$key}{$k} = $v;
257: }
1.2 andrew 258: }
259: }
260: close $fh;
261:
262: return %contents;
263: }
264:
265: sub parse_check {
266: my $type = shift;
267: my $check = shift;
268:
1.15 andrew 269: return undef unless $check;
270: return undef if $check->{'STATUS'};
271: return 'IGNORE' if $check->{'IGNORE'};
272:
1.10 andrew 273: foreach my $code ('crit', 'warn') {
274: if (defined $check->{$code} && $check->{$code} =~ /:/) {
275: if (my ($low, $high) = split /:/, $check->{$code}) {
276: $check->{$code . '.low'} = $low;
277: $check->{$code . '.high'} = $high;
278: }
279: delete $check->{$code};
1.2 andrew 280: }
1.10 andrew 281:
282: foreach my $severity ('low', 'high') {
283: if (defined $check->{$severity}) {
284: $check->{ $code . '.' . $severity } = $check->{$severity}
285: unless defined $check->{ $code . '.' . $severity };
286: }
1.2 andrew 287: }
1.10 andrew 288: no warnings 'uninitialized';
289: $check->{$code} = [ split /,\s*/, $check->{$code} ];
1.2 andrew 290: }
291:
292: return $check;
293: }
294:
295: sub check_sensor {
1.15 andrew 296: my $sensor = shift;
1.2 andrew 297: my $check = shift;
298: my $result = 'UNKNOWN';
299: my %errors = (
300: 'warn' => 'WARNING',
301: 'crit' => 'CRITICAL',
302: );
303:
304: return $result unless ref $sensor eq 'HASH';
1.15 andrew 305: $check = parse_check($sensor->{'type'}, $check) if $check;
306:
307: unless ($check) {
308: if ($sensor->{'status'}) {
309: # It looks like doing this should be safe, from
310: # src/sbin/sysctl/sysctl.c
311: $result = $sensor->{'status'}
312: } else {
313: return undef;
314: }
315: return $result;
316: }
1.2 andrew 317:
1.15 andrew 318: return undef if $check eq 'IGNORE';
1.2 andrew 319:
320: $result = 'OK';
321: foreach my $code ('warn', 'crit') {
322: if (
323: $sensor->{'type'} eq 'volts_dc' ||
324: $sensor->{'type'} eq 'fanrpm' ||
325: $sensor->{'type'} eq 'raw'
326: ) {
327: my $data = $sensor->{'data'};
328: $data =~ s/[^\d\.]//g;
329: unless (length $data) {
330: warn "INVALID DATA ($sensor->{'data'}) for '$sensor->{'id'}'";
331: next;
332: }
333:
334: if (
335: defined $check->{$code . ".low"} ||
336: defined $check->{$code . ".high"}
337: ) {
338: if (defined $check->{$code . ".low"}) {
339: my $c = $check->{$code . ".low"};
340: $c =~ s/[^\d\.]//g;
341:
342: unless (length $c) {
343: warn "INVALID CHECK (" . $check->{$code . ".low"} .
344: ") for '$sensor->{'id'}:$code.low'";
345: next;
346: }
347:
348: $result = $errors{$code}
349: if ($c >= $data);
350: }
351: if (defined $check->{$code . ".high"}) {
352: my $c = $check->{$code . ".high"};
353: $c =~ s/[^\d\.]//g;
354: unless (length $c) {
355: warn "INVALID CHECK (" . $check->{$code . ".high"} .
356: ") for '$sensor->{'id'}:$code.high'";
357: next;
358: }
359:
360: $result = $errors{$code}
361: if ($c <= $data);
362: }
1.9 andrew 363: } elsif (@{ $check->{$code} }) {
1.2 andrew 364: my $matched = 0;
365: foreach my $c (@{ $check->{$code} }) {
366: $c =~ s/[^\d\.]//g;
367: unless (length $c) {
1.7 andrew 368: warn "INVALID CHECK (" . $c .
1.2 andrew 369: ") for '$sensor->{'id'}:$code'";
370: next;
371: }
372:
1.15 andrew 373: if ($c eq $data) {
1.2 andrew 374: $matched = 1;
375: last;
376: }
377: }
378: $result = $errors{$code} unless $matched;
379: }
380:
381: } elsif ($sensor->{'type'} eq 'temp') {
382: my ($degC, $degF) = split /\//, $sensor->{'data'};
383: $degC =~ s/[^\d\.]//g;
1.18 ! andrew 384: $degF ||= $degC * 9 / 5 + 32;
1.2 andrew 385: $degF =~ s/[^\d\.]//g;
386: if (
387: defined $check->{$code . ".low"} ||
388: defined $check->{$code . ".high"}
389: ) {
390: if (defined $check->{$code . ".low"}) {
391: my $c = $check->{$code . ".low"};
392: my $data = $degC;
393:
394: $data = $degF if ($c =~ /F/i);
395: unless (length $data) {
396: warn "INVALID DATA (" . $sensor->{'data'} .
397: ") for '$sensor->{'id'}'";
398: next;
399: }
400:
401: $c =~ s/[^\d\.]//g;
402: unless (length $c) {
403: warn "INVALID CHECK (" . $check->{$code . ".low"} .
404: ") for '$sensor->{'id'}':$code.low";
405: next;
406: }
407:
408: $result = $errors{$code}
409: if ($c >= $data);
410: }
411: if (defined $check->{$code . ".high"}) {
412: my $c = $check->{$code . ".high"};
413:
414: my $data = $degC;
415: $data = $degF if ($c =~ /F/i);
416: unless (length $data) {
417: warn "INVALID DATA (" . $sensor->{'data'} .
418: ") for '$sensor->{'id'}'";
419: next;
420: }
421:
422: $c =~ s/[^\d\.]//g;
423: unless (length $c) {
424: warn "INVALID CHECK (" . $check->{$code . ".high"} .
425: ") for '$sensor->{'id'}:$code.high'";
426: next;
427: }
428:
429: $result = $errors{$code}
430: if ($c <= $data);
431: }
1.9 andrew 432: } elsif (@{ $check->{$code} }) {
1.2 andrew 433:
434: my $matched = 0;
435: foreach my $c (@{ $check->{$code} }) {
436: my $data = $degC;
437:
438: $data = $degF if ($c =~ /F/i);
439: unless (length $data) {
440: warn "INVALID DATA (" . $sensor->{'data'} .
441: ") for '$sensor->{'id'}'";
442: next;
443: }
444:
445: $c =~ s/[^\d\.]//g;
446: unless (length $c) {
1.7 andrew 447: warn "INVALID CHECK (" . $c .
1.2 andrew 448: ") for '$sensor->{'id'}':$code";
449: next;
450: }
451:
1.15 andrew 452: if ($c eq $data) {
1.2 andrew 453: $matched = 1;
454: last;
455: }
456: }
457: $result = $errors{$code} unless $matched;
458: }
459:
460: } elsif (
461: $sensor->{'type'} eq 'drive' ||
462: $sensor->{'type'} eq 'indicator'
463: ) {
1.18 ! andrew 464: $sensor->{'type'} =~ s/^drive\s+//;
1.9 andrew 465: if (@{ $check->{$code} }) {
1.2 andrew 466: my $matched = 0;
467: foreach (@{ $check->{$code} }) {
468: if ($_ eq $sensor->{'data'}) {
469: $matched = 1;
470: last;
471: }
472: }
473: $result = $errors{$code} unless $matched;
474: }
475:
476: } else {
477: $result = 'UNKNOWN';
478: }
479:
480: }
481:
482: return $result;
483: }
484:
485: sub print_help {
486: print <<EOL;
487: $PROGNAME plugin for Nagios monitors sysctl hw.sensors on OpenBSD
1.15 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.17 andrew 492: Don't 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
496: ID of a single sensor. "-s 0" means hw.sensors.0.
497: -w, --warning=RANGE or single ENTRY
498: Exit with WARNING status if outside of RANGE or if != ENTRY
1.13 andrew 499: -c, --critical=RANGE or single ENTRY
1.12 andrew 500: Exit with CRITICAL status if outside of RANGE or if != ENTRY
501:
1.13 andrew 502: FILE is in the same format as sensorsd.conf(5) plus some additional
503: entries. These additional entries in the file are ignored by
504: sensorsd(8).
1.12 andrew 505:
506: $PROGNAME understands the following entries:
507:
1.15 andrew 508: low, high, crit, warn, crit.low, crit.high, warn.low, warn.high,
509: ignore, status
1.12 andrew 510:
511: An ENTRY depends on the type. The descriptions in sensorsd.conf(5)
512: can be used when appropriate, or you can use the following:
513:
514: volts_dc, fanrpm or raw - Anything that includes digits.
515: Both the value of the check and the value of the sensor
516: response that are not either a digit or period are stripped
517: and then the two resultant values are compared.
518:
519: temp - Can be as above, but if the entry has an F in it,
520: it compares farenheit, otherwise it uses celcius.
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
524: any of the entries, it matches the status.
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
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.18 ! andrew 545: print_revision($PROGNAME, '$Revision: 1.17 $');
1.2 andrew 546: }
547:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>