#!/usr/bin/perl -wT
#
# check_megaraid Nagios plugin.  Checks a Dell PERC card (megaraid driver)
#       and reports the status of the logical and physical drives.
#
# Requires that Net::SNMP be installed on the machine performing
#       the monitoring and that the megaraid snmp agent be set up
#       on the machine to be monitored.
#
#--------------------------------------------------#
#
# Additions for 1.3 version (by William) as compared to original 0.8 version (by John): 
#   1. [0.9] Check consistancy has been downgraded to WARNING
#   2. [0.9] The message in the output is now more detailed
#   3. [0.9] The number of drives is now displayed in the output
#   4. [1.1] Plugin now retrieves snmp oid for number of read and write errors
#      and reports an alert if its not 0 or -1
#   5. [1.2] Plugin now checks 'medium' and 'other' errors for all physical drives,
#      this data is returned as performance output and in order to detect change
#      you need to send previous performance out in the command line to this plugin.
#      If you're nagios is set to send notifications after multiple subsequent non-OK
#      alerts then you also need to send previous state so as to force notification
#      (performance data would be same as original until previous state is non-OK HARD state)
#   6. [1.3] Reworked reporting of physical id to report it as "controler/channel/id" when 
#      more then one controller is present or as "channnel/id" when more then one channel
#      Also for persnmp5 if you have multiple luns (should not happen with disk drives)
#      it will in theory add lun # as ".lun" to the physical id
#   7. [1.35] Changed reporting of 'medium' and 'other' errors as WARNING
#      Changed output system so that old performance data is reported even for UNKNOWN
#   8. [1.4] Added specifying SNMP version and changed default to v1 because as I found out
#      this actually gets executed faster. Also added capability to profile time it takes
#      for this plugin to execute with "--debug_time" option
#   9. [1.45] Modifications to output +num of physical or logical drive errors when it changes instead of total
#      where as total number of such errors is always now printed as part of the plugin output
#      Also plugin now reports WARNING when array is degraded but one of the disks is being rebuilt
#
# Here is an example of how to specify that in nagios config
#  (note that $USER1$ and $USER6$ are defined in resource.cfg, 
#   $USER1$ is path to plugin directory and $USER6$ is community string
#   also "" around macros in commands.cfg are on purpose, don't forget them):
#
# define command {
#        command_name check_megaraid
#        command_line $USER1$/check_megaraid.pl -s 1 --debug_time -H $HOSTADDRESS$ -C $USER6$ -P "$SERVICEPERFDATA$" -S "$SERVICESTATE$,$SERVICESTATETYPE$"
# }
# define service{
#	 host_name			 list of target hosts
#        service_description             Megaraid Status
#        check_command                   check_megaraid
# 	 ...
# }
#
#--------------------------------------------------#
#
# Netsaint config example (this is old, use only with nagios 1.0) - see above for nagios 2.0:
#
# command[check_megaraid]=/usr/local/netsaint/libexec/check_megaraid -H $HOSTADDRESS$ -C $ARG1$
# service[target_host]=megaraid;0;24x7;3;10;5;admins;60;24x7;1;1;1;;check_megaraid!public
#
#--------------------------------------------------#
#
# Copyright (C) 2002 ibiblio
# Written by: John Reuning <john@ibiblio.org> and revised by William Leibzon <william@leibzon.org>
# http://www.ibiblio.org/john/megaraid/ and http://william.leibzon.org/nagios/
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
#--------------------------------------------------#
my $version = "1.46";
# modified by William Leibzon in April/May 2007; original code was 0.8 from 22-Mar-2002
#--------------------------------------------------#

use strict;
use lib "/opt/nagios/plugins";
use Net::SNMP qw(oid_lex_sort);
use utils qw($TIMEOUT %ERRORS);
use Getopt::Long;
use Time::HiRes qw(time);
Getopt::Long::Configure('bundling', 'no_ignore_case');

