#!/usr/bin/perl

=head1 NAME 

check_jmx4perl - Nagios check using jmx4perl for accessing JMX information 

=head1 SYNOPSIS 

 check_jmx4perl --url=http://localhost:8888/j4p-agent \
                --name= memory_used \
                --mbean java.lang:type=Memory \
                --attribute HeapMemoryUsage \ 
                --path used \
                --critical 10000000 \
                --warning   5000000 

 check_jmx4perl --url=http://localhost:8888/j4p-agent \
                --list

=head1 DESCRIPTION

Command for providing Nagios conformant output for JMX response fetched via
L<JMX::Jmx4Perl>. It knows about critical (via C<--critical>) and warning (via
C<--warning>) thresholds. 

C<--list> lists all registered MBeans along with their attributes and
operations. You can provide an inner path with C<--path> as well. See
L<JMX::Jmx4Perl::Request> for an explanation about inner pathes (in short, it's
some sort of XPath expression which selects only a subset of all MBeans and
their values). See L<JMX::Jmx4Perl>, method "list()" for a more rigorous
documentation abouting listing of MBeans.

=cut

use FindBin qw ($Bin);
use lib qq($Bin/../lib);
use JMX::Jmx4Perl;
use JMX::Jmx4Perl::Request;
use JMX::Jmx4Perl::Response;
use strict;
use Data::Dumper;
use Nagios::Plugin;
use Time::HiRes qw(gettimeofday);

# Hack for avoiding a label in front of "OK" or "CRITICAL", in order to conform
# to the usual Nagios conventions
*Nagios::Plugin::Functions::get_shortname = sub {
    return undef;
};

my $np = Nagios::Plugin->
  new(
      usage => 
      "Usage: %s -u <agent-url> -m <mbean> -a <attribute> -c <threshold critical> -w <threshold warning> -n <label>\n" . 
      "                      [--user <user>] [--password <password>] [-v] [--help]\n",
      version => "0.01",
      url => "http://www.consol.com/opensource/nagios/",
      plugin => "check_jmx4perl",
      blurb => "This plugin checks for JMX attribute values on a remote Java application server",
      extra => "\n\nYou need to deploy j4p-agent.war on the target application server.\n" .
      "Please refer to the documentation for JMX::Jmx4Perl for further details"
     );
$np->shortname(undef);
$np->add_arg(
         spec => "url|u=s",
         help => "URL to agent web application (e.g. http://server:8080/j4p-agent/)",
         required => 1
        );
$np->add_arg(
         spec => "mbean|m=s",
         help => "MBean name (e.g. \"java.lang:type=Memory\")",
        );
$np->add_arg(
         spec => "attribute|a=s",
         help => "Attribute name (e.g. \"HeapMemoryUsage\")",
        );
$np->add_arg(
         spec => "path|p=s",
         help => "Inner path for extracting a single value from a complex attribute (e.g. \"used\")",
        );
$np->add_arg(
         spec => "critical|c=s",
         help => "Critical Threshold for value. " . 
         "See http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT " .
         "for the threshold format.",
        );
$np->add_arg(
         spec => "user=s",
         help => "User for HTTP authentication"
        );
$np->add_arg(
         spec => "password=s",
         help => "Password for HTTP authentication"
        );
$np->add_arg(
         spec => "warning|w=s",
         help => "Warning Threshold for value.",
        );
$np->add_arg(
         spec => "verbose|v",
         help => "Print verbose output "
        );
$np->add_arg(
             spec => "name|n=s",
             help => "Name to use for output. Optional, by default a standard value based on the MBean ".
             "and attribute will be used"
            );
$np->add_arg(
             spec => "list|l",
             help => "List MBean names along with there attributes and operations. An inner path can be given to only list a subset"
            );
$np->getopts();

my $o = $np->opts;

$np->nagios_die("An MBean name and a attribute must be provided")
  if (!$o->list && (!$o->mbean && !$o->attribute));

$np->nagios_die("At least a critical or warning threshold must be given") 
  if ((!defined($o->critical) && !defined($o->warning)) && !$o->list);


if (!$o->list) {
    $np->set_thresholds
      (
       $np->opts->critical ? (critical => $np->opts->critical) : (),
       $np->opts->warning ? (warning => $np->opts->warning) : ()
      );
}

eval {
    my $start_time = (gettimeofday)[1];
    my $jmx = JMX::Jmx4Perl->new(mode => "agent", url => $o->url, user => $o->user, password => $o->password);
    my $jmx_request;
    if ($o->list) {
        $jmx_request = JMX::Jmx4Perl::Request->new(LIST_MBEANS,$o->path);
    } else {
        $jmx_request = JMX::Jmx4Perl::Request->new(READ_ATTRIBUTE,$o->mbean,$o->attribute,$o->path);
    }
    if ($o->verbose) {
        print "Request URL: ",$jmx->request_url($jmx_request),"\n";
        if ($o->user) {
            print "Remote User: ",$o->user,"\n";
        }
    }
    my $resp = $jmx->request($jmx_request);
    if ($resp->is_error) {
       $np->nagios_die("Error: ".$resp->status." ".$resp->error_text."\nStacktrace:\n".$resp->stacktrace);
    }
    my $duration = int ((gettimeofday)[1] - $start_time) / 1000;

    # Show list of beans
    if ($o->list) {
        print $jmx->formatted_list($resp);
        exit(0);
    }

    $np->add_perfdata(label => "duration", value => $duration, uom => "ms");
    if (!defined($resp->value)) {
        $np->nagios_die("JMX Request " . &get_name($o) . " failed" . Dumper($resp));
    }
    if ($o->verbose) {
        print "Result fetched in ",$duration,"ms:\n";
        print Dumper($resp);
    }
    my $value = $resp->{value};
    if (ref($value)) { 
        $np->nagios_die("Response value is a ".ref($value).
                  ", not a plain value. Did you forget a --path parameter ?","Value: " . Dumper($value));
    }
    $np->add_perfdata(label => &get_name($o),value => $value, critical => $o->critical, warning => $o->warning);
    my $code = $np->check_threshold($value);    
    $np->nagios_exit($code,&get_name($o). " : Threshold " . ($code == CRITICAL ? $o->critical : $o->warning) . 
                     " failed for value $value") if $code != OK;
    $np->nagios_exit(OK,&get_name($o) . " : $value in range");
};
if ($@) {
    $np->nagios_die("Error: $@");
}

sub get_name { 
    my $o = shift;
    if ($o->name) {
        return $o->name;
    } else {
        # Default name
        return "[".$o->mbean.",".$o->attribute.($o->path ? "," . $o->path : "")."]";
    }
}

sub print_mbeans {
    my $resp = shift;
    
    
    print Dumper($resp->value);    
}

=head1 LICENSE

This file is part of jmx4perl.

Jmx4perl 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.

jmx4perl 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.

You should have received a copy of the GNU General Public License
along with jmx4perl.  If not, see <http://www.gnu.org/licenses/>.

=head1 AUTHOR

roland@cpan.org

=cut
