#!/usr/local/bin/perl # # Check a filesystem via snmp and the host resources mib # # $Header: /opt/home/doke/work/nagios/RCS/check_hrStorage,v 1.7 2015/03/13 19:47:42 doke Exp $ use strict; use warnings; no warnings 'redefine'; no warnings 'uninitialized'; use Getopt::Long; #use Data::Dumper; use UD::SNMP qw( $mib2 ); use vars qw( $host $community $filesystem $crit_threshold $warn_threshold $pattern_match $verbose $help @crits @warns @unknowns @oks $rc $sep @perfdata $uds $snmp_port ); $host = ''; $community = 'public'; $filesystem = '/'; $crit_threshold = '10%'; $warn_threshold = '20%'; $pattern_match = 0; $snmp_port = 161; $verbose = 0; $help = 0; sub usage { my( $rc ) = @_; print "Usage: $0 [-vh] -H -C -n -H s hostname [$host] -C s snmp community [$community] -f s filesystem mount point [$filesystem] -c n critical at n free [$crit_threshold] -w n warn at n free [$warn_threshold] The critical and warning thresholds must be a number then a unit K for kilobytes, M for megabytes, G for gigabytes, T for terrabytes, or '%' for percent, ie '50M' or '10%'. -p the filesystem name is a case insensitive perl regexp pattern Useful for windows drive letters with serial numbers. -P n snmp port [$snmp_port] -v verbose -h help "; exit $rc; } Getopt::Long::Configure ("bundling"); GetOptions( 'H=s' => \$host, 'C=s' => \$community, 'f=s' => \$filesystem, 'c=s' => \$crit_threshold, 'w=s' => \$warn_threshold, 'p' => \$pattern_match, 'P=i' => \$snmp_port, 'v+' => \$verbose, 'h' => \$help, ); &usage( 0 ) if ( $help ); sub alarm_handler { warn "alarm timeout in pid $$\n"; exit 3 } $SIG{ALRM} = \&alarm_handler; alarm( 30 ); # make sure we don't hang nagios if ( ! $host ) { print "no host specified\n"; usage( 3 ); } if ( ! $community ) { print "no community specified\n"; usage( 3 ); } if ( ! $snmp_port ) { print "invalid snmp port\n"; usage( 3 ); } if ( ! $filesystem ) { print "no filesystem specified\n"; usage( 3 ); } # Try to sanity check the warn and crit levels. # If one is a percentage, and the other is an absolute, then # we won't know if they're wrong until we get the size of the filesystem # from snmp. my ( $crit_level, $crit_is_percent ) = parse_threshold( $crit_threshold ); my ( $warn_level, $warn_is_percent ) = parse_threshold( $warn_threshold ); if ( $crit_is_percent == $warn_is_percent && $crit_level >= $warn_level ) { print "crit level must be less than warn level\n"; usage( 3 ); } check_filesystem_snmp(); $rc = 0; $sep = ''; if ( $#crits >= 0 ) { $rc = 2; print "CRITICAL ", join( ", ", @crits ); $sep = '; '; } if ( $#warns >= 0 ) { $rc = 1 if ( $rc == 0 ); print $sep, "Warning ", join( ", ", @warns ); $sep = '; '; } if ( $#unknowns >= 0 ) { $rc = 3 if ( $rc == 0 ); print $sep, "Unknown ", join( ", ", @unknowns ); $sep = '; '; } if ( $rc == 0 || $verbose ) { print $sep, "Ok ", join( ", ", @oks ); } print( " | ", join( ", ", @perfdata ) ) if ( $#perfdata >= 0 ); print "\n"; exit $rc; ################## sub check_filesystem_snmp { my( $data, $row, $found, $descr, $size, $used, $free, $percent_free, $percent_used, $alloc, $level, $is_percent, $msg ); $uds = UD::SNMP->new( verbose => $verbose, use_snmpv2c => 1, # use snmp version 2c instead of 1 timeout => 10, # seconds snmp_port => $snmp_port, ); # hrStorageTable .1.3.6.1.2.1.25.2.3 # hrStorageIndex(1) # hrStorageType(2) # hrStorageDescr(3) # hrStorageAllocationUnits(4) # hrStorageSize(5) # hrStorageUsed(6) $data = $uds->snmp_table( $host, $community, 'hrStorageTable', "$mib2.25.2.3", [ 3 .. 6 ] ); if ( ! $data || scalar( @$data ) < 1 ) { push @unknowns, "can't get hrStorageTable: " . ( $uds->{ error } ) ? $uds->{ error } : 'snmp failed'; return; } #$verbose && print "data: ", Dumper( $data ), "\n"; $verbose && printf "%-3s %6s %8s %8s %8s %4s%% %s\n", 'row', 'alloc', 'size MB', 'used MB', 'free MB', 'used', 'descr'; $found = 0; foreach $row ( 1 .. scalar( @$data ) ) { next unless ( defined $data->[ $row ] ); $descr = $data->[ $row ][ 3 ]; $alloc = $data->[ $row ][ 4 ] / 1048576; # convert to MB, so we don't roll over 32 bits $size = $data->[ $row ][ 5 ] * $alloc; $used = $data->[ $row ][ 6 ] * $alloc; $free = $size - $used; $percent_used = ( $size ) ? $used * 100 / $size : 0; $percent_free = ( $size ) ? $free * 100 / $size : 0; $verbose && printf "%-3d %6d %8u %8u %8u %4.0f%% %s\n", $row, $data->[ $row ][ 4 ], $size, $used, $free, $percent_used, $descr; next unless ( $descr eq $filesystem || ( $pattern_match && $descr =~ m/$filesystem/i ) ); $verbose && print "found $filesystem, free $free MB, percent_free $percent_free %\n"; $found = 1; push @perfdata, sprintf "size=%0.2f", $size; push @perfdata, sprintf "used=%0.2f", $used; push @perfdata, sprintf "free=%0.2f", $free; push @perfdata, sprintf "percent_free=%0.2f", $percent_free; $msg = sprintf "%s is %0.0f%% free - %lu MegaBytes free", $descr, $percent_free, $free; ( $level, $is_percent ) = parse_threshold( $crit_threshold ); if ( ( $is_percent && $percent_free < $level ) || ( ! $is_percent && $free < $level ) ) { push @crits, $msg; next; } ( $level, $is_percent ) = parse_threshold( $warn_threshold ); if ( ( $is_percent && $percent_free < $level ) || ( ! $is_percent && $free < $level ) ) { push @warns, $msg; next } push @oks, $msg; } if ( ! $found ) { push @unknowns, "didn't find filesystem '$filesystem'"; } } sub parse_threshold { my( $threshold ) = @_; my( $level, $unit, $is_percent ); if ( $threshold !~ m/^(\d+)([KMGT%])$/i ) { print "can't parse threshold '$threshold'\n"; usage( 3 ); } $level = $1; $unit = uc $2; $is_percent = 0; if ( $unit eq '%' ) { # percent if ( $level < 0 || 100 < $level ) { print "percentages must be between 0 and 100, inclusive\n"; usage( 3 ); } $is_percent = 1; } elsif ( $unit eq 'K' ) { # kilobytes $level /= 1024; # convert to Megabytes, so we don't roll over 32 bits } elsif ( $unit eq 'M' ) { # megabytes, stays the same } elsif ( $unit eq 'G' ) { # megabytes $level *= 1024; } elsif ( $unit eq 'T' ) { # megabytes $level *= 1048576; } $verbose && print "threshold $threshold -> level $level MB, is_percent $is_percent\n"; return ( $level, $is_percent ); }