#! /usr/bin/perl
########################################################################
# Copyright (c) VIX TECHNOLOGY (AUST) LTD
#
# Module name   : blatNagios.pl
# Module type   : Nagios Plug in
# Compiler(s)   : Perl
# Environment(s): jats
#
# Description   : A Nagios plugin to interface to BLAT data
#                 This is a simple stand alone Perl Program
#                 It does not require the JATS environment
#
# Usage         : See POD at the end of this file
#
#......................................................................#


require 5.008_002;
use strict;
use warnings;
use Pod::Usage;
use Getopt::Long;
use File::Basename;
use Data::Dumper;
use File::Spec::Functions;
use FindBin;                                    # Determine the current directory

#
#   Globals
#
my $VERSION = "1.0.0";                      # Update this
my $opt_verbose = 1;
my $opt_help = 0;
my $opt_target;
my $opt_rundir = 'run';
my @opt_data;
my @opt_text;
my @opt_warn;
my %stats;
my $exitMsg = "OK";
my @exitMsgTxt;
my @perfData;
my $setWarning;
my $setCritical;

#-------------------------------------------------------------------------------
# Function        : Main Entry
#
# Description     :
#
# Inputs          :
#
# Returns         :
#
my $result = GetOptions (
                "help:+"        => \$opt_help,          # flag, multiple use allowed
                "manual:3"      => \$opt_help,          # flag
                "verbose:+"     => \$opt_verbose,       # flag
                "target=s"      => \$opt_target,        # String
                "rundir=s"      => \$opt_rundir,        # String
                "data=s"        => sub{push @opt_data, split(/\s*,\s*/,$_[1])},          # Strings
                "text=s"        => sub{push @opt_text, split(/\s*,\s*/,$_[1])},          # Strings
                "warn=s"        => sub{push @opt_warn, split(/\s*,\s*/,$_[1])},          # Strings
                );

#
#   Process help and manual options
#
pod2usage(-verbose => 0, -message => "Version: $VERSION")  if ($opt_help == 1  || ! $result);
pod2usage(-verbose => 1)  if ($opt_help == 2 );
pod2usage(-verbose => 1)  if ($opt_help > 2);

# Validate options
Error ("Target machine not specified") unless ($opt_target);

# Determine the 'run' directory
unless ($opt_rundir =~ m~^/~)
{
    $opt_rundir = catdir($FindBin::Bin , $opt_rundir);
}
Error ("Rundir not found: $opt_rundir") unless -d $opt_rundir;

#
# See if the named BLAT target is running
#   It must have a PID pid file
#
Error('BLAT daemon not running') unless testPid('blat');
Error('Transfer daemon not running') unless testPid($opt_target);

#
# Read in the statistics file
#   Format is is key:data
# 
my $statsFile = catfile( $opt_rundir, $opt_target . '.stats');
Warning('No Statistics available') unless -f $statsFile;
open my $fh, $statsFile || Error("Cannot read statistics");
while (<$fh>)
{
    m~(.*):(.*)~;
    $stats{lc($1)} = $2;
}
close $fh;

#
#   Determine the Nagios State. Will be CRITICAL if
#       state is NOT OK
#       timeStamp is more than 5 minutes old
#       
unless (defined $stats{state} && $stats{state} eq 'OK')
{
    $setCritical = 1;
    $exitMsg = $stats{state} || 'Unknown state' ;
}

my $dataAge = time() - $stats{timestamp};
if ($dataAge > (10 * 60))
{
    $setCritical = 1;
    $exitMsg = 'Data too old(Secs):' . $dataAge;
}

#
#   Insert Text data - not perf data.
#   This will be displayed as a part of the output
#
foreach my $item (@opt_text)
{
    push @exitMsgTxt, $item . '=' . getDataItem($item); 
}
#
#   Insert the required performance data
#   
foreach my $item (@opt_data)
{
    push @perfData, $item . '=' . getDataItem($item); 
}

#
#   Process warning thresholds
#
foreach my $item (@opt_warn)
{
    my ($Name, $warn, $critical) = split(/:/, $item);
    my $name = lc $Name;
    my $isCritical;
    if (exists $stats{$name})
    {
        my $value = int($stats{$name});
        if (defined $critical)
        {
            if (compareValues($value, $critical ))
            {
                $isCritical = 1;
                $setCritical = 1;
                push @exitMsgTxt, $Name . ' is Critical'; 

            }
        }
        if (! $isCritical)
        {
            if (defined $warn )
            {
                if (compareValues($value, $warn ))
                {
                    $setWarning = 1;
                    push @exitMsgTxt, $Name . ' is Warning'; 
                }
            }
            else
            {
                push @exitMsgTxt, "$item bad format";
                $setWarning = 1; 
            }
        }
    }
    else
    {
        push @exitMsgTxt, "$Name not known";
        $setWarning = 1; 
    }
}

#
# Prepare the output
#   STATUS, Status Text, Performance Data
# 
print($exitMsg);
print (' - ',join(', ', @exitMsgTxt)) if (@exitMsgTxt);
print ('|',join('; ', @perfData), ';') if (@perfData);
print("\n");
exit 2 if $setCritical;
exit 1 if $setWarning;
exit 0;

