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