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