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