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