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