# variables
my(
$DEBUG,			# print debug messages
$MAX_OUTPUTSTR,		# maximum number of characters in otput, default is 256
%LOGDRV_CODES,
%PHYDRV_CODES,
$session,
$error,
$line,
$foo,			# represents an unused string
$host,
$community,
$port,
$timeout,
$alert,
$code,			# code value returned by snmp
$logdrv_id,		# drive id
$phydrv_id,
$nagios_status,		# nagios return status code, starts with "OK"
$opt_H,
$opt_C,
$opt_p,
$opt_t,
$opt_a,
$opt_P,
$opt_snmpversion,
$opt_debug,
$opt_perfdata,
$opt_prevstate,
%pdrv_status,
%curr_perf,
%prev_perf,
@prev_state,
$opt_debugtime,
%debug_time,
$total_merr,
$total_oerr,
);

sub help;
sub version;
sub print_usage;
sub process_perf;
sub print_output;
sub usage;

#--------------------------------------------------#
# need to manually define some things here

# for cases when you're not using utils.pm
# my %ERRORS=('OK'=>0,'WARNING'=>1,'CRITICAL'=>2,'UNKNOWN'=>3,'DEPENDENT'=>4);
# my $TIMEOUT=30;

$nagios_status = "OK";
$DEBUG = 0;	# to print debug messages, set this to 1
$MAX_OUTPUTSTR = 512;
%LOGDRV_CODES = (
	0 => ['offline', 'raid drive is offline'],
	1 => ['degraded', 'raid array is degraded'],
	2 => ['optimal', 'array funtioning properly'],
	3 => ['initialize', 'currently initializing'],
	4 => ['checkconsistency', 'raid array is being checked'],
);

%PHYDRV_CODES = (
	1 => ['ready'],
	3 => ['online'],
	4 => ['failed'],
	5 => ['rebuild'],
	6 => ['hotspare'],
	20 => ['nondisk'],
);

my $logdrv_status_tableoid = ".1.3.6.1.4.1.3582.1.1.2.1.3";
my $phydrv_status_tableoid = ".1.3.6.1.4.1.3582.1.1.3.1.4";
my $phydrv_mediumerrors_tableoid=".1.3.6.1.4.1.3582.1.1.3.1.12";
my $phydrv_othererrors_tableoid=".1.3.6.1.4.1.3582.1.1.3.1.15";
my $readfail_oid=".1.3.6.1.4.1.3582.1.1.1.1.13";
my $writefail_oid=".1.3.6.1.4.1.3582.1.1.1.1.14";
my $adpt_readfail_oid=".1.3.6.1.4.1.3582.1.1.1.1.15";
my $adpt_writefail_oid=".1.3.6.1.4.1.3582.1.1.1.1.16";

#--------------------------------------------------#
# parse command line arguments

GetOptions (
	"h|help"		=> \&help,
	"V|version"		=> \&version,
	"H|hostname=s"		=> \$opt_H,
	"C|community=s"		=> \$opt_C,
	"p|port=s"		=> \$opt_p,
	"t|timeout=s"		=> \$opt_t,
	"s|snmp_version=s"	=> \$opt_snmpversion,
	"a|alert=s"		=> \$opt_a,
	"d|debug"		=> \$opt_debug,
	"P|perf=s"		=> \$opt_perfdata,
	"S|state=s"		=> \$opt_prevstate,
	"debug_time"		=> \$opt_debugtime
);

$debug_time{plugin_start}=time() if $opt_debugtime;

# hostname
($opt_H) || usage("Hostname or IP address not specified\n");
$host = $1 if ($opt_H =~ m/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[a-zA-Z][-a-zA-Z0-9]*(\.[a-zA-Z][-a-zA-Z0-9]*)*)$/);
if (!($host)) {
	usage("Invalid hostname: $opt_H\n");
}

# community string - defaults to "public"
($opt_C) || ($opt_C = "public");
$community = $opt_C;

# port number - defaults to 161
($opt_p) || ($opt_p = 161);
($opt_p =~ m/^[0-9]+$/) || usage("Invalid port number: $opt_p\n");
$port = $opt_p;

