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