#!/usr/bin/perl 

# check_nmon.pl
# Author: Miguel Moro
# Version: 0


# PACKAGES AND GLOBAL VARIABLES

use Time::Local;
use strict;
use Getopt::Std;

my %options=();
my $debug = 0;
my $test = 0;
my $nmonfile = undef;
my $check_type = undef;
my $check_key_found = 0;
my $check_key = undef;
my $check_key_value = undef;
my $critical_threshold = undef;
my $warning_threshold = undef;
my $sev = undef;
my $outmsg = undef;
my $perfstring = undef;
my $find_keys_ref = undef;
my $metric_ref = undef;
my $helpstring = "
check_sa

This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
It may be used, redistributed and/or modified under the terms of the GNU
General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).

This script will read a data file generated by NMON and return the value for the key/subkey params passed to it.
Option -t just read the data file and returns the list of key/subkey values included in the file. This option is 
useful at a beginning to determine exactly what to monitor.

It is possible to check thresholds and generate warning or critical alarms for:
- % occupied CPU.
- % occupied MEM.
- % disk busy time.

Options:
 -F     NMON data file.
 -k     Comma separated metric keys to report.
 -s	Comma separated metric subkeys to report.
 -h     Displays help message.
 -d     Debug mode on. Prints debug messages.
 -t     Test mode. List metric and sub-metrics in the specified file.
 -y	Check type. Possible values: CPU, MEM, DISKBUSY.
 -w	Warning threshold.
 -c	Critical threshold.
";


# OPTIONS AND SANITY CHECKS

getopts("F:k:s:hdty:w:c:",\%options);
# like the shell getopt, "d:" means d takes an argument

# Sanity checks
if (defined $options{h}) {
	print $helpstring;
	exit 0;
}
unless (defined $options{'F'}) {
	print "UNKNOWN - Option -F must always be given.";
	exit 3;
}
unless (-e $options{'F'}) {
	print "UNKNOWN - NMON file $options{'F'} does not exist.";
	exit 3;
}
if ((stat($options{'F'}))[9] + 3600 < time) {
	print "UNKNOWN - NMON file $options{'F'} older than an hour. NMON is stopped!";
	exit 3;
}
unless (defined $options{'t'} || defined $options{'k'}) {
	print "UNKNOWN - Option -t or option -k must be given.";
	exit 3;
}
if (defined $options{y}) {
	unless ($options{y} eq 'CPU' || $options{y} eq 'MEM' || $options{y} eq 'DISKBUSY') {
		print "UNKNOWN - Option -y must be CPU, MEM or DISKBUSY.";
		exit 3;
	}
	unless (defined $options{'w'} || defined $options{'c'}) {
		print "UNKNOWN - If option -y is specified warning and/or critical threshold must be given.";
		exit 3;
	}
}

# Option assignment and initialization
$nmonfile = $options{F};
if (defined $options{d}) {
	$debug = 1;
}
if (defined $options{t}) {
	$test = 1;
} 
if (defined $options{y}) {
	$check_type = $options{y};
	if ($check_type eq 'CPU') {
		$check_key = 'Idle%';
		$outmsg = 'UNKNOWN - for CPU check type Idle% subkey must be specified.';
	}
	if ($check_type eq 'MEM') {
		$check_key = 'Real Free %';
		$outmsg = 'UNKNOWN - for MEM check type \'Real Free %\' subkey must be specified.';
	}
	if ($check_type eq 'DISKBUSY') {
		$check_key = 'DISKBUSY';
		$outmsg = 'UNKNOWN - for DISKBUSY check type DISKBUSY key must be specified.';
	}
} 
if (defined $options{k}) {
	my @keys = split /\s*,\s*/,$options{k};
	foreach my $key (@keys) {
		if (defined $options{s}) {
			my @subkeys = split /\s*,\s*/,$options{s};
			foreach my $subkey (@subkeys) {
				$find_keys_ref->{uc($key)}->{SUBKEYS}->{$subkey}->{DEFINED} = 1;
			}
		} else {
			$find_keys_ref->{uc($key)}->{ALL_SUBKEYS}->{DEFINED} = 1;
		}
	}
	if ($debug) {
		print "\nMetrics that will be searched for: \n";
		foreach my $key (sort keys %{$find_keys_ref}) {
			print "key: $key\n";
			foreach my $subkey (sort keys %{$find_keys_ref->{$key}->{SUBKEYS}}) {
				print "\tsubkey: $subkey\n";
			}
		}
	}
}
if (defined $options{w}) {
	$warning_threshold = $options{w};
} 
if (defined $options{c}) {
	$critical_threshold = $options{c};
} 

# PROCCESS THE NMON FILE