# timeout - defaults to netsaint timeout
($opt_t) || ($opt_t = $TIMEOUT);
($opt_t =~ m/^[0-9]+$/) || usage("Invalid timeout value: $opt_t\n");
$timeout = $opt_t;

# alert - defaults to "crit"
($opt_a) || ($opt_a = "crit");
if ($opt_a =~ /warn/) {
	$alert = "WARNING";
} elsif ($opt_a =~ /crit/) {
	$alert = "CRITICAL";
} else {
	usage("Invalid alert: $opt_a\n");
	&print_usage;
	$alert = "UNKNOWN";
	$nagios_status = "UNKNOWN";
}

# previos performance data string and previous state
%prev_perf=process_perf($opt_perfdata) if $opt_perfdata;
@prev_state=split(',',$opt_prevstate) if $opt_prevstate;

$DEBUG=$opt_debug if defined($opt_debug) && $opt_debug;
if ($DEBUG) {
	print "hostname: $host >>$opt_H<<\n";
	print "community: $community >>$opt_C<<\n";
	print "port: $port >>$opt_p<<\n";
	print "timeout: $timeout >>$opt_t<<\n";
	print "alert: $alert >>$opt_a<<\n";
	print "prev_state: $opt_prevstate\n" if $opt_prevstate;
	# print "perf: $opt_perfdata \n" if $opt_perfdata;
}

#--------------------------------------------------#
# open the snmp connection and fetch the data
if ($DEBUG) {
	print "host = $host\n";
	print "community = $community\n\n";
}

# set the timeout
$SIG{'ALRM'} = sub {
	if(defined($session)) {
		$session->close;
	}
	print_output("UNKNOWN","snmp query timed out");
	exit $ERRORS{"UNKNOWN"};
};
alarm($timeout);

# open the snmp connection
if (!defined($opt_snmpversion) || $opt_snmpversion eq '1') {
	$opt_snmpversion='1';
}
elsif ($opt_snmpversion eq '2' || $opt_snmpversion eq '2c') {
	$opt_snmpversion='snmpv2c';
}
else {
	print_output("UNKNOWN","invalid or unsupported value ($opt_snmpversion) for SNMP version");
	exit $ERRORS{"UNKNOWN"};
}
($session, $error) = Net::SNMP->session(
	-hostname  => $host,
	-community => $community,
	-port      => $port,
	-version   => $opt_snmpversion
);

if (!defined($session)) {
	if ($DEBUG) {
		print("snmp connection error: %s.\n", $error);
	}
	print_output("UNKNOWN","no data received");
	exit $ERRORS{"UNKNOWN"};
}

# fetch snmp data
$error="";
$debug_time{snmpretrieve_readwritefailoids}=time() if $opt_debugtime;
my $snmp_result=$session->get_request(-Varbindlist => [ $readfail_oid, $writefail_oid, $adpt_readfail_oid, $adpt_writefail_oid ]);
$debug_time{snmpretrieve_readwritefailoids}=time()-$debug_time{snmpretrieve_readwritefailoids} if $opt_debugtime;
$error.="could not retrieve snmp data OIDs" if !$snmp_result;
$debug_time{snmpgettable_logdrvstatus}=time() if $opt_debugtime;
my $logdrv_data_in = $session->get_table(-baseoid => $logdrv_status_tableoid) if !$error;
$debug_time{snmpgettable_logdrvstatus}=time()-$debug_time{snmpgettable_logdrvstatus} if $opt_debugtime;
$error.= "could not retrieve snmp table $logdrv_status_tableoid" if !$logdrv_data_in && !$error;
$debug_time{snmpgettable_phydrvstatus}=time() if $opt_debugtime;
my $phydrv_data_in = $session->get_table(-baseoid => $phydrv_status_tableoid) if !$error;
$debug_time{snmpgettable_phydrvstatus}=time()-$debug_time{snmpgettable_phydrvstatus} if $opt_debugtime;
$error.= "could not retrieve snmp table $phydrv_status_tableoid" if !$phydrv_data_in && !$error;
$debug_time{snmpgettable_mederrors}=time() if $opt_debugtime;
my $phydrv_merr_in = $session->get_table(-baseoid=>$phydrv_mediumerrors_tableoid) if !$error;
$debug_time{snmpgettable_mederrors}=time()-$debug_time{snmpgettable_mederrors} if $opt_debugtime;
$error.= "could not retrieve snmp table $phydrv_mediumerrors_tableoid" if !$phydrv_merr_in && !$error;
$debug_time{snmpgettable_odrverrors}=time() if $opt_debugtime;
my $phydrv_oerr_in = $session->get_table(-baseoid=>$phydrv_othererrors_tableoid) if !$error;
$debug_time{snmpgettable_odrverrors}=time()-$debug_time{snmpgettable_odrverrors} if $opt_debugtime;
$error.= "could not retrieve snmp table $phydrv_othererrors_tableoid" if !$phydrv_oerr_in && !$error;