#-------------------------------------------------------------------------------
# Function        : expandSuffix 
#
# Description     : Convert text like 10Gb to a number 10000000000 
#                   Also removes '_' added to aid readability
#                   Support KB, MB, GB, TB and is case insensitive
#                   
#
# Inputs          : $text   - Test to convert
#
# Returns         : Converted number
#                   Will generate error
#
sub expandSuffix
{
    my ($text) = @_;
    my $number;
    $text =~ s~_~~g;

    if ($text =~ m~(\d+)T$~i) {
        $number = int($1) * 1000000000000;
    } elsif ($text =~ m~(\d+)G$~i) {
        $number = int($1) * 1000000000 ;
    } elsif ($text =~ m~(\d+)M$~i) {
        $number = int($1) * 1000000 ;
    } elsif ($text =~ m~(\d+)K$~i) {
        $number = int($1) * 1000 ;
    } elsif ($text =~ m~(\d+)$~i) {
        $number = int($1);
    } else {
        Error ("Number: $text not valid");
    }
    return $number;
}

#-------------------------------------------------------------------------------
# Function        : compareValues
#
# Description     : Compares two values
#
# Inputs          : $value          - Data from the machine
#                   $limit          - User limit
#                                     If '-' then sense of comparison is reversed 
#
# Returns         : True: value exceeds limit ( unless - ) 
#
sub compareValues
{
    my ($value, $limit) = @_;
    my $reverse = 0;
    if ( $limit =~ m~-(.*)~) {
        $reverse = 1;
        $limit = $1;
    }
    $limit = expandSuffix($limit);
    if ($reverse) {
        return $value < $limit
    } else {
        return $value > $limit
    }
}


#-------------------------------------------------------------------------------
# Function        : getDataItem 
#
# Description     : Get an item of statistical data 
#
# Inputs          : $name       - Name of the item to get 
#
# Returns         : The value of the item or the text 'Unknown'
#
sub getDataItem
{
    my ($name) = @_;
    $name = lc $name;
    return 'Unknown' unless exists $stats{$name};
    return $stats{$name}; 
}


#-------------------------------------------------------------------------------
# Function        : testPid 
#
# Description     : See if a PID exists    
#
# Inputs          : $name               - Name of pid file to examine
#
# Returns         : TRUE - looks OK
#
sub testPid
{
    my ($name) = @_;
    my $pidfile = catfile( $opt_rundir, $name . '.pid');
    open (my $fh, $pidfile) || return 0;
    my ($pid) = <$fh>;
    close $fh;

    if (defined $pid)
    {
        chomp $pid;
        $pid = int $pid;
        if ($pid > 0 )
        {
            # Brute force - unix specific check
            return 1 if -d catdir( '/proc', $pid);
        }
    }
    return 0;
}


#-------------------------------------------------------------------------------
# Function        : Error 
#
# Description     : Report an error to Nagios
#                   Returns an UNKNOWN exit code for Nagios
#
# Inputs          : Error string
#
# Returns         : Does not return
#

sub Error
{
    print("ERROR: @_\n");
    exit 3;
}

#-------------------------------------------------------------------------------
# Function        : Warning 
#
# Description     : Report an warning to Nagios
#                   Returns an WARNING exit code for Nagios
#
# Inputs          : Error string
#
# Returns         : Does not return
#

sub Warning
{
    print("Warning: @_\n");
    exit 1;
}


#-------------------------------------------------------------------------------
#   Documentation
#

=pod

=head1

blatNagios - Nagios Plugin for BLAT

=head1 SYNOPSIS

 blatNagios.pl [options]

 Options:
    -help              - brief help message
    -help -help        - Detailed help message
    -man               - Full documentation
    -target=name       - Target Machine name
    -rundir=path       - Alternate run directory
    -data=item         - Data item to report as performance data. Multiple allowed
    -text=item         - Data item to report as text. Multiple allowed
    -warn=item:vw:vc   - Report warnings for item. Multiple allowed

=head1 OPTIONS

=over 8

=item B<-help>

Print a brief help message and exits.

=item B<-help -help>

Print a detailed help message with an explanation for each option.

=item B<-man>

Prints the manual page and exits.

=item B<-target=name>

The name of the target BLAT server. This will be used to locate the PID and STATS files

=item B<-rundir=path>

The path to the 'run' directory in which the targets PID and STATS files are stored.

If a relative path is provided, then it is relative to this script.

The default value if 'run'.

=item B<-data=item>

One or more data items to be returned as a part of the performance data.

Multiple items are comma seperated. The argument may be used more than once.

=item B<-text=item>

One or more data items to be returned as a part of the text message.

Multiple items are comma seperated. The argument may be used more than once.

=item B<-warn=item:vw:vc>

Report a Nagios error state if the value of the named item exceeds the specified value.

vw is the warning threshold. vc is the critical threshold.

The warning and critical thresholds must be numeric with optional multipliers of K, M, G or T.

Multiple items are comma seperated. The argument may be used more than once.

=back

=head1 EXAMPLE

perl blatNagios.pl -target=auawsaarc001 -data=txCount,delCount -data=linkErrors -warn=txCount:40:50  -w=Target.avail:15GB:10GB

=cut

