#!/usr/local/bin/perl # nagios: +epn =pod =head1 NAME check_snmp_hr_storage.pl - Check storage devices using the HOST-RESOURCES-MIB =head1 SYNOPSIS Check partition utilization on a device by percent space used or minimum free space available; you can filter partitions based on mount point (-P), file system type (-T), or storage device type (-F). In addition to thresholds, script will output performance data, percent used if -U % is selected, free bytes if -U [KMG] is selected. Examples: * By percent, SNMP v3, all partitions. Warn at 90% used, critical at 95% used. ./check_snmp_hr_storage.pl --hostname 192.168.3.1 --snmp-version 3 --auth-username my_user --auth-password my_pass -w 90 -c 95 -U % -P all SNMP-HR-STORAGE OK - /backup = 88.38%, / = 82.82%, /home2 = 36.93%, /tmp = 1.77%, /boot = 14.72%, /home3 = 10.97% | '/backup'=88.38%;90;95;0;100 '/'=82.82%;90;95;0;100 '/home2'=36.93%;90;95;0;100 '/tmp'=1.77%;90;95;0;100 '/boot'=14.72%;90;95;0;100 '/home3'=10.97%;90;95;0;100 * By minimum space free, SNMP v2c, /tmp and /boot, in Megabytes. Warn at 50M left available, critical at 20M available ./check_snmp_hr_storage.pl --hostname 192.168.3.1 --snmp-version 2c --rocommunity -w 50 -c 20 -U M -P /tmp -P /boot SNMP-HR-STORAGE OK - /tmp - 932.82M, /boot - 84.19M | '/tmp'=978128896;52428800;20971520;0;995774464 '/boot'=88274944;52428800;20971520;0;103515136 =cut sub check_snmp_hr_storage { use strict; use FindBin; use lib "$FindBin::Bin/lib"; use Nagios::Plugin::SNMP; use Nagios::Plugin::Threshold; my $USAGE = <new( 'shortname' => $LABEL, 'usage' => $USAGE ); $plugin->add_arg( 'spec' => 'partition|P=s@', 'help' => "-P, --partition NAME\n" . " Partition(s) to check, use switch multiple times\n" . " to specify multiple partitions or use 'all' to \n" . " check all partitions [required]", 'required' => 1 ); $plugin->add_arg( 'spec' => 'fs-type-expr|F=s', 'help' => "-F, --fs-type-expr\n" . " String or regular expression to use to determine\n" . " which file systems to include in disk checks.\n" . " Use fs-type-opt to select the operator to use\n" . " for the check. [optional, default 'any']", 'default' => 'any' ); $plugin->add_arg( 'spec' => 'fs-type-op|O=s', 'help' => "-O, --fs-type-op\n" . " Operator to use when checking to see if a file\n" . " system should be included in checks this plugin\n" . " performs. Valid operators are 'eq', 'ne', '=~'\n" . " and '~!'. [optional, default '']", 'default' => '' ); $plugin->add_arg( 'spec' => 'storage-type|T=s', 'help' => "-T, --storage-type\n" . " Type of storage (e.g. 'hrStorageFixedDisk')\n" . " [optional, default 'any']", 'default' => 'any' ); $plugin->add_arg( 'spec' => 'unit-type|U=s', 'help' => "-U, --unit-type\n" . " type of unit to use for check:\n" . " (B: bytes, M: Megabytes, G: Gigabytes, %: percent)\n" . " If a byte measurement is specified, thresholds are\n" . " low bounds. If % is specified thresholds are high\n" . " bounds (e.g. use > 90%) [optional, default '%']", 'default' => '%' ); $plugin->add_arg( 'spec' => 'volatile-partition|X=s@', 'help' => "-X, --volatile-partition\n" . " Names of partitions that may not always be present\n" . " on the system when disks are checked; this will\n" . " cause the script to output 0s for perf_data for\n" . " any partition listed to keep trending data in good\n" . " shape.\n", 'required' => 0, 'default' => [] ); $plugin->add_arg( 'spec' => 'no-fsafc|N', 'help' => "-N, --no-fsafc\n" . " Do not check for storage allocation failures; \n" . " provided for use with buggy HOST-RESOURCES MIB\n" . " implementations.", 'required' => 1, 'default' => '0' ); $plugin->getopts; my $WARNING = $plugin->opts->get('warning'); my $CRITICAL = $plugin->opts->get('critical'); my $UNIT_TYPE = uc($plugin->opts->get('unit-type')); if ($UNIT_TYPE !~ m/^[%MKG]$/) { die <opts->get('fs-type-op') ne '') { my $expr = $plugin->opts->get('fs-type-expr'); my $op = $plugin->opts->get('fs-type-op'); if ($op !~ m/^(?:=~|!~|eq|ne)$/) { die <opts->get('fs-type-expr') ne 'any') { die "fs-type-op is required if fs-type-expr is provided!" if $plugin->opts->get('fs-type-op') eq ''; } my @partitions = @{$plugin->opts->partition}; my %wanted; # # Filter on mount point # my %mount_names = filter_on_mount_point($plugin, \%wanted, @partitions); # Add volatile partitions in if not present so trending data # stays constant for partitions not always present my $volatile_partitions = $plugin->opts->get('volatile-partition'); if (ref($volatile_partitions) eq 'ARRAY') { my @volatile = @$volatile_partitions; for my $vp (@volatile) { next if exists $mount_names{$vp}; $mount_names{$vp} = -1; } } # # Filter on FS type # filter_on_fs_type($plugin, \%wanted, \%mount_names, $plugin->opts->get('fs-type-op'), $plugin->opts->get('fs-type-expr')); # # Get storage indexes - table at 1.3.6.1.2.1.25.3.8.1.7 # { local $_; my @oids = map { ".1.3.6.1.2.1.25.3.8.1.7.$_"; } keys %wanted; my $results = $plugin->get(@oids); for my $sindex (keys %$results) { my $idx = ($sindex =~ m/.+\.(\d+)$/)[0]; debug("$wanted{$idx}->{'mount'} - IDX $idx - SIDX $sindex"); $wanted{$idx}->{'storage_index'} = $results->{$sindex}; } } # # Filter out based on storage type if user requested we filter # based on type. # # hrStorageType table at 1.3.6.1.2.1.25.2.3.1.2 # my $storage_type = $plugin->opts->get('storage-type'); filter_on_storage_type($plugin, \%wanted, \%mount_names, $storage_type); # # Get storage units, storage size, description, storage used, # and storage failures for each device we have left # add_hr_storage_metrics($plugin, \%wanted); # # Check for storage failures and utilization problems # dump_wanted(\%wanted) if ($plugin->opts->get('snmp-debug') == 1); my @ok; my @warn; my @crit; for my $name (sort keys %mount_names) { my $idx = $mount_names{$name}; # Volatile partition, just add dummy perfdata to keep trending # straight if ($idx == -1) { $plugin->add_perfdata( 'label' => "'${name}'", 'value' => 0, 'uom' => '', 'min' => 0, 'max' => 0, 'threshold' => make_threshold(0, 0) ); next; } debug("$name [$idx]: checking utilization"); my %part = %{$wanted{$idx}}; my $mount = $part{'mount'}; $mount_names{$mount} = 1; if ($plugin->opts->get('no-fsafc') == 0) { my $sf = $part{'storage_allocation_failures'}; if (($sf ne 'noSuchInstance') && ($sf > 0)) { push(@crit, "$mount - $sf storage allocation failures"); } } my $units = $part{'storage_allocation_units'}; my $size = $part{'storage_size'}; my $used = $part{'storage_used'}; # Only on Solaris does this happen with Net-SNMP. Thank you! if ($size == 0) { debug("$mount - agent returned 0 bytes as size. Is this " . "a Solaris zone or other VPS?"); next; } # If % was specified, we are checking utilization to see if # partition is more utilized than thresholds if ($UNIT_TYPE eq '%') { my $pct_used = sprintf("%.2f", ($used / $size) * 100); if ($pct_used >= $CRITICAL) { push(@crit, "$mount (${pct_used}% >= ${CRITICAL}%)"); } elsif ($pct_used >= $WARNING) { push(@warn, "$mount (${pct_used}% >= ${WARNING}%)"); } else { push(@ok, "$mount = ${pct_used}%"); } $plugin->add_perfdata( 'label' => "'${mount}'", 'value' => $pct_used, 'uom' => '%', 'min' => 0, 'max' => 100, 'threshold' => make_threshold($WARNING, $CRITICAL) ); } else { # Otherwise we are checking for space remaining my $warn_bytes = $WARNING; my $crit_bytes = $CRITICAL; my $size_bytes = $size * $units; my $used_bytes = $used * $units; my $free_bytes = $size_bytes - $used_bytes; my $free_label; if ($UNIT_TYPE eq 'K') { $warn_bytes *= 1024; $crit_bytes *= 1024; $free_label = $free_bytes / 1024; } elsif ($UNIT_TYPE eq 'M') { $warn_bytes *= (1024 ** 2); $crit_bytes *= (1024 ** 2); $free_label = $free_bytes / (1024 ** 2); } elsif ($UNIT_TYPE eq 'G') { $warn_bytes *= (1024 ** 3); $crit_bytes *= (1024 ** 3); $free_label = $free_bytes / (1024 ** 3); } if ($free_bytes <= $crit_bytes) { push(@crit, "$mount <= ${CRITICAL}${UNIT_TYPE} free"); } elsif ($free_bytes <= $warn_bytes) { push(@warn, "$mount <= ${WARNING}${UNIT_TYPE} free"); } else { my $free_label = sprintf("%.2f", $free_label); $free_label =~ s/\.00//; push(@ok, "$mount - $free_label$UNIT_TYPE"); } $plugin->add_perfdata( 'label' => "'${mount}'", 'value' => $free_bytes, 'uom' => '', 'min' => 0, 'max' => $size_bytes, 'threshold' => make_threshold($warn_bytes, $crit_bytes) ); } } my $level = OK; my $msg = ""; if (scalar(@crit) > 0) { $msg = join(', ', @crit); $level = CRITICAL; } if (scalar(@warn) > 0) { if ($level == CRITICAL) { $msg .= "; WARNING "; } else { $level = WARNING; } $msg .= join(', ', @warn); } if (scalar(@ok) > 0) { if ($level != OK) { $msg .= "; OK "; } $msg .= join(', ', @ok); } $plugin->nagios_exit($level, $msg); ######## # SUBS # ######## sub dump_wanted { my $wanted = shift; for my $idx (keys %$wanted) { warn "Index $idx\n"; my %info = %{$wanted->{$idx}}; for my $key (keys %info) { warn "- $key: $info{$key}\n"; } } } sub filter_on_mount_point { my $agent = shift; my $wanted = shift; my @partitions = @_; my $hr_fs_mount_point = '.1.3.6.1.2.1.25.3.8.1.2'; my $results = $agent->walk($hr_fs_mount_point); my %mounts = %{$results->{$hr_fs_mount_point}}; my %mount_names = (); for my $mount (keys %mounts) { my $index = $mount; $index =~ s/$hr_fs_mount_point\.//; if ($partitions[0] ne 'all') { next unless grep(/^$mounts{$mount}$/, @partitions); } $wanted->{$index} = {'mount' => $mounts{$mount}}; # Index mounts by mount name too (for volatile processing) $mount_names{$mounts{$mount}} = $index; } if (scalar(keys %$wanted) == 0) { die "No partitions found matching @partitions!"; } return %mount_names; } sub filter_on_fs_type { my $agent = shift; my $wanted = shift; my $mount_names = shift; my $fs_filter_op = shift; my $fs_filter_expr = shift; # Indexed at 1.3.6.1.2.1.25.3.9.X my @hr_fs_types = qw( StartOfTable hrFSOther hrFSUnknown hrFSBerkeleyFFS hrFSSys5FS hrFSFat hrFSHPFS hrFSHFS hrFSMFS hrFSNTFS hrFSVNode hrFSJournaled hrFSiso9660 hrFSRockRidge hrFSNFS hrFSNetware hrFSAFS hrFSDFS hrFSAppleshare hrFSRFS hrFSDGCFS hrFSBFS hrFSFAT32 hrFSLinuxExt2 ); # hrFSType Table .1.3.6.1.2.1.25.3.8.1.4 my @oids; { local $_; @oids = map { ".1.3.6.1.2.1.25.3.8.1.4.$_"; } keys %$wanted; } my $results = $agent->get(@oids); for my $type (keys %{$results}) { next if $type eq ''; my $idx = ($type =~ m/.+\.(\d+)$/)[0]; my $fsidx = ($results->{$type} =~ m/.+\.(\d+)$/)[0]; # Somehow did not get back a valid index! if ((! defined $fsidx) || ($fsidx eq '')) { delete $mount_names->{$wanted->{$idx}->{'mount'}}; delete $wanted->{$idx}; next; } my $fs = $hr_fs_types[$fsidx]; if (fs_type_check($fs, $fs_filter_op, $fs_filter_expr)) { # Add the FS type to the wanted entry $wanted->{$idx}->{'fstype'} = $fs; } else { # Not an FS type we are about, remove the entry delete $mount_names->{$wanted->{$idx}->{'mount'}}; delete $wanted->{$idx}; next; } } if (scalar(keys %$wanted) == 0) { die "No partitions found that $fs_filter_op $fs_filter_expr!"; } } sub filter_on_storage_type { my $agent = shift; my $wanted = shift; my $mount_names = shift; my $storage_type = shift; # Indexed at 1.3.6.1.2.1.25.2.1.X my @hr_storage_types = qw( StartOfTable hrStorageOther hrStorageRam hrStorageVirtualMemory hrStorageFixedDisk hrStorageRemovableDisk hrStorageFloppyDisk hrStorageCompactDisc hrStorageRamDisk ); # hrStorageType Table 1.3.6.1.2.1.25.2.3.1.2 my @oids; my %idx_storage_idx; for my $idx (keys %$wanted) { my $sidx = $wanted->{$idx}->{'storage_index'}; push(@oids, ".1.3.6.1.2.1.25.2.3.1.2.$sidx"); $idx_storage_idx{$sidx} = $idx; } my $results = $agent->get(@oids); for my $type (keys %{$results}) { my $sidx = ($type =~ m/.+?\.(\d+)$/)[0]; my $i = $idx_storage_idx{$sidx}; if ($results->{$type} eq 'noSuchInstance') { my $m = $wanted->{$i}->{'mount'}; debug("$m - agent returned noSuchInstance for " . "storage type index - deleting. Is this a " . "Solaris zone or other VPS?"); delete $mount_names->{$wanted->{$i}->{'mount'}}; delete $wanted->{$i}; next; } my $stidx = ($results->{$type} =~ m/.+?\.(\d+)$/)[0]; my $stype = $hr_storage_types[$stidx]; if (($storage_type ne 'any') && ($stype !~ /$storage_type/i)) { # Not an storage type we care about, remove the entry delete $mount_names->{$wanted->{$i}->{'mount'}}; delete $wanted->{$i}; next; } $wanted->{$i}->{'storage_type'} = $stype; } if (scalar(keys %$wanted) == 0) { die "No partitions found matching storage type $storage_type!"; } } sub add_hr_storage_metrics { my $agent = shift; my $wanted = shift; my %stoids = qw( .1.3.6.1.2.1.25.2.3.1.3 storage_descr .1.3.6.1.2.1.25.2.3.1.4 storage_allocation_units .1.3.6.1.2.1.25.2.3.1.5 storage_size .1.3.6.1.2.1.25.2.3.1.6 storage_used ); # Check for storage allocation failures unless user # requests we do not check for them. if ($plugin->opts->get('no-fsafc') == 0) { $stoids{'.1.3.6.1.2.1.25.2.3.1.7'} = 'storage_allocation_failures'; } for my $mount (keys %wanted) { my @oids; my $sidx = $wanted->{$mount}->{'storage_index'}; for my $oid (keys %stoids) { push(@oids, "${oid}.$sidx"); } my $result = $agent->get(@oids); debug("$mount - populating info"); for my $i (keys %$result) { my ($base, $idx) = ($i =~ m/^(.+)\.(\d+)$/); my $key = $stoids{$base}; $wanted->{$mount}->{$key} = $result->{$i}; } } } sub make_threshold { my $w = shift; my $c = shift; return Nagios::Plugin::Threshold->set_thresholds('warning' => $w, 'critical' => $c); } sub fs_type_check { my $value = shift; my $op = shift; my $expr = shift; my $code = ""; # If user wants custom FS type filter if (($expr eq 'any') || ($expr eq '')) { return 1; } if ($op =~ m/~/) { $code = "('$value' $op m{$expr}i);"; } else { $code = "('$value' $op q{$expr});"; } my $result = 0; eval { $result = eval $code; die $@ if $@; }; die "Invalid fs type operator / expression: $@" if $@; debug("fs_type_check: evaled $code, result $result"); return $result; } sub debug { return unless $plugin->opts->get('snmp-debug') == 1; my $msg = shift; warn scalar(localtime()) . ": $msg\n"; } } exit check_snmp_hr_storage();