if ($error) {
	if ($DEBUG) {
		printf("snmp error: %s\n", $session->error());
	}
	$session->close;
	print_output("UNKNOWN",$error);
	exit $ERRORS{'UNKNOWN'};
}

# close the snmp connection
$session->close;

#--------------------------------------------------#
# parse the data and determine status

# set the initial output string and ok status
my $output_data = "";
my $output_data_end = "";

if ($DEBUG) {
	print "adpt_readfail: ". $adpt_readfail_oid ." = ". $snmp_result->{$adpt_readfail_oid} ."\n";
	print "adpt_writefail: ". $adpt_writefail_oid ." = ". $snmp_result->{$adpt_writefail_oid} ."\n";
	print "readfail_sec: ". $readfail_oid ." = ". $snmp_result->{$readfail_oid} ."\n";
	print "writefail_sec: ". $writefail_oid ." = ". $snmp_result->{$writefail_oid} ."\n";
}
if ($snmp_result->{$adpt_readfail_oid}>0) {
	$output_data=$snmp_result->{$adpt_readfail_oid}." adapter read failures";
	$nagios_status=$alert;
}
if ($snmp_result->{$adpt_writefail_oid}>0) {
	$output_data.= ", " if $output_data;
        $output_data=$snmp_result->{$adpt_writefail_oid}." adapter write failures";
        $nagios_status=$alert;
}
if ($snmp_result->{$writefail_oid}>0) {
        $output_data.= ", " if $output_data;
        $output_data=$snmp_result->{$writefail_oid}." write failures";
        $nagios_status=$alert;
}
if ($snmp_result->{$readfail_oid}>0) {
        $output_data.= ", " if $output_data;
        $output_data=$snmp_result->{$readfail_oid}." read failures";
        $nagios_status=$alert;
}

# physical drive states
my $phydrv_total=0;
my ($controller_id, $channel_id, $drive_id, $lun_id);
my %h_controllers=();
my %h_channels=();

# first loop to load data (and find controller, channel, drive ids) for all drives into our hash
foreach $line (oid_lex_sort(keys(%{$phydrv_data_in}))) {
	$code = $phydrv_data_in->{$line};
	  print "phydrv_status: $line = $code" if $DEBUG;
	$line = substr($line,length($phydrv_status_tableoid)+1);
	($controller_id,$channel_id,$drive_id,$lun_id) = split(/\./,$line,4);
	$lun_id = 0 if !defined($lun_id);
	  print " | suffix = $line, controller = $controller_id, channel = $channel_id, drive = $drive_id, lun = $lun_id\n" if $DEBUG;
	$h_controllers{$controller_id}=1;
	$h_channels{$controller_id.'_'.$channel_id}=1;
	if (!$pdrv_status{$line}) {
		$pdrv_status{$line} = { 'status' => $code, 'controller' => $controller_id, 'channel' => $channel_id, 'drive' => $drive_id, 'lun' => $lun_id };
	}
	else {
		print_output("UNKNOWN","processing error, physical drive $line found in SNMP result 2nd time");
        	exit $ERRORS{'UNKNOWN'};
	}
}

