Annotation of nagios/check_hw_sensors/check_hw_sensors, Revision 1.7
1.2 andrew 1: #!/usr/bin/perl
1.7 ! andrew 2: # $RedRiver: check_hw_sensors,v 1.6 2006/05/02 19:59:47 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.7 ! andrew 68: print_revision($PROGNAME,'$Revision: 1.6 $ ');
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.5 andrew 151: my $have_results = 0;
152: foreach my $error (sort { $ERRORS{$a} <=> $ERRORS{$b} } keys %ERRORS) {
153: if (exists $states{$error}) {
154: $have_results++;
155: $state = $error;
156: }
157: }
158: if (NAGIOS_OUTPUT && $have_results) {
1.4 andrew 159: print '<ul>';
160: }
161: foreach my $error (sort { $ERRORS{$b} <=> $ERRORS{$a} } keys %ERRORS) {
162: if (exists $states{$error}) {
163: if (NAGIOS_OUTPUT) {
1.6 andrew 164: print "<li>$error (" . scalar(@{ $states{ $error } }) . ")";
1.5 andrew 165: unless ($error eq 'OK') {
1.6 andrew 166: print '<ul>';
1.5 andrew 167: foreach (@{ $states{ $error } }) {
168: print "<li>$_</li>";
169: }
1.6 andrew 170: print '</ul>';
1.4 andrew 171: }
1.6 andrew 172: print "</li>"
1.4 andrew 173: } else {
174: print "$error (" . scalar(@{ $states{ $error } }) . "):\n";
175: foreach (@{ $states{ $error } }) {
176: print " $_\n";
177: }
178: }
179: }
180: }
1.5 andrew 181: if ($have_results == 0) {
182: print "No results found\n";
183: }
184: if (NAGIOS_OUTPUT && $have_results) {
1.4 andrew 185: print '</ul>' . "\n";
186: }
1.2 andrew 187: exit $ERRORS{$state};
188:
189:
190: sub parse_file {
191: my $filename = shift;
192: my %contents;
193:
194: open my $fh, '-|', $GETCAP, '-a', '-f', $filename
195: or die "Couldn't open FILE '$GETCAP -a -f $filename': $!";
196: while (<$fh>) {
197: chomp;
198: my ($key, @c) = split /\:/;
199: foreach (@c) {
200: my ($k, $v) = split /\=/;
201: $contents{$key}{$k} = $v;
202: }
203: }
204: close $fh;
205:
206: return %contents;
207: }
208:
209: sub parse_check {
210: my $type = shift;
211: my $check = shift;
212:
213: if (defined $check->{'warn'} && $check->{'warn'} =~ /:/) {
214: if (my ($low, $high) = split /:/, $check->{'warn'}) {
215: $check->{'warn.low'} = $low;
216: $check->{'warn.high'} = $high;
217: }
218: delete $check->{'warn'};
219: }
220: if (defined $check->{'crit'} && $check->{'crit'} =~ /:/) {
221: if (my ($low, $high) = split /:/, $check->{'crit'}) {
222: $check->{'crit.low'} = $low;
223: $check->{'crit.high'} = $high;
224: }
225: delete $check->{'crit'};
226: }
227:
228: if (defined $check->{'low'}) {
229: $check->{'warn.low'} = $check->{'low'}
230: unless defined $check->{'warn.low'};
231: $check->{'crit.low'} = $check->{'low'}
232: unless defined $check->{'crit.low'};
233: }
234: if (defined $check->{'high'}) {
235: $check->{'warn.high'} = $check->{'high'}
236: unless defined $check->{'warn.high'};
237: $check->{'crit.high'} = $check->{'high'}
238: unless defined $check->{'crit.high'};
239: }
240:
241: no warnings 'uninitialized';
242: $check->{'warn'} = [ split /,\s*/, $check->{'warn'} ];
243: $check->{'crit'} = [ split /,\s*/, $check->{'crit'} ];
244:
245: return $check;
246: }
247:
248: sub check_sensor {
249: my $check = shift;
250: my $sensor = shift;
251: my $result = 'UNKNOWN';
252: my %errors = (
253: 'warn' => 'WARNING',
254: 'crit' => 'CRITICAL',
255: );
256:
257: return $result unless ref $sensor eq 'HASH';
258:
259: $check = parse_check($sensor->{'type'}, $check);
260: #print Dumper $check, $sensor;
261:
262: $result = 'OK';
263:
264: foreach my $code ('warn', 'crit') {
265: if (
266: $sensor->{'type'} eq 'volts_dc' ||
267: $sensor->{'type'} eq 'fanrpm' ||
268: $sensor->{'type'} eq 'raw'
269: ) {
270: my $data = $sensor->{'data'};
271: $data =~ s/[^\d\.]//g;
272: unless (length $data) {
273: warn "INVALID DATA ($sensor->{'data'}) for '$sensor->{'id'}'";
274: next;
275: }
276:
277: if (
278: defined $check->{$code . ".low"} ||
279: defined $check->{$code . ".high"}
280: ) {
281: if (defined $check->{$code . ".low"}) {
282: my $c = $check->{$code . ".low"};
283: $c =~ s/[^\d\.]//g;
284:
285: unless (length $c) {
286: warn "INVALID CHECK (" . $check->{$code . ".low"} .
287: ") for '$sensor->{'id'}:$code.low'";
288: next;
289: }
290:
291: $result = $errors{$code}
292: if ($c >= $data);
293: }
294: if (defined $check->{$code . ".high"}) {
295: my $c = $check->{$code . ".high"};
296: $c =~ s/[^\d\.]//g;
297: unless (length $c) {
298: warn "INVALID CHECK (" . $check->{$code . ".high"} .
299: ") for '$sensor->{'id'}:$code.high'";
300: next;
301: }
302:
303: $result = $errors{$code}
304: if ($c <= $data);
305: }
306: } elsif (defined $check->{$code}) {
307: my $matched = 0;
308: foreach my $c (@{ $check->{$code} }) {
309: $c =~ s/[^\d\.]//g;
310: unless (length $c) {
1.7 ! andrew 311: warn "INVALID CHECK (" . $c .
1.2 andrew 312: ") for '$sensor->{'id'}:$code'";
313: next;
314: }
315:
316: if ($_ eq $data) {
317: $matched = 1;
318: last;
319: }
320: }
321: $result = $errors{$code} unless $matched;
322: }
323:
324: } elsif ($sensor->{'type'} eq 'temp') {
325: my ($degC, $degF) = split /\//, $sensor->{'data'};
326: $degC =~ s/[^\d\.]//g;
327: $degF =~ s/[^\d\.]//g;
328: if (
329: defined $check->{$code . ".low"} ||
330: defined $check->{$code . ".high"}
331: ) {
332: if (defined $check->{$code . ".low"}) {
333: my $c = $check->{$code . ".low"};
334: my $data = $degC;
335:
336: $data = $degF if ($c =~ /F/i);
337: unless (length $data) {
338: warn "INVALID DATA (" . $sensor->{'data'} .
339: ") for '$sensor->{'id'}'";
340: next;
341: }
342:
343: $c =~ s/[^\d\.]//g;
344: unless (length $c) {
345: warn "INVALID CHECK (" . $check->{$code . ".low"} .
346: ") for '$sensor->{'id'}':$code.low";
347: next;
348: }
349:
350: $result = $errors{$code}
351: if ($c >= $data);
352: }
353: if (defined $check->{$code . ".high"}) {
354: my $c = $check->{$code . ".high"};
355:
356: my $data = $degC;
357: $data = $degF if ($c =~ /F/i);
358: unless (length $data) {
359: warn "INVALID DATA (" . $sensor->{'data'} .
360: ") for '$sensor->{'id'}'";
361: next;
362: }
363:
364: $c =~ s/[^\d\.]//g;
365: unless (length $c) {
366: warn "INVALID CHECK (" . $check->{$code . ".high"} .
367: ") for '$sensor->{'id'}:$code.high'";
368: next;
369: }
370:
371: $result = $errors{$code}
372: if ($c <= $data);
373: }
374: } elsif (defined $check->{$code}) {
375:
376: my $matched = 0;
377: foreach my $c (@{ $check->{$code} }) {
378: my $data = $degC;
379:
380: $data = $degF if ($c =~ /F/i);
381: unless (length $data) {
382: warn "INVALID DATA (" . $sensor->{'data'} .
383: ") for '$sensor->{'id'}'";
384: next;
385: }
386:
387: $c =~ s/[^\d\.]//g;
388: unless (length $c) {
1.7 ! andrew 389: warn "INVALID CHECK (" . $c .
1.2 andrew 390: ") for '$sensor->{'id'}':$code";
391: next;
392: }
393:
394: if ($_ eq $data) {
395: $matched = 1;
396: last;
397: }
398: }
399: $result = $errors{$code} unless $matched;
400: }
401:
402: } elsif (
403: $sensor->{'type'} eq 'drive' ||
404: $sensor->{'type'} eq 'indicator'
405: ) {
406: if (defined $check->{$code}) {
407: my $matched = 0;
408: foreach (@{ $check->{$code} }) {
409: if ($_ eq $sensor->{'data'}) {
410: $matched = 1;
411: last;
412: }
413: }
414: $result = $errors{$code} unless $matched;
415: }
416:
417: } else {
418: $result = 'UNKNOWN';
419: }
420:
421: }
422:
423: return $result;
424: }
425:
426: sub print_help {
427: print <<EOL;
428: $PROGNAME plugin for Nagios monitors sysctl hw.sensors on OpenBSD
429: $PROGNAME (-f [<FILENAME>]|(-s <hw.sensors id> -w limit -c limit))
430:
431: Usage:
432: -f, --filename=FILE
433: FILE to load checks from (defaults to /etc/sensorsd.conf)
434: -s, --sensor=ID
435: ID of a single sensor. "-s 0" means hw.sensors.0.
436: -w, --warning=RANGE or single ENTRY
437: Exit with WARNING status if outside of RANGE or if != ENTRY
438: -c, --critical=INTEGER
439: Exit with CRITICAL status if outside of RANGE or if != ENTRY
440:
441: -h (--help) usage help
442:
443: 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:
444: low, high, crit, warn, crit.low, crit.high, warn.low, warn.high
445:
446: An ENTRY depends on the type. The descriptions in sensorsd.conf(5) can be used when appropriate, or you can use the following:
447: 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.
448: temp - Can be as above, but if the entry has an F in it, it compares farenheit, otherwise it uses celcius.
449: 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.
450:
451: 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.
452:
453: A RANGE is a low ENTRY and a high ENTRY separated by a colon (:).
454:
455: EOL
456:
1.7 ! andrew 457: print_revision($PROGNAME, '$Revision: 1.6 $');
1.2 andrew 458: }
459:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>