#!/usr/bin/perl -T
# nagios: -epn
#
#  Author: Hari Sekhon
#  Date: 2013-10-15 04:56:49 +0100 (Tue, 15 Oct 2013)
#
#  https://github.com/harisekhon/nagios-plugins
#
#  License: see accompanying LICENSE file
#  

$DESCRIPTION = "Nagios Plugin to fetch Cassandra's thread pool stats per node by parsing 'nodetool tpstats'.

Checks Pending/Blocked operations against warning/critical thresholds.
Check the baseline first and then set appropriate thresholds since a build up of Pending/Blocked operations is indicative of performance problems.

Also returns Active and Dropped operations with perfdata for graphing.

Can specify a remote host and port otherwise it checks the local node's stats (for calling over NRPE on each Cassandra node)

Tested on Cassandra 1.2.9, 2.0.1, 2.0.9, 2.2.5, 3.0.8, 3.5, 3.6, 3.7";

$VERSION = "0.7.0";

use strict;
use warnings;
BEGIN {
    use File::Basename;
    use lib dirname(__FILE__) . "/lib";
}
use HariSekhonUtils;
use HariSekhon::Cassandra::Nodetool;

set_threshold_defaults(0, 0);

%options = (
    %nodetool_options,
    %thresholdoptions,
);
splice @usage_order, 0, 0, 'nodetool';

get_options();

($nodetool, $host, $port, $user, $password) = validate_nodetool_options($nodetool, $host, $port, $user, $password);
validate_thresholds(1, 1, { "simple" => "upper", "integer" => 1, "positive" => 1 } );

vlog2;
set_timeout();

$status = "OK";

my $options = nodetool_options($host, $port, $user, $password);
my $cmd     = "${nodetool} ${options}tpstats";

vlog2 "fetching threadpool stats";
my @output = cmd($cmd);

foreach(@output){
    skip_nodetool_output($_) and next;
    check_nodetool_errors($_);
}
my $i = 0;
while(skip_nodetool_output($output[$i])){
    $i++;
}
$output[$i] =~ /Pool\s+Name\s+Active\s+Pending\s+Completed\s+Blocked\s+All time blocked\s*$/i or die_nodetool_unrecognized_output($output[$i]);
$i++;
my @stats;
foreach(; $i < scalar @output; $i++){
    $output[$i] =~ /^\s*$/ and $i++ and last;
    $output[$i] =~ /^([\w-]+(?:\s[A-Za-z]+)?)\s+(\d+)\s+(\d+)\s+(\d+)(?:\s+(\d+)\s+(\d+))?\s*$/ or die_nodetool_unrecognized_output($output[$i]);
    push(@stats,
        (
            { "$1_Blocked"          => $5, },
            { "$1_Pending"          => $3, },
            { "$1_Active"           => $2, },
            #{ "$1_Completed"        => $4, },
            #{ "$1_All_time_blocked" => $6, },
        )
    );
}
foreach(; $i < scalar @output; $i++){
    next if $output[$i] =~ /^\s*$/;
    last;
}

$output[$i] =~ /^Message type\s+Dropped/ or die_format_changed($output[$i]);
$i++;
my @stats2;
foreach(; $i < scalar @output; $i++){
    $output[$i] =~ /^(\w+)\s+(\d+)$/ or die_format_changed($output[$i]);
    push(@stats2,
        (
            { ucfirst(lc($1)) . "_Dropped" => $2 }
        )
    );
}

push(@stats2, @stats);

my $msg2;
my $msg3;
my ($thresholds_ok, $thresholds_msg);
foreach(my $i = 0; $i < scalar @stats2; $i++){
    foreach my $stat3 ($stats2[$i]){
        foreach my $key (keys %$stat3){
            $msg2 = "$key=$$stat3{$key} ";
            $msg3 .= $msg2;
            if($key =~ /Pending|Blocked/i){
                ($thresholds_ok, $thresholds_msg) = check_thresholds($$stat3{$key}, 1);
                unless($thresholds_ok){
                    $msg2 = uc $msg2;
                }
            }
            $msg .= $msg2;
        }
    }
}
$msg  =~ s/\s$//;
if($verbose or $status ne "OK"){
    msg_thresholds();
}
$msg .= "| $msg3";

vlog2;
quit $status, $msg;