my $num_controllers = scalar(keys %h_controllers);
my $num_channels = scalar(keys %h_channels);
my $phd_nagios_status = $nagios_status;

# 2nd loop as we now can find what physical id to use
foreach $line (oid_lex_sort(keys(%{$phydrv_data_in}))) {
	$line = substr($line,length($phydrv_status_tableoid)+1);
	if ($num_controllers > 1) {
		$phydrv_id = $pdrv_status{$line}{controller}.'/'.$pdrv_status{$line}{channel}.'/'.$pdrv_status{$line}{drive};
	}
	elsif ($num_channels > 1) {
		$phydrv_id = $pdrv_status{$line}{channel}.'/'.$pdrv_status{$line}{drive};
	}
	else {
		$phydrv_id = $pdrv_status{$line}{drive};
	}
	$phydrv_id .= '.'.$pdrv_status{$line}{lun} if ($pdrv_status{$line}{lun} != 0);
	$pdrv_status{$line}{phydrv_id}=$phydrv_id;

	$code= $pdrv_status{$line}{status};
	# check status (catch if state is either "failed" (4) or "rebuild" (5))
	if (!defined($PHYDRV_CODES{$code})) {
                $output_data.=", " if $output_data;
                $output_data.= "phy drv($phydrv_id) unknown code $code";
                $nagios_status = $alert; # maybe this should not be an alert???
		$pdrv_status{$line}{'status_str'} = $code;
        }
	else {
		$pdrv_status{$line}{'status_str'} = $PHYDRV_CODES{$code};
		if ($PHYDRV_CODES{$code}[0] eq 'failed' || $PHYDRV_CODES{$code}[0] eq 'rebuild') {
			$output_data .= ", " if $output_data;
			$output_data .= "phy drv($phydrv_id) ".$PHYDRV_CODES{$code}[0];
			if ($PHYDRV_CODES{$code}[0] eq 'rebuild') {
				$phd_nagios_status = "WARNING" if $phd_nagios_status eq 'OK';
			}
			else { # failed state
				$phd_nagios_status = $alert; 
			}
		}
		$phydrv_total++ if ($PHYDRV_CODES{$code}[0] ne 'nondisk');  # only count disks for output
	}
}

# check logical drive status
foreach $line (oid_lex_sort(keys(%{$logdrv_data_in}))) {
        $code = $logdrv_data_in->{$line};
        if ($DEBUG) {
                print "logdrv_status: $line = $code";
        }
        $line = substr($line,length($logdrv_status_tableoid)+1);
        ($foo,$logdrv_id) = split(/\./,$line,2);
        if ($DEBUG) {
                print " | logdrv_id = $logdrv_id\n";
        }
        # check status (catch if status is not "optimal" (2))
        if (!defined($LOGDRV_CODES{$code})) {
                $output_data.=", " if $output_data;
                $output_data.= "log drv($logdrv_id) unknown code $code";
                $nagios_status = $alert; # maybe this should not be an alert???
        }
        elsif ($LOGDRV_CODES{$code}[0] ne 'optimal') {
                $output_data.= ", " if $output_data;
                $output_data .= "log drv($logdrv_id) ".$LOGDRV_CODES{$code}[0]." (".$LOGDRV_CODES{$code}[1].")";
                if ($LOGDRV_CODES{$code}[0] eq 'checkconsistency' || $LOGDRV_CODES{$code}[0] eq 'initialize') {
                        $nagios_status = "WARNING" if $nagios_status eq "OK";
                }
                else {
			# below is to force WARNING in case when array is degraded but disk is already being rebuild
			if ($LOGDRV_CODES{$code}[0] eq 'degraded' && $phd_nagios_status eq 'WARNING' && $nagios_status ne $alert) {
				$nagios_status='WARNING';
			}
			else {
                        	$nagios_status = $alert;
			}
                }
        }
}

# physical drive errors
$total_merr=0;
$total_oerr=0;
my $nerr=0;
my $ndiff=0;

