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