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