if (defined($opt_perfdata)) {
    foreach $line (oid_lex_sort(keys(%{$phydrv_merr_in}))) {
        $nerr = $phydrv_merr_in->{$line};
          print "phydrv_mediumerr: $line = $nerr" if $DEBUG;
        $line = substr($line,length($phydrv_mediumerrors_tableoid)+1);
	if (defined($pdrv_status{$line}) && $pdrv_status{$line}{status_str} ne 'nondisk') {
		  print " | suffix = $line, phydrv_id = ".$pdrv_status{$line}{phydrv_id} if $DEBUG;
		$curr_perf{'merr_'.$line}=$nerr;
		if ($nerr!=0 && (!defined($prev_perf{'merr_'.$line}) || $prev_perf{'merr_'.$line} < $nerr)) {
			$ndiff=$nerr;
			$ndiff-=$prev_perf{'merr_'.$line} if defined($prev_perf{'merr_'.$line});
			$output_data .= ", " if $output_data;
                        $output_data .= "phy drv(".$pdrv_status{$line}{phydrv_id}.") +$ndiff medium errors";
			$phd_nagios_status = 'WARNING' if $phd_nagios_status eq 'OK';
		}
                if ($nerr!=0) {
			$total_merr+=$nerr;
                        $output_data_end .= ", " if $output_data_end;
                        $output_data_end .= "phy drv(".$pdrv_status{$line}{phydrv_id}.") $nerr medium errors";
                }
	}
	  print "\n" if $DEBUG;
    }
    foreach $line (oid_lex_sort(keys(%{$phydrv_oerr_in}))) {
        $nerr = $phydrv_oerr_in->{$line};
          print "phydrv_othererr: $line = $nerr" if $DEBUG;
        $line = substr($line,length($phydrv_othererrors_tableoid)+1);
	if (defined($pdrv_status{$line}) && $pdrv_status{$line}{status_str} ne 'nondisk') {
		  print " | suffix = $line, phydrv_id = ".$pdrv_status{$line}{phydrv_id} if $DEBUG;
		$curr_perf{'oerr_'.$line}=$nerr;
		if ($nerr!=0 && (!defined($prev_perf{'oerr_'.$line}) || $prev_perf{'oerr_'.$line} < $nerr)) {
			$ndiff=$nerr;
			$ndiff-=$prev_perf{'oerr_'.$line} if defined($prev_perf{'oerr_'.$line});
                	$output_data .= ", " if $output_data;
                        $output_data .= "phy drv(".$pdrv_status{$line}{phydrv_id}.") +$ndiff other errors";
			$phd_nagios_status = 'WARNING' if $phd_nagios_status eq 'OK';
		}
                if ($nerr!=0) {
			$total_oerr+=$nerr;
                        $output_data_end .= ", " if $output_data_end;
                        $output_data_end .= "phy drv(".$pdrv_status{$line}{phydrv_id}.") $nerr other errors";
                }
	}
	print "\n" if $DEBUG;
    }
}

$debug_time{plugin_finish}=time() if $opt_debugtime;
$debug_time{plugin_totaltime}=$debug_time{plugin_finish}-$debug_time{plugin_start} if $opt_debugtime;

# output text results
$output_data.= " - " if $output_data;
$output_data.= sprintf("%d logical disks, %d physical drives, %d controllers found", scalar(keys %{$logdrv_data_in}), $phydrv_total, $num_controllers);
$output_data.= " - ". $output_data_end if $output_data_end;

# netsaint doesn't like output strings larger than 256 chars
# [William: modified it so that number of characters is now $MAX_OUTPUTSTR constant
#           defined top of the file, if you set it to undef this is not checked]
$output_data = substr($output_data,0,$MAX_OUTPUTSTR) if defined($MAX_OUTPUTSTR) && length($output_data) > $MAX_OUTPUTSTR;

# combine status from checking physical and logical drives and print everything
$nagios_status = $phd_nagios_status if $ERRORS{$nagios_status}<$ERRORS{$phd_nagios_status};
print_output($nagios_status,$output_data);
exit $ERRORS{"$nagios_status"};