open (IN,$nmonfile) or die "Unable to open NMON file $nmonfile\n";
while (my $line=<IN>) {
	chomp $line;
	my @keys=split /\s*,\s*/,$line;

	if ($keys[0] =~ /^AAA/) { next; }		# Skip lines starting with this
	if ($keys[0] =~ /^BBB/) { next; }		# Skip lines starting with this
	if ($keys[0] =~ /^ZZZ/) { next; }		# Skip timestamp lines

	if ($test) {	
		# if test look for all subkeys
		$find_keys_ref->{$keys[0]}->{ALL_SUBKEYS}->{DEFINED} = 1;
	} else {   
		# else check if key is selected
		my $found = undef;
		foreach my $findkey (keys %{$find_keys_ref}) {
			if ($keys[0] =~/^$findkey$/i) {
				$found = 1;
				last;
			}
		}
		if (!$found) { next	}
	}

	$check_key_found = 1 if ($keys[0] eq $check_key);
	if (!defined($metric_ref->{$keys[0]}) ) {
		$metric_ref->{$keys[0]}->{DESC} = $keys[1];
		if ($find_keys_ref->{$keys[0]}->{ALL_SUBKEYS}->{DEFINED}){
			for (my $i=2;$i<=$#keys;$i++) {
				$metric_ref->{$keys[0]}->{SUBKEYS}->{$i}->{NAME} = $keys[$i];
				$check_key_found = 1 if ($keys[$i] eq $check_key);
			}
		} else {
			foreach my $findsubkey (keys %{$find_keys_ref->{$keys[0]}->{SUBKEYS}}) {
				for (my $i=2;$i<=$#keys;$i++) {
					$check_key_found = 1 if ($keys[$i] eq $check_key);
					if ($keys[$i] eq $findsubkey) {
						$metric_ref->{$keys[0]}->{SUBKEYS}->{$i}->{NAME} = $keys[$i];
						last;
					}
				}
			}
		}
		next;
	} elsif ($keys[1] =~ /^(T\d+)$/) {
		foreach my $subkey (keys %{$metric_ref->{$keys[0]}->{SUBKEYS}}) {
			$metric_ref->{$keys[0]}->{SUBKEYS}->{$subkey}->{VALUE} = $keys[$subkey];
			if ($metric_ref->{$keys[0]}->{SUBKEYS}->{$subkey}->{NAME} eq $check_key ||
			    $keys[0] eq $check_key) {
				$check_key_value = $metric_ref->{$keys[0]}->{SUBKEYS}->{$subkey}->{VALUE};
			}
		}
		next;
	}
}
close IN;

# If check type defined and needed key or subkey not found exit
if (defined $check_key and !$check_key_found) {
	print $outmsg;
	exit 3;	
}

# if  debug, print all found keys, subkeys and values
if ($debug) {
	print "\nMetrics found: \n";
	foreach my $key (sort keys %{$metric_ref}) {
		print "Major key: $key, Description: ".$metric_ref->{$key}->{DESC}."\n";
		foreach my $subkey (sort keys %{$metric_ref->{$key}->{SUBKEYS}}) {
			print "\tSubkey: $subkey, Name: ".$metric_ref->{$key}->{SUBKEYS}->{$subkey}->{NAME}."\n";
			print "\t\tValue=".$metric_ref->{$key}->{SUBKEYS}->{$subkey}->{VALUE}."\n";
		}
	}
}

# METRICS PROCESSING

# if test mode show the keys and subkeys and exit.
if ($test) {
	print "\nAvailable key  and subkey values in file $nmonfile:\n";
	foreach my $key (sort keys %{$metric_ref}) {
		print "Major key: $key, Description: ".$metric_ref->{$key}->{DESC}."\n";
		print "Sub-keys:\n";
		foreach my $subkey (sort keys %{$metric_ref->{$key}->{SUBKEYS}}) {
			print "\tSubkey: ".$metric_ref->{$key}->{SUBKEYS}->{$subkey}->{NAME}."\n";
		}
	}
	exit 0;
}

# performance data
foreach my $key (sort keys %{$metric_ref}) {	
	foreach my $subkey (sort keys %{$metric_ref->{$key}->{SUBKEYS}}) {
		my $name = $metric_ref->{$key}->{SUBKEYS}->{$subkey}->{NAME};
		my $value = $metric_ref->{$key}->{SUBKEYS}->{$subkey}->{VALUE};
		$perfstring .= "$key\[$name\]=$value ";
	}
}

# warning and critical checking
$sev = 0;
$outmsg = "OK - All counters within specified thresholds";
if (defined $check_key) {
	print "Warning and critical checking " if $debug;
	if ($check_type eq 'CPU' || $check_type eq 'MEM') {
		# general checks of the type (100 - check_key_value) > threshold
		$check_key_value = 100 - $check_key_value;
		print "($check_type check type): 100 - $check_key\($check_key_value\)|$warning_threshold|$critical_threshold\n" if $debug;
		if (defined $warning_threshold && $check_key_value > $warning_threshold) {
			$sev = 1;
			$outmsg = "WARNING - 100-$check_key\($check_key_value\) greater than $warning_threshold";
		}
		if (defined $critical_threshold && $check_key_value > $critical_threshold) {
			$sev = 2;
			$outmsg = "CRITICAL - 100-$check_key\($check_key_value\) greater than $critical_threshold";
		}
	}
	if ($check_type eq 'DISKBUSY') {
		# general checks of the type check_key_value > threshold
		print "($check_type check type): $check_key\($check_key_value\)|$warning_threshold|$critical_threshold\n" if $debug;
		if (defined $warning_threshold && $check_key_value > $warning_threshold) {
			$sev = 1;
			$outmsg = "WARNING - $check_key\($check_key_value\) greater than $warning_threshold";
		}
		if (defined $critical_threshold && $check_key_value > $critical_threshold) {
			$sev = 2;
			$outmsg = "CRITICAL - $check_key\($check_key_value\) greater than $critical_threshold";
		}
	}
}

# Print output and exit
print $outmsg." | ".$perfstring; 
exit $sev;

__END__
