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