#--------------------------------------------------#
# version flag function
sub version {
	print "$0 version $version\n";
	exit $ERRORS{'UNKNOWN'};
}

#--------------------------------------------------#
# display help information
sub help {
        print "Checks logical and physical drive status of a Dell PERC controller.\n";
	print "\n";
        print_usage();
        print "\n";
	print "Options:\n";
	print "  -h, --help\n";
	print "    Display help\n";
	print "  -V, --version\n";
	print "    Display version\n";
        print "  -H, --hostname <host>\n";
	print "    Hostname or IP address of target to check\n";
	print "  -C, --community <community>\n";
	print "    SNMP community string (defaults to \"public\")\n";
	print "  -s, --snmp_version 1 | 2 | 2c";
	print "	   Version of SNMP protocol to use - default is 2c\n";
	print "  -p, --port <port>\n";
	print "    SNMP port (defaults to 161)\n";
	print "  -t, --timeout <timeout>\n";
	print "    Seconds before timing out (defaults to Netsaint timeout value)\n";
	print "  -a, --alert <alert level>\n";
	print "    Alert status to use if an error condition is found\n";
	print "    Accepted values are: \"crit\" and \"warn\" (defaults to crit)\n";
	print "  -P, --perf <performance data>\n";
	print '    The results of previous check performance data ($SERVICEPERFDATA$ macro)'."\n";
	print "    which contains number of medium and other errors that were before\n";
	print "    if this is not the same now then ALERT is sent\n";
	print "  -S, --state <STATE,STATETYPE>\n";
	print "    If you use -P and you have notifications sent to be sent at > 1 alerts\n";
	print "    then you need to send previous state and type (HARD or SOFT) and then\n";
	print "    this plugin would continue to report non-OK state until STATETYPE changes\n";
	print "    to HARD thereby making sure user receives NOTIFICATION\n";
	print "    Proper use of this is have '-S ".'"$SERVICESTATE$,$SERVICESTATETYPE$"'."' in your commands.cfg\n";
	print "\n";
	exit $ERRORS{'UNKNOWN'};
}

sub usage {
	print $_."\n" foreach @_;
	print_usage();
	exit $ERRORS{'UNKNOWN'};
}

#--------------------------------------------------#
# display usage information
sub print_usage {
        print "Usage:\n";
        print "$0 -H <host> -C <community> [-p <port>] [-t <timeout>] [-a <alert level>] [-P <previous performance data>] [-S <previous state>]\n";
        print "$0 --help\n";
        print "$0 --version\n";
}

#--------------------------------------------------#
# process previous performance data - William
sub process_perf {
 my %pdh;
 foreach (split(' ',$_[0])) {
   if (/(.*)=(\d+)/) {
	print "prev_perf: $1 = $2\n" if $DEBUG;
	$pdh{$1}=$2 if $1 !~ /^time_/;
   }
 }
 return %pdh;
}

# print output and performance data
sub print_output {
   my ($out_status,$out_str)=@_;

   print "Megaraid $out_status";
   print " - $out_str" if $out_str;
   if (defined($opt_perfdata)) {
	print " |";
	# below is done to force notification on alert condition when you have notifications after 2 or more alerts
	if (scalar(keys %curr_perf)!=0 && (!defined($opt_prevstate) || scalar(keys %prev_perf)==0 || (defined($prev_state[0]) && $prev_state[0] ne 'OK' && (!defined($prev_state[1]) || $prev_state[1] eq 'HARD')))) {
		print " ". $_  ."=". $curr_perf{$_} foreach keys %curr_perf;
        }
	else {
		print " ". $_  ."=". $prev_perf{$_} foreach keys %prev_perf;
	}
	print " total_merr=".$total_merr if defined($total_merr);
	print " total_oerr=".$total_oerr if defined($total_oerr);
	if ($opt_debugtime) {
		print " time_".$_ ."=". $debug_time{$_} foreach keys %debug_time;
	}
   }
   print "\n";
}
