Annotation of nagios/check_hw_sensors/check_hw_sensors, Revision 1.15
1.14 andrew 1: #!/usr/bin/perl -T
1.15 ! andrew 2: # $RedRiver: check_hw_sensors,v 1.14 2006/05/04 01:30:29 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.15 ! andrew 16: use constant NAGIOS_OUTPUT => 0;
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: );
55:
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:
59: if ($status == 0) {
60: print_help() ;
61: exit $ERRORS{'OK'};
62: }
63:
64: if ($opt_V) {
1.15 ! andrew 65: print_revision($PROGNAME,'$Revision: 1.14 $ ');
1.2 andrew 66: exit $ERRORS{'OK'};
67: }
68:
1.15 ! andrew 69: unless (
! 70: (
1.10 andrew 71: defined $filename ||
1.15 ! andrew 72: (not defined $ignore_status) ||
1.10 andrew 73: (defined $sensor && ($warning || $critical))
74: ) &&
75: ( (!defined $filename) || (!defined $sensor) )
1.2 andrew 76: ) {
77: print_help();
78: exit $ERRORS{'OK'};
79: }
80:
81:
82: if (defined $sensor) {
83: if ($sensor !~ /^$BASE/) {
84: $sensor = $BASE . '.' . $sensor;
85: }
86: $CHECK_SENSOR = $sensor;
87:
1.15 ! andrew 88: $CHECKS{$sensor}{'warn'} = $warning if defined $warning;
! 89: $CHECKS{$sensor}{'crit'} = $critical if defined $critical;
! 90:
1.2 andrew 91: } elsif (defined $filename) {
92: %CHECKS = parse_file($filename);
93: }
94:
95: open my $sysctl, "-|", $SYSCTL, $CHECK_SENSOR
96: or die "Couldn't open sysctl: $!";
97: while (<$sysctl>) {
98: #while (<>) {
99: chomp;
100: my ($id, $output) = split /=/;
101: my @o = split /,\s*/, $output;
102:
103: my $source = $o[0];
104: my $descr = $o[1];
105: my $status = $o[2] if @o == 5;
106: my $type = $o[-2];
107: my $data = $o[-1];
108:
109: $SENSORS{$id} = {
110: id => $id,
111: output => $output,
112: source => $source,
113: description => $descr,
114: status => $status,
115: type => $type,
116: data => $data,
117: };
118:
119: }
120: close $sysctl;
121:
122: sub as_if_numeric {
123: my $_a = $a;
124: my $_b = $b;
125: $_a =~ s/\D//g;
126: $_b =~ s/\D//g;
127: $_a <=> $_b;
128: }
129:
1.15 ! andrew 130: foreach my $s (sort as_if_numeric keys %SENSORS) {
! 131: my ($r, $data);
! 132: if (exists $CHECKS{$s}) {
! 133: $r = check_sensor($SENSORS{$s}, $CHECKS{$s});
! 134: $data = $s . '=' . $SENSORS{$s}{'output'};
! 135: } elsif (not $ignore_status) {
! 136: $r = check_sensor($SENSORS{$s});
! 137: $data = $s . '=' . $SENSORS{$s}{'output'};
1.2 andrew 138: } else {
1.15 ! andrew 139: # ignore this sensor
1.2 andrew 140: }
1.15 ! andrew 141: next unless defined $r;
! 142: push @{ $states{ $r } }, $data;
1.2 andrew 143: }
144:
145: $state = 'OK';
1.5 andrew 146: my $have_results = 0;
147: foreach my $error (sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS) {
148: if (exists $states{$error}) {
149: $have_results++;
150: $state = $error;
151: }
152: }
1.4 andrew 153: foreach my $error (sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS) {
154: if (exists $states{$error}) {
155: if (NAGIOS_OUTPUT) {
1.8 andrew 156: print "$error (" . scalar(@{ $states{ $error } }) . ")";
1.5 andrew 157: unless ($error eq 'OK') {
1.9 andrew 158: print '<br>';
1.15 ! andrew 159: print map {" - $_<br>"} @{ $states{ $error } };
1.4 andrew 160: }
161: } else {
162: print "$error (" . scalar(@{ $states{ $error } }) . "):\n";
163: foreach (@{ $states{ $error } }) {
164: print " $_\n";
165: }
166: }
167: }
168: }
1.5 andrew 169: if ($have_results == 0) {
170: print "No results found\n";
171: }
1.2 andrew 172: exit $ERRORS{$state};
173:
174:
175: sub parse_file {
176: my $filename = shift;
177: my %contents;
1.15 ! andrew 178:
! 179: die "file '$filename' does not exist." unless -e $filename;
1.2 andrew 180:
181: open my $fh, '-|', $GETCAP, '-a', '-f', $filename
182: or die "Couldn't open FILE '$GETCAP -a -f $filename': $!";
1.15 ! andrew 183: LINE: while (<$fh>) {
1.2 andrew 184: chomp;
185: my ($key, @c) = split /\:/;
186: foreach (@c) {
187: my ($k, $v) = split /\=/;
1.15 ! andrew 188: if (lc($k) eq 'ignore') {
! 189: $contents{$key}{'IGNORE'} = 1;
! 190: } elsif (lc($k) eq 'status') {
! 191: $contents{$key}{'STATUS'} = 1;
! 192: } else {
! 193: $contents{$key}{$k} = $v;
! 194: }
1.2 andrew 195: }
196: }
197: close $fh;
198:
199: return %contents;
200: }
201:
202: sub parse_check {
203: my $type = shift;
204: my $check = shift;
205:
1.15 ! andrew 206: return undef unless $check;
! 207: return undef if $check->{'STATUS'};
! 208: return 'IGNORE' if $check->{'IGNORE'};
! 209:
1.10 andrew 210: foreach my $code ('crit', 'warn') {
211: if (defined $check->{$code} && $check->{$code} =~ /:/) {
212: if (my ($low, $high) = split /:/, $check->{$code}) {
213: $check->{$code . '.low'} = $low;
214: $check->{$code . '.high'} = $high;
215: }
216: delete $check->{$code};
1.2 andrew 217: }
1.10 andrew 218:
219: foreach my $severity ('low', 'high') {
220: if (defined $check->{$severity}) {
221: $check->{ $code . '.' . $severity } = $check->{$severity}
222: unless defined $check->{ $code . '.' . $severity };
223: }
1.2 andrew 224: }
1.10 andrew 225: no warnings 'uninitialized';
226: $check->{$code} = [ split /,\s*/, $check->{$code} ];
1.2 andrew 227: }
228:
229: return $check;
230: }
231:
232: sub check_sensor {
1.15 ! andrew 233: my $sensor = shift;
1.2 andrew 234: my $check = shift;
235: my $result = 'UNKNOWN';
236: my %errors = (
237: 'warn' => 'WARNING',
238: 'crit' => 'CRITICAL',
239: );
240:
241: return $result unless ref $sensor eq 'HASH';
1.15 ! andrew 242: $check = parse_check($sensor->{'type'}, $check) if $check;
! 243:
! 244: unless ($check) {
! 245: if ($sensor->{'status'}) {
! 246: # It looks like doing this should be safe, from
! 247: # src/sbin/sysctl/sysctl.c
! 248: $result = $sensor->{'status'}
! 249: } else {
! 250: return undef;
! 251: }
! 252: return $result;
! 253: }
1.2 andrew 254:
1.15 ! andrew 255: return undef if $check eq 'IGNORE';
1.2 andrew 256:
257: $result = 'OK';
258: foreach my $code ('warn', 'crit') {
259: if (
260: $sensor->{'type'} eq 'volts_dc' ||
261: $sensor->{'type'} eq 'fanrpm' ||
262: $sensor->{'type'} eq 'raw'
263: ) {
264: my $data = $sensor->{'data'};
265: $data =~ s/[^\d\.]//g;
266: unless (length $data) {
267: warn "INVALID DATA ($sensor->{'data'}) for '$sensor->{'id'}'";
268: next;
269: }
270:
271: if (
272: defined $check->{$code . ".low"} ||
273: defined $check->{$code . ".high"}
274: ) {
275: if (defined $check->{$code . ".low"}) {
276: my $c = $check->{$code . ".low"};
277: $c =~ s/[^\d\.]//g;
278:
279: unless (length $c) {
280: warn "INVALID CHECK (" . $check->{$code . ".low"} .
281: ") for '$sensor->{'id'}:$code.low'";
282: next;
283: }
284:
285: $result = $errors{$code}
286: if ($c >= $data);
287: }
288: if (defined $check->{$code . ".high"}) {
289: my $c = $check->{$code . ".high"};
290: $c =~ s/[^\d\.]//g;
291: unless (length $c) {
292: warn "INVALID CHECK (" . $check->{$code . ".high"} .
293: ") for '$sensor->{'id'}:$code.high'";
294: next;
295: }
296:
297: $result = $errors{$code}
298: if ($c <= $data);
299: }
1.9 andrew 300: } elsif (@{ $check->{$code} }) {
1.2 andrew 301: my $matched = 0;
302: foreach my $c (@{ $check->{$code} }) {
303: $c =~ s/[^\d\.]//g;
304: unless (length $c) {
1.7 andrew 305: warn "INVALID CHECK (" . $c .
1.2 andrew 306: ") for '$sensor->{'id'}:$code'";
307: next;
308: }
309:
1.15 ! andrew 310: if ($c eq $data) {
1.2 andrew 311: $matched = 1;
312: last;
313: }
314: }
315: $result = $errors{$code} unless $matched;
316: }
317:
318: } elsif ($sensor->{'type'} eq 'temp') {
319: my ($degC, $degF) = split /\//, $sensor->{'data'};
320: $degC =~ s/[^\d\.]//g;
321: $degF =~ s/[^\d\.]//g;
322: if (
323: defined $check->{$code . ".low"} ||
324: defined $check->{$code . ".high"}
325: ) {
326: if (defined $check->{$code . ".low"}) {
327: my $c = $check->{$code . ".low"};
328: my $data = $degC;
329:
330: $data = $degF if ($c =~ /F/i);
331: unless (length $data) {
332: warn "INVALID DATA (" . $sensor->{'data'} .
333: ") for '$sensor->{'id'}'";
334: next;
335: }
336:
337: $c =~ s/[^\d\.]//g;
338: unless (length $c) {
339: warn "INVALID CHECK (" . $check->{$code . ".low"} .
340: ") for '$sensor->{'id'}':$code.low";
341: next;
342: }
343:
344: $result = $errors{$code}
345: if ($c >= $data);
346: }
347: if (defined $check->{$code . ".high"}) {
348: my $c = $check->{$code . ".high"};
349:
350: my $data = $degC;
351: $data = $degF if ($c =~ /F/i);
352: unless (length $data) {
353: warn "INVALID DATA (" . $sensor->{'data'} .
354: ") for '$sensor->{'id'}'";
355: next;
356: }
357:
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:
370: my $matched = 0;
371: foreach my $c (@{ $check->{$code} }) {
372: my $data = $degC;
373:
374: $data = $degF if ($c =~ /F/i);
375: unless (length $data) {
376: warn "INVALID DATA (" . $sensor->{'data'} .
377: ") for '$sensor->{'id'}'";
378: next;
379: }
380:
381: $c =~ s/[^\d\.]//g;
382: unless (length $c) {
1.7 andrew 383: warn "INVALID CHECK (" . $c .
1.2 andrew 384: ") for '$sensor->{'id'}':$code";
385: next;
386: }
387:
1.15 ! andrew 388: if ($c eq $data) {
1.2 andrew 389: $matched = 1;
390: last;
391: }
392: }
393: $result = $errors{$code} unless $matched;
394: }
395:
396: } elsif (
397: $sensor->{'type'} eq 'drive' ||
398: $sensor->{'type'} eq 'indicator'
399: ) {
1.9 andrew 400: if (@{ $check->{$code} }) {
1.2 andrew 401: my $matched = 0;
402: foreach (@{ $check->{$code} }) {
403: if ($_ eq $sensor->{'data'}) {
404: $matched = 1;
405: last;
406: }
407: }
408: $result = $errors{$code} unless $matched;
409: }
410:
411: } else {
412: $result = 'UNKNOWN';
413: }
414:
415: }
416:
417: return $result;
418: }
419:
420: sub print_help {
421: print <<EOL;
422: $PROGNAME plugin for Nagios monitors sysctl hw.sensors on OpenBSD
1.15 ! andrew 423: $PROGNAME [-i] (-f [<FILENAME>]|(-s <hw.sensors id> [-w limit] [-c limit]))
1.2 andrew 424:
425: Usage:
1.15 ! andrew 426: -i, --ignore-status
! 427: Whether to check the "status" of the sensors that report it.
! 428: Normally enabled, pass this
1.12 andrew 429: -f, --filename=FILE
430: FILE to load checks from (defaults to /etc/sensorsd.conf)
431: -s, --sensor=ID
432: ID of a single sensor. "-s 0" means hw.sensors.0.
433: -w, --warning=RANGE or single ENTRY
434: Exit with WARNING status if outside of RANGE or if != ENTRY
1.13 andrew 435: -c, --critical=RANGE or single ENTRY
1.12 andrew 436: Exit with CRITICAL status if outside of RANGE or if != ENTRY
437:
438: -h (--help) usage help
439:
1.13 andrew 440: FILE is in the same format as sensorsd.conf(5) plus some additional
441: entries. These additional entries in the file are ignored by
442: sensorsd(8).
1.12 andrew 443:
444: $PROGNAME understands the following entries:
445:
1.15 ! andrew 446: low, high, crit, warn, crit.low, crit.high, warn.low, warn.high,
! 447: ignore, status
1.12 andrew 448:
449: An ENTRY depends on the type. The descriptions in sensorsd.conf(5)
450: can be used when appropriate, or you can use the following:
451:
452: volts_dc, fanrpm or raw - Anything that includes digits.
453: Both the value of the check and the value of the sensor
454: response that are not either a digit or period are stripped
455: and then the two resultant values are compared.
456:
457: temp - Can be as above, but if the entry has an F in it,
458: it compares farenheit, otherwise it uses celcius.
459:
460: indicator or drive - does a case sensitive match of each
461: entry in the comma separated list and if it does not match
462: any of the entries, it matches the status.
463:
464: The entries 'crit' or 'warn' (or the -c or -w on the command line)
465: may be a RANGE or a comma separated list of acceptable values.
466: The comma separated list of values contains a list of things that
467: will NOT cause the status. This is possibly counterintuitive, but
468: you are more likely to know good values than bad values.
469:
470: A RANGE is a low ENTRY and a high ENTRY separated by a colon (:).
471: It can also be low: or :high with the other side left blank to only
472: make the single check..
1.2 andrew 473:
1.15 ! andrew 474: An entry marked "ignore" will cause that sensor to be skipped.
! 475: Generally used with state checking of all sensors to ignore sensors you
! 476: don't care about or that report incorrectly.
! 477:
! 478: If you are using --ignore-status, you can still check the status of
! 479: individual sensors with a status entry.
! 480:
1.2 andrew 481: EOL
1.15 ! andrew 482:
! 483: print_revision($PROGNAME, '$Revision: 1.14 $');
1.2 andrew 484: }
485:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>