Imported Upstream version 1.12

This commit is contained in:
Mario Fetka
2017-10-31 14:38:28 +01:00
commit ae1fc8494f
157 changed files with 35016 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
package JMX::Jmx4Perl::Nagios::CactiJmx4Perl;
use strict;
use base qw(JMX::Jmx4Perl::Nagios::CheckJmx4Perl);
use Data::Dumper;
=head1 NAME
JMX::Jmx4Perl::Nagios::CactiJmx4Perl - Module for encapsulating the functionality of
L<cacti_jmx4perl>
=head1 SYNOPSIS
# One line in check_jmx4perl to rule them all
JMX::Jmx4Perl::Nagios::CactiJmx4Perl->new(@ARGV)->execute();
=head1 DESCRIPTION
=cut
sub create_nagios_plugin {
my $self = shift;
my $np = Monitoring::Plugin->
new(
usage =>
"Usage: %s -u <agent-url> [-m <mbean>] [-a <attribute>]\n" .
" [--alias <alias>] [--value <shortcut>] [--base <alias/number/mbean>] [--delta <time-base>]\n" .
" [--name <name>] [--product <product>]\n".
" [--user <user>] [--password <password>] [--proxy <proxy>]\n" .
" [--target <target-url>] [--target-user <user>] [--target-password <password>]\n" .
" [--legacy-escape]\n" .
" [--config <config-file>] [--check <check-name>] [--server <server-alias>] [-v] [--help]\n" .
" arg1 arg2 ....",
version => $JMX::Jmx4Perl::VERSION,
url => "http://www.jmx4perl.org",
plugin => "cacti_jmx4perl",
license => undef,
blurb => "This script can be used as an script for a Cacti Data Input Method",
extra => "\n\nYou need to deploy jolokia.war on the target application server or an intermediate proxy.\n" .
"Please refer to the documentation for JMX::Jmx4Perl for further details.\n\n" .
"For a complete documentation please consult the man page of cacti_jmx4perl or use the option --doc"
);
$np->shortname(undef);
$self->add_common_np_args($np);
# Add dummy thresholds to keep Nagios plugin happy
$np->set_thresholds(warning => undef, critical => undef);
$np->getopts();
return $np;
}
sub verify_check {
# Not needed
}
sub do_exit {
my $self = shift;
my $error_stat = shift;
my $np = $self->{np};
my $perf = $np->perfdata;
my @res;
for my $p (@$perf) {
my $label = $p->label;
$label =~ s/\s/_/g;
push @res,@$perf > 1 ? $label . ":" . $p->value : $p->value;
}
print join(" ",@res),"\n";
exit 0;
}
=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
1;

View File

@@ -0,0 +1,979 @@
package JMX::Jmx4Perl::Nagios::CheckJmx4Perl;
use strict;
use warnings;
use JMX::Jmx4Perl::Nagios::SingleCheck;
use JMX::Jmx4Perl::Nagios::MessageHandler;
use JMX::Jmx4Perl;
use JMX::Jmx4Perl::Request;
use JMX::Jmx4Perl::Response;
use Data::Dumper;
use Monitoring::Plugin;
use Monitoring::Plugin::Functions qw(:codes %ERRORS %STATUS_TEXT);
use Time::HiRes qw(gettimeofday tv_interval);
use Carp;
use Text::ParseWords;
our $AUTOLOAD;
=head1 NAME
JMX::Jmx4Perl::Nagios::CheckJmx4Perl - Module for encapsulating the functionality of
L<check_jmx4perl>
=head1 SYNOPSIS
# One line in check_jmx4perl to rule them all
JMX::Jmx4Perl::Nagios::CheckJmx4Perl->new()->execute();
=head1 DESCRIPTION
The purpose of this module is to encapsulate a single run of L<check_jmx4perl>
in a perl object. This allows for C<check_jmx4perl> to run within the embedded
Nagios perl interpreter (ePN) wihout interfering with other, potential
concurrent, runs of this check. Please refer to L<check_jmx4perl> for
documentation on how to use this check. This module is probably I<not> of
general interest and serves only the purpose described above.
Its main task is to set up one ore more L<JMX::Jmx4Perl::Nagios::SingleCheck>
objects from command line arguments and optionally from a configuration file.
=head1 METHODS
=over
=item $check = new $JMX::Jmx4Perl::Nagios::CheckJmx4Perl()
Set up a object used for a single check. It will parse the command line
arguments and any configuation file given.
=cut
sub new {
my $class = shift;
my $self = { };
bless $self,(ref($class) || $class);
$self->{np} = $self->create_nagios_plugin();
$self->{cmd_args} = [ @ARGV ];
$self->_print_doc_and_exit($self->{np}->opts->{doc}) if defined $self->{np}->opts->{doc};
$self->_verify_and_initialize();
return $self;
}
=back
=head1 $check->execute()
Send the JMX request to the server monitored and print out a nagios output.
=cut
sub execute {
my $self = shift;
my $np = $self->{np};
eval {
# Request
my @optional = ();
my $error_stat = { };
my $target_config = $self->target_config;
my $jmx = JMX::Jmx4Perl->new(mode => "agent", url => $self->url, user => $self->user,
password => $self->password,
product => $self->product,
proxy => $self->proxy_config,
timeout => $np->opts->{timeout} || 180,
target => $target_config,
# For Jolokia agents < 1.0
'legacy-escape' => $self->legacy_escape);
my @requests;
for my $check (@{$self->{checks}}) {
push @requests,@{$check->get_requests($jmx,\@ARGV)};
}
my $responses = $self->_send_requests($jmx,@requests);
#print Dumper($responses);
my @extra_requests = ();
my $nr_checks = scalar(@{$self->{checks}});
if ($nr_checks == 1) {
eval {
my @r = $self->{checks}->[0]->extract_responses($responses,\@requests,{ target => $target_config });
push @extra_requests,@r if @r;
};
$self->nagios_die($@) if $@;
} else {
my $i = 1;
for my $check (@{$self->{checks}}) {
# A check can consume more than one response
my $prefix = $self->_multi_check_prefix($check,$i++,$nr_checks);
eval {
my @r = $check->extract_responses($responses,\@requests,
{
target => $target_config,
prefix => $prefix,
error_stat => $error_stat
});
push @extra_requests,@r if @r;
};
if ($@) {
my $txt = $@;
$txt =~ s/^(.*?)\n.*$/$1/s;
my $code = $np->opts->{'unknown-is-critical'} ? CRITICAL : UNKNOWN;
$check->update_error_stats($error_stat,$code);
$prefix =~ s/\%c/$STATUS_TEXT{$code}/g;
my $msg_handler = $np->{msg_handler} || $np;
$msg_handler->add_message($code,$prefix . $txt);
}
}
}
# Send extra requests, e.g. for switching on the history
if (@extra_requests) {
$self->_send_requests($jmx,@extra_requests);
}
# Different outputs for multi checks/single checks
$self->do_exit($error_stat);
};
if ($@) {
# p1.pl, the executing script of the embedded nagios perl interpreter
# uses this tag to catch an exit code of a plugin. We rethrow this
# exception if we detect this pattern.
if ($@ !~ /^ExitTrap:/) {
$self->nagios_die("Error: $@");
} else {
die $@;
}
}
}
=head1 $check->exit()
Write out result and exit. This method can be overridden to provide a custom
output, which can be extracted from NagiosPlugin object.
=cut
sub do_exit {
my $self = shift;
my $error_stat = shift;
my $np = $self->{np};
my $msg_handler = $np->{msg_handler} || $np;
my ($code,$message) = $msg_handler->check_messages(join => "\n", join_all => "\n");
($code,$message) = $self->_prepare_multicheck_message($np,$code,$message,$error_stat) if scalar(@{$self->{checks}}) > 1;
$np->nagios_exit($code, $message);
}
sub _prepare_multicheck_message {
my $self = shift;
my $np = shift;
my $code = shift;
my $message = shift;
my $error_stat = shift;
my $summary;
my $labels = $self->{multi_check_labels} || {};
my $nr_checks = scalar(@{$self->{checks}});
$code = $self->_check_for_UNKNOWN($error_stat,$code);
if ($code eq OK) {
$summary = $self->_format_multicheck_ok_summary($labels->{summary_ok} ||
"All %n checks OK",$nr_checks);
} else {
$summary = $self->_format_multicheck_failure_summary($labels->{summary_failure} ||
"%e of %n checks failed [%d]",
$nr_checks,
$error_stat);
}
return ($code,$summary . "\n" . $message);
}
# UNKNOWN shadows everything else
sub _check_for_UNKNOWN {
my $self = shift;
my $error_stat = shift;
my $code = shift;
return $error_stat->{UNKNOWN} && scalar(@$error_stat->{UNKNOWN}) ? UNKNOWN : $code;
}
sub _format_multicheck_ok_summary {
my $self = shift;
my $format = shift;
my $nr_checks = shift;
my $ret = $format;
$ret =~ s/\%n/$nr_checks/g;
return $ret;
}
sub _format_multicheck_failure_summary {
my $self = shift;
my $format = shift;
my $nr_checks = shift;
my $error_stat = shift;
my $ret = $format;
my $details = "";
my $total_errors = 0;
for my $code (UNKNOWN,CRITICAL,WARNING) {
if (my $errs = $error_stat->{$code}) {
$details .= scalar(@$errs) . " " . $STATUS_TEXT{$code} . " (" . join (",",@$errs) . "), ";
$total_errors += scalar(@$errs);
}
}
if ($total_errors > 0) {
# Cut off extra chars at the end
$details = substr($details,0,-2);
}
$ret =~ s/\%d/$details/g;
$ret =~ s/\%e/$total_errors/g;
$ret =~ s/\%n/$nr_checks/g;
return $ret;
}
# Create a formatted prefix for multicheck output
sub _multi_check_prefix {
my $self = shift;
my $check = shift;
my $idx = shift;
my $max = shift;
my $c = $check->{config};
my $l = length($max);
return sprintf("[%$l.${l}s] %%c ",$idx)
if (defined($c->{multicheckprefix}) && !length($c->{multicheckprefix}));
my $label = $c->{multicheckprefix} || $c->{name} || $c->{key} || "";
return sprintf("[%$l.${l}s] %%c %s: ",$idx,$label);
}
# Send the requests via the build up agent
sub _send_requests {
my ($self,$jmx,@requests) = @_;
my $o = $self->{opts};
my $start_time;
if ($o->verbose) {
# TODO: Print summary of request (GET vs POST)
if ($self->user) {
print "Remote User: ",$o->user,"\n";
}
$start_time = [gettimeofday];
}
# Detangle request for direct method calls and JMX requests to call:
my $req_map = $self->_detangle_requests(\@requests);
my @responses = ();
$self->_execute_scripts(\@responses,$req_map);
$self->_execute_requests(\@responses,$req_map,$jmx);
if ($o->verbose) {
print "Result fetched in ",tv_interval($start_time) * 1000," ms:\n";
print Dumper(\@responses);
}
return \@responses;
}
# Split up request for code-requests (i.e. scripts given in the configuration)
# and 'real' requests. Remember the index, too so that the response can be
# weave together
sub _detangle_requests {
my $self = shift;
my $requests = shift;
my $req_map = {};
my $idx = 0;
for my $r (@$requests) {
push @{$req_map->{ref($r) eq "CODE" ? "code" : "request"}},[$r,$idx];
$idx++;
}
return $req_map;
}
# Execute subrefs created out of scripts. Put it in the right place of the
# result array according to the remembered index
sub _execute_scripts {
my $self = shift;
my $responses = shift;
my $req_map = shift;
for my $e (@{$req_map->{"code"}}) {
# Will die on error which will bubble up
$responses->[$e->[1]] = &{$e->[0]}();;
}
}
# Execute requests and put it in the received responses in the right place for
# the returned array. The index has been extracted beforehand and stored in the
# given req_map
sub _execute_requests {
my $self = shift;
my $responses = shift;
my $req_map = shift;
my $jmx = shift;
# Call remote JMX and weave in
my $reqs2send = $req_map->{"request"};
if ($reqs2send) {
my @resp_received = $jmx->request(map { $_->[0] } @$reqs2send);
for my $r (@$reqs2send) {
$responses->[$r->[1]] = shift @resp_received;
}
}
}
# Print online manual and exit (somewhat crude, I know)
sub _print_doc_and_exit {
my $self = shift;
my $section = shift;
if (!eval "require Pod::Usage; Pod::Usage->import(qw(pod2usage)); 1;") {
print "Please install Pod::Usage for creating the online help\n";
exit 1;
}
if ($section) {
my %sects = (
tutorial => "TUTORIAL",
reference => "REFERENCE",
options => "COMMAND LINE",
config => "CONFIGURATION",
);
my $real_section = $sects{lc $section};
if ($real_section) {
pod2usage(-verbose => 99, -sections => $real_section );
} else {
print "Unknown documentation section '$section' (known: ",join (",",sort keys %sects),")\n";
exit 1;
}
} else {
pod2usage(-verbose => 99);
}
}
# Initialize this object and validate the mandatory parameters (obtained from
# the command line or a configuration file). It will also build up
# one or more SingleCheck which are later on sent as a bulk request to
# the server.
sub _verify_and_initialize {
my $self = shift;
my $np = $self->{np};
my $o = $np->opts;
$self->{opts} = $self->{np}->opts;
# Fetch configuration
my $config = $self->_get_config($o->config);
# Now, if a specific check is given, extract it, too.
my $check_configs;
#print Dumper($config);
$check_configs = $self->_extract_checks($config,$o->check);
#print Dumper($check_configs);
if ($check_configs) {
for my $c (@$check_configs) {
my $s_c = new JMX::Jmx4Perl::Nagios::SingleCheck($np,$c);
push @{$self->{checks}},$s_c;
}
} else {
$self->{checks} = [ new JMX::Jmx4Perl::Nagios::SingleCheck($np) ];
}
# If a server name is given, we use that for the connection parameters
if ($o->server) {
$self->{server_config} = $config->get_server_config($o->server)
|| $self->nagios_die("No server configuration for " . $o->server . " found");
}
# Sanity checks
$self->nagios_die("No Server URL given") unless $self->url;
for my $check (@{$self->{checks}}) {
my $name = $check->name ? " [Check: " . $check->name . "]" : "";
$self->nagios_die("An MBean name and a attribute/operation must be provided " . $name)
if ((!$check->mbean || (!$check->attribute && !$check->operation)) && !$check->alias && !$check->value && !$check->script);
}
}
# Extract one or more check configurations which can be
# simple <Check>s or <MultiCheck>s
sub _extract_checks {
my $self = shift;
my $config = shift;
my $check = shift;
my $np = $self->{np};
if ($check) {
$self->nagios_die("No configuration given") unless $config;
$self->nagios_die("No checks defined in configuration") unless $config->{check};
my $check_configs;
unless ($config->{check}->{$check}) {
$check_configs = $self->_resolve_multicheck($config,$check,$self->{cmd_args});
$self->_retrieve_mc_summary_label($config,$check);
} else {
my $check_config = $config->{check}->{$check};
$check_configs = ref($check_config) eq "ARRAY" ? $check_config : [ $check_config ];
$check_configs->[0]->{key} = $check;
}
$self->nagios_die("No check configuration with name " . $check . " found") unless (@{$check_configs});
#print Dumper($check_configs);
# Resolve parent values
for my $c (@{$check_configs}) {
#print "[A] ",Dumper($c);
$self->_resolve_check_config($c,$config,$self->{cmd_args});
#print "[B] ",Dumper($c);
# Finally, resolve any left over place holders
for my $k (keys(%$c)) {
$c->{$k} = $self->_replace_placeholder($c->{$k},undef) unless ref($c->{$k});
}
#print "[C] ",Dumper($c);
}
return $check_configs;
} else {
return undef;
}
}
# Resolve a multicheck configuration (<MultiCheck>)
sub _resolve_multicheck {
my $self = shift;
my $config = shift;
my $check = shift;
my $args = shift;
my $np = $self->{np};
my $multi_checks = $config->{multicheck};
my $check_config = [];
if ($multi_checks) {
my $m_check = $multi_checks->{$check};
if ($m_check) {
# Resolve all checks
my $c_names = [];
for my $type( qw(check multicheck)) {
if ($m_check->{$type}) {
push @$c_names, ref($m_check->{$type}) eq "ARRAY" ? @{$m_check->{$type}} : $m_check->{$type};
}
}
for my $name (@$c_names) {
my ($c_name,$c_args) = $self->_parse_check_ref($name);
my $args_merged = $self->_merge_multicheck_args($c_args,$args);
$self->nagios_die("Unknown check '" . $c_name . "' for multi check " . $check)
unless defined($config->{check}->{$c_name}) or defined($multi_checks->{$c_name});
if ($config->{check}->{$c_name}) {
# We need a copy of the check hash to avoid mangling it up
# if it is referenced multiple times
my $check = { %{$config->{check}->{$c_name}} };
$check->{key} = $c_name;
$check->{args} = $args_merged;
push @{$check_config},$check;
} else {
# It's a multi check referenced via <Check> or <MultiCheck> ....
push @{$check_config},@{$self->_resolve_multicheck($config,$c_name,$args_merged)};
}
}
}
}
return $check_config;
}
sub _retrieve_mc_summary_label {
my $self = shift;
my $config = shift;
my $check = shift;
my $multi_checks = $config->{multicheck};
if ($multi_checks) {
my $m_check = $multi_checks->{$check};
if ($m_check && ($m_check->{summaryok} || $m_check->{summaryfailure})) {
my $mc_labels =
$self->{multi_check_labels} = {
summary_ok => $m_check->{summaryok},
summary_failure => $m_check->{summaryfailure}
};
}
}
}
sub _merge_multicheck_args {
my $self = shift;
my $check_params = shift;
my $args = shift;
if (!$args || !$check_params) {
return $check_params;
}
my $ret = [ @$check_params ]; # Copy it over
for my $i (0 .. $#$check_params) {
if ($check_params->[$i] =~ /^\$(\d+)$/) {
my $j = $1;
if ($j <= $#$args) {
$ret->[$i] = $args->[$j];
next;
}
# Nothing to replace
$ret->[$i] = $check_params->[$i];
}
}
return $ret;
}
# Resolve a singe <Check> configuration
sub _resolve_check_config {
my $self = shift;
my $check = shift;
my $config = shift;
# Args can come from the outside, but also as part of a multicheck (stored
# in $check->{args})
my $args = $check->{args} && @{$check->{args}} ? $check->{args} : shift;
my $np = $self->{np};
if ($check->{use}) {
# Resolve parents
my $parents = ref($check->{use}) eq "ARRAY" ? $check->{use} : [ $check->{use} ];
my $parent_merged = {};
for my $p (@$parents) {
my ($p_name,$p_args) = $self->_parse_check_ref($p);
$self->nagios_die("Unknown parent check '" . $p_name . "' for check '" .
($check->{key} ? $check->{key} : $check->{name}) . "'")
unless $config->{check}->{$p_name};
# Clone it to avoid side effects when replacing checks inline
my $p_check = { %{$config->{check}->{$p_name}} };
$p_check->{key} = $p_name;
#print "::::: ",Dumper($p_check,$p_args);
$self->_resolve_check_config($p_check,$config,$p_args);
#$self->_replace_args($p_check,$config,$p_args);
$parent_merged->{$_} = $p_check->{$_} for keys %$p_check;
}
# Replace inherited values
for my $k (keys %$parent_merged) {
my $parent_val = defined($parent_merged->{$k}) ? $parent_merged->{$k} : "";
if (defined($check->{$k})) {
$check->{$k} =~ s/\$BASE/$parent_val/g;
} else {
$check->{$k} = $parent_val;
}
}
}
$self->_replace_args($check,$config,$args);
return $check;
}
# Replace argument placeholders with a given list of arguments
sub _replace_args {
my $self = shift;
my $check = shift;
my $config = shift;
my $args = shift;
for my $k (keys(%$check)) {
next if $k =~ /^(key|args)$/; # Internal keys
$check->{$k} =
$self->_replace_placeholder($check->{$k},$args)
if ($args && @$args && !ref($check->{$k}));
}
}
sub _replace_placeholder {
my $self = shift;
my $val = shift;
my $args = shift;
my $index = defined($args) ? join "|",0 ... $#$args : "\\d+";
my $regexp_s = <<'EOP';
^(.*?) # Start containing no args
\$( # Variable starts with '$'
($index) | # $0 without default value
\{\s*($index)\s* # ${0:12300} with default value
(?: :([^\}]+) )*\} # ?: --> clustering group, optional (${0} is also ok)
)
(.*|$) # The rest which will get parsed next
EOP
$regexp_s =~ s/\$index/$index/g;
my $regexp = qr/$regexp_s/sx;
die "Cannot create placeholder regexp" if $@;
my $rest = $val;
my $ret = "";
while (defined($rest) && length($rest) && $rest =~ $regexp) {
# $1: start with no placeholder
# $2: literal variable as it is defined
# $3: variable name (0,1,2,3,...)
# $4: same as $3, but either $3 or $4 is defined
# $5: default value (if any)
# $6: rest which is processed next in the loop
my $start = defined($1) ? $1 : "";
my $orig_val = '$' . $2;
my $i = defined($3) ? $3 : $4;
my $default = $5;
my $end = defined($6) ? $6 : "";
$default =~ s/^\s*(.*)+?\s*$/$1/ if $default; # Trim whitespace
#print Dumper({start => $start, orig => $orig_val,end => $end, default=> $default, rest => $rest, i => $i});
if (defined($args)) {
my $repl = $args->[$i];
if (defined($repl)) {
if ($repl =~ /^\$(\d+)$/) {
my $new_index = $1;
#print "============== $val $new_index\n";
# Val is a placeholder itself
if (defined($default)) {
$ret .= $start . '${' . $new_index . ':' . $default . '}';
} else {
$ret .= $start . '$' . $new_index;
}
} else {
$ret .= $start . $repl;
}
} else {
$ret .= $start . $orig_val;
}
} else {
# We have to replace any left over placeholder either with its
# default value or with an empty value
if (defined($default)) {
$ret .= $start . $default;
} elsif (length($start) || length($end)) {
$ret .= $start;
} else {
if (!length($ret)) {
# No default value, nothing else for this value. We
# consider it undefined
return undef;
}
}
}
$rest = $end;
#print "... $ret$rest\n";
}
return $ret . (defined($rest) ? $rest : "");
}
# Split up a 'Use' parent config reference, including possibly arguments
sub _parse_check_ref {
my $self = shift;
my $check_ref = shift;
if ($check_ref =~/^\s*(.+?)\((.*)\)\s*$/) {
my $name = $1;
my $args_s = $2;
my $args = [ parse_line('\s*,\s*',0,$args_s) ];
return ($name,$args);
} else {
return $check_ref;
}
}
# Get the configuration as a hash
sub _get_config {
my $self = shift;
my $path = shift;
my $np = $self->{np};
$self->nagios_die("No configuration file " . $path . " found")
if ($path && ! -e $path);
return new JMX::Jmx4Perl::Config($path);
}
# The global server config part
sub _server_config {
return shift->{server_config};
}
# Create the nagios plugin used for preparing the nagios output
sub create_nagios_plugin {
my $self = shift;
my $np = Monitoring::Plugin->
new(
usage =>
"Usage: %s -u <agent-url> -m <mbean> -a <attribute> -c <threshold critical> -w <threshold warning>\n" .
" [--alias <alias>] [--value <shortcut>] [--base <alias/number/mbean>] [--delta <time-base>]\n" .
" [--name <perf-data label>] [--label <output-label>] [--product <product>]\n".
" [--user <user>] [--password <password>] [--proxy <proxy>]\n" .
" [--target <target-url>] [--target-user <user>] [--target-password <password>]\n" .
" [--legacy-escape]\n" .
" [--config <config-file>] [--check <check-name>] [--server <server-alias>] [-v] [--help]\n" .
" arg1 arg2 ....",
version => $JMX::Jmx4Perl::VERSION,
url => "http://www.jmx4perl.org",
plugin => "check_jmx4perl",
blurb => "This plugin checks for JMX attribute values on a remote Java application server",
extra => "\n\nYou need to deploy jolokia.war on the target application server or an intermediate proxy.\n" .
"Please refer to the documentation for JMX::Jmx4Perl for further details.\n\n" .
"For a complete documentation please consult the man page of check_jmx4perl or use the option --doc"
);
$np->shortname(undef);
$self->add_common_np_args($np);
$self->add_nagios_np_args($np);
$np->{msg_handler} = new JMX::Jmx4Perl::Nagios::MessageHandler();
$np->getopts();
return $np;
}
sub add_common_np_args {
my $self = shift;
my $np = shift;
$np->add_arg(
spec => "url|u=s",
help => "URL to agent web application (e.g. http://server:8080/jolokia/)",
);
$np->add_arg(
spec => "product=s",
help => "Name of app server product. (e.g. \"jboss\")",
);
$np->add_arg(
spec => "alias=s",
help => "Alias name for attribte (e.g. \"MEMORY_HEAP_USED\")",
);
$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 => "operation|o=s",
help => "Operation to execute",
);
$np->add_arg(
spec => "value=s",
help => "Shortcut for specifying mbean/attribute/path. Slashes within names must be escaped with \\",
);
$np->add_arg(
spec => "delta|d:s",
help => "Switches on incremental mode. Optional argument are seconds used for normalizing.",
);
$np->add_arg(
spec => "path|p=s",
help => "Inner path for extracting a single value from a complex attribute or return value (e.g. \"used\")",
);
$np->add_arg(
spec => "target=s",
help => "JSR-160 Service URL specifing the target server"
);
$np->add_arg(
spec => "target-user=s",
help => "Username to use for JSR-160 connection (if --target is set)"
);
$np->add_arg(
spec => "target-password=s",
help => "Password to use for JSR-160 connection (if --target is set)"
);
$np->add_arg(
spec => "proxy=s",
help => "Proxy to use"
);
$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 => "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 => "legacy-escape!",
help => "Use legacy escape mechanism for Jolokia agents < 1.0"
);
$np->add_arg(
spec => "config=s",
help => "Path to configuration file. Default: ~/.j4p"
);
$np->add_arg(
spec => "server=s",
help => "Symbolic name of server url to use, which needs to be configured in the configuration file"
);
$np->add_arg(
spec => "check=s",
help => "Name of a check configuration as defined in the configuration file"
);
$np->add_arg(
spec => "method=s",
help => "HTTP method to use. Either \"get\" or \"post\""
);
$np->add_arg(
spec => "doc:s",
help => "Print the documentation of check_jmx4perl, optionally specifying the section (tutorial, args, config)"
);
}
sub add_nagios_np_args {
my $self = shift;
my $np = shift;
$np->add_arg(
spec => "base|base-alias|b=s",
help => "Base name, which when given, interprets critical and warning values as relative in the range 0 .. 100%. Must be given in the form mbean/attribute/path",
);
$np->add_arg(
spec => "base-mbean=s",
help => "Base MBean name, interprets critical and warning values as relative in the range 0 .. 100%. Requires a base-attribute, too",
);
$np->add_arg(
spec => "base-attribute=s",
help => "Base attribute for a relative check. Used together with base-mbean",
);
$np->add_arg(
spec => "base-path=s",
help => "Base path for relatie checks, where this path is used on the base attribute's value",
);
$np->add_arg(
spec => "unit=s",
help => "Unit of measurement of the data retreived. Recognized values are [B|KB|MN|GB|TB] for memory values and [us|ms|s|m|h|d] for time values"
);
$np->add_arg(
spec => "null=s",
help => "Value which should be used in case of a null return value of an operation or attribute. Is \"null\" by default"
);
$np->add_arg(
spec => "string",
help => "Force string comparison for critical and warning checks"
);
$np->add_arg(
spec => "numeric",
help => "Force numeric comparison for critical and warning checks"
);
$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 => "warning|w=s",
help => "Warning Threshold for value.",
);
$np->add_arg(
spec => "label|l=s",
help => "Label to be used for printing out the result of the check. Placeholders can be used."
);
$np->add_arg(
spec => "perfdata=s",
help => "Whether performance data should be omitted, which are included by default."
);
$np->add_arg(
spec => "unknown-is-critical",
help => "Map UNKNOWN errors to errors with a CRITICAL status"
);
}
# Access to configuration informations
# Known config options (key: cmd line arguments, values: keys in config);
my $SERVER_CONFIG_KEYS = {
"url" => "url",
"user" => "user",
"password" => "password",
"product" => "product",
"legacy_escape" => "legacyconfig"
};
# Get target configuration or undef if no jmx-proxy mode
# is used
sub target_config {
return shift->_target_or_proxy_config("target","target-user","target-password");
}
# Get proxy configuration or undef if no proxy configuration
# is used
sub proxy_config {
return shift->_target_or_proxy_config("proxy","proxy-user","proxy-password");
}
sub _target_or_proxy_config {
my $self = shift;
my $main_key = shift;
my $user_opt = shift;
my $password_opt = shift;
my $np = $self->{np};
my $opts = $np->opts;
my $server_config = $self->_server_config;
if ($opts->{$main_key}) {
# Use configuration from the command line:
return {
url => $opts->{$main_key},
user => $opts->{$user_opt},
password => $opts->{$password_opt}
}
} elsif ($server_config && $server_config->{$main_key}) {
# Use configuration directly from the server definition:
return $server_config->{$main_key}
} else {
return undef;
}
}
# Autoloading is used to fetch the proper connection parameters
sub AUTOLOAD {
my $self = shift;
my $np = $self->{np};
my $name = $AUTOLOAD;
$name =~ s/.*://; # strip fully-qualified portion
my $opts_name = $name;
$opts_name =~ s/_/-/;
if ($SERVER_CONFIG_KEYS->{$name}) {
return $np->opts->{$opts_name} if $np->opts->{$opts_name};
my $c = $SERVER_CONFIG_KEYS->{$name};
if ($c) {
my @parts = split "/",$c;
my $h = $self->_server_config ||
return undef;
while (@parts) {
my $p = shift @parts;
return undef unless $h->{$p};
$h = $h->{$p};
return $h unless @parts;
}
} else {
return undef;
}
} else {
$self->nagios_die("No config attribute \"" . $name . "\" known");
}
}
sub nagios_die {
my $self = shift;
my @args = @_;
my $np = $self->{np};
$np->nagios_die(join("",@args),$np->opts->{'unknown-is-critical'} ? CRITICAL : UNKNOWN)
}
# Declared here to avoid AUTOLOAD confusions
sub DESTROY {
}
=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
1;

View File

@@ -0,0 +1,60 @@
package JMX::Jmx4Perl::Nagios::MessageHandler;
use Monitoring::Plugin::Functions qw(:codes %ERRORS %STATUS_TEXT);
use strict;
=head1 NAME
JMX::Jmx4Perl::Nagios::MessageHandler - Handling Nagios exit message (one or
many)
=cut
sub new {
my $class = shift;
my $self = {
messages => {}
};
bless $self,(ref($class) || $class);
return $self;
}
sub add_message {
my $self = shift;
my ($code,@messages) = @_;
die "Invalid error code '$code'\n"
unless defined($ERRORS{uc $code}) || defined($STATUS_TEXT{$code});
# Store messages using strings rather than numeric codes
$code = $STATUS_TEXT{$code} if $STATUS_TEXT{$code};
$code = lc $code;
$self->{messages}->{$code} = [] unless $self->{messages}->{$code};
push @{$self->{messages}->{$code}}, @messages;
}
sub check_messages {
my $self = shift;
my %arg = @_;
for my $code (qw(critical warning ok unknown)) {
$arg{$code} = $self->{messages}->{$code} || [];
}
my $code = OK;
$code ||= UNKNOWN if @{$arg{unknown}};
$code ||= CRITICAL if @{$arg{critical}};
$code ||= WARNING if @{$arg{warning}};
my $message = join( "\n",
map { @$_ ? join( "\n", @$_) : () }
$arg{unknown},
$arg{critical},
$arg{warning},
$arg{ok} ? (ref $arg{ok} ? $arg{ok} : [ $arg{ok} ]) : []
);
return ($code, $message);
}
1;

View File

@@ -0,0 +1,821 @@
package JMX::Jmx4Perl::Nagios::SingleCheck;
use strict;
use warnings;
use JMX::Jmx4Perl;
use JMX::Jmx4Perl::Request;
use JMX::Jmx4Perl::Response;
use JMX::Jmx4Perl::Alias;
use Data::Dumper;
use Monitoring::Plugin;
use Monitoring::Plugin::Functions qw(:codes %STATUS_TEXT);
use Carp;
use Scalar::Util qw(looks_like_number);
use URI::Escape;
use Text::ParseWords;
use JSON;
our $AUTOLOAD;
=head1 NAME
JMX::Jmx4Perl::Nagios::SingleCheck - A single nagios check
This is an package used internally by
L<JMX::Jmx4Perl::Nagios::CheckJmx4Perl>. It encapsulates the configuration for
single checks, which can be combined to a bulk JMX-Request so only a single
server turnaround is used to obtain multiple checks results at once.
=head1 METHODS
=over
=item $single_check = new $JMX::Jmx4Perl::Nagios::SingleCheck($nagios_plugin,$check_config)
Construct a new single check from a given L<Monitoring::Plugin> object
C<$nagios_plugin> and a parsed check configuration $check_config, which is a
hash.
=cut
sub new {
my $class = shift;
my $np = shift || die "No Nagios Plugin given";
my $config = shift;
my $self = {
np => $np,
config => $config
};
bless $self,(ref($class) || $class);
return $self;
}
=item $requests = $single_check->get_requests($jmx,$args)
Called to obtain an arrayref of L<JMX::Jmx4Perl::Request> objects which should
be send to the server agent. C<$jmx> ist the L<JMX::Jmx4Perl> agent, C<$args>
are additonal arguments used for exec-operations,
Multiple request object are returned e.g. if a relative check has to be
performed in order to get the base value as well.
The returned array can contain coderefs which should be executed directly and
its return value should be used in order to perfoorm the check.
=cut
sub get_requests {
my $self = shift;
my $jmx = shift;
my $args = shift;
# If a script is given, extract a subref and return it
return [ $self->_extract_script_as_subref($jmx) ] if $self->script;
my $do_read = $self->attribute || $self->value;
my $do_exec = $self->operation;
if ($self->alias) {
my $alias = JMX::Jmx4Perl::Alias->by_name($self->alias);
die "No alias '",$self->alias," known" unless $alias;
$do_read = $alias->type eq "attribute";
}
my @requests = ();
my $request;
if ($do_read) {
$request = JMX::Jmx4Perl::Request->new(READ,$self->_prepare_read_args($jmx));
} elsif ($do_exec) {
$request = JMX::Jmx4Perl::Request->new(EXEC,$self->_prepare_exec_args($jmx,@$args));
} else {
die "Neither an attribute/value, an operation or a script given";
}
my $method = $self->{np}->opts->{method} || $self->{config}->{method};
if ($method) {
$request->method($method);
}
push @requests,$request;
if ($self->base || $self->base_mbean) {
if (!looks_like_number($self->base)) {
# It looks like a number, so we will use the base literally
my $alias;
if ($self->base) {
$alias = JMX::Jmx4Perl::Alias->by_name($self->base);
}
if ($alias) {
push @requests,new JMX::Jmx4Perl::Request(READ,$jmx->resolve_alias($self->base));
} else {
my ($mbean,$attr,$path) = $self->base_mbean ?
($self->base_mbean, $self->base_attribute, $self->base_path) :
$self->_split_attr_spec($self->base);
die "No MBean given in base name ",$self->base unless $mbean;
die "No Attribute given in base name ",$self->base unless $attr;
$mbean = URI::Escape::uri_unescape($mbean);
$attr = URI::Escape::uri_unescape($attr);
$path = URI::Escape::uri_unescape($path) if $path;
push @requests,new JMX::Jmx4Perl::Request(READ,$mbean,$attr,$path);
}
}
}
return \@requests;
}
# Create a subref where all params from the outside are available as closures.
sub _extract_script_as_subref {
my $self = shift;
my $jmx = shift;
my $script = $self->script || die "No script given";
my $full_script = <<"EOT";
sub {
my \$j4p = shift;
return sub {
$script
}
}
EOT
#print $full_script,"\n";
my $sub = eval $full_script;
die "Cannot eval script for check ",$self->name,": $@" if $@;
return &$sub($jmx);
}
=item $single_check->exract_responses($responses,$requests,$target)
Extract L<JMX::Jmx4Perl::Response> objects and add the deducted results to
the nagios plugin (which was given at construction time).
C<$responses> is an arrayref to the returned responses, C<$requests> is an
arrayref to the original requests. Any response consumed from C<$requests>
should be removed from the array, as well as the corresponding request.
The requests/responses for this single request are always a the beginning of
the arrays.
C<$target> is an optional target configuration if the request was used in
target proxy mode.
=cut
sub extract_responses {
my $self = shift;
my $responses = shift;
my $requests = shift;
my $opts = shift || {};
my $np = $self->{np};
my $msg_handler = $np->{msg_handler} || $np;
# Get response/request pair
my $resp = shift @{$responses};
my $request = shift @{$requests};
#print Dumper($resp);
my @extra_requests = ();
my $value;
my $script_mode = undef;
if (ref($request) eq "CODE") {
# It's a script, so the 'response' is already the value
$script_mode = 1;
$value = $resp;
} else {
$self->_verify_response($request,$resp);
$value = $self->_extract_value($request,$resp);
}
# Delta handling
my $delta = $self->delta;
if (defined($delta) && !$script_mode) {
$value = $self->_delta_value($request,$resp,$delta);
unless (defined($value)) {
push @extra_requests,$self->_switch_on_history($request,$opts->{target});
$value = 0;
}
}
# Normalize value
my ($value_conv,$unit) = $self->_normalize_value($value);
my $label = $self->_get_name(cleanup => 1);
if ( ($self->base || $self->base_mbean) && !$script_mode) {
# Calc relative value
my $base_value = $self->_base_value($self->base,$responses,$requests);
my $rel_value = sprintf "%2.2f",$base_value ? (int((($value / $base_value) * 10000) + 0.5) / 100) : 0;
# Performance data. Convert to absolute values before
if ($self->_include_perf_data) {
if ($self->perfdata && $self->perfdata =~ /^\s*\%\s*/) {
$np->add_perfdata(label => $label, value => $rel_value, uom => '%',
critical => $self->critical, warning => $self->warning);
} else {
my ($critical,$warning) = $self->_convert_relative_to_absolute($base_value,$self->critical,$self->warning);
$np->add_perfdata(label => $label,value => $value,
critical => $critical,warning => $warning,
min => 0,max => $base_value,
$self->unit ? (uom => $self->unit) : ());
}
}
# Do the real check.
my ($code,$mode) = $self->_check_threshold($rel_value);
# For Multichecks, we remember the label of a currently failed check
$self->update_error_stats($opts->{error_stat},$code) unless $code == OK;
my ($base_conv,$base_unit) = $self->_normalize_value($base_value);
$msg_handler->add_message($code,$self->_exit_message(code => $code,mode => $mode,rel_value => $rel_value,
value => $value_conv, unit => $unit, base => $base_conv,
base_unit => $base_unit, prefix => $opts->{prefix}));
} else {
# Performance data
$value = $self->_sanitize_value($value);
if ($self->_include_perf_data) {
$np->add_perfdata(label => $label,
critical => $self->critical, warning => $self->warning,
value => $value,$self->unit ? (uom => $self->unit) : ());
}
# Do the real check.
my ($code,$mode) = $self->_check_threshold($value);
$self->update_error_stats($opts->{error_stat},$code) unless $code == OK;
$msg_handler->add_message($code,$self->_exit_message(code => $code,mode => $mode,value => $value_conv, unit => $unit,
prefix => $opts->{prefix}));
}
return @extra_requests;
}
sub _include_perf_data {
my $self = shift;
# No perf dara for string based checks by default
my $default = not defined($self->string);
# If 'PerfData' is set explicitely to false/off/no/0 then no perfdata
# will be included
return $default unless defined($self->perfdata);
return $self->perfdata !~ /^\s*(false|off|no|0)\s*$/i;
}
sub update_error_stats {
my $self = shift;
my $error_stat = shift || return;
my $code = shift;
my $label = $self->{config}->{name} || $self->{config}->{key};
if ($label) {
my $arr = $error_stat->{$code} || [];
push @$arr,$label;
$error_stat->{$code} = $arr;
}
}
# Extract a single value, which is different, if the request was a pattern read
# request
sub _extract_value {
my $self = shift;
my $req = shift;
my $resp = shift;
if ($req->get('type') eq READ && $req->is_mbean_pattern) {
return $self->_extract_value_from_pattern_request($resp->value);
} else {
return $self->_null_safe_value($resp->value);
}
}
sub _null_safe_value {
my $self = shift;
my $value = shift;
if (defined($value)) {
if (JSON::is_bool($value)) {
return $value ? "true" : "false";
} elsif (ref($value) && $self->string) {
# We can deal with complex values withing string comparison
if (ref($value) eq "ARRAY") {
return join ",",@{$value};
} else {
return Dumper($value);
}
} else {
return $value;
}
} else {
# Our null value
return defined($self->null) ? $self->null : "null";
}
}
sub _extract_value_from_pattern_request {
my $self = shift;
my $val = shift;
my $np = $self->{np};
$self->_die("Pattern request does not result in a proper return format: " . Dumper($val))
if (ref($val) ne "HASH");
$self->_die("More than one MBean found for a pattern request: " . Dumper([keys %$val])) if keys %$val != 1;
my $attr_val = (values(%$val))[0];
$self->_die("Invalid response for pattern match: " . Dumper($attr_val)) unless ref($attr_val) eq "HASH";
$self->_die("Only a single attribute can be used. Given: " . Dumper([keys %$attr_val])) if keys %$attr_val != 1;
return $self->_null_safe_value((values(%$attr_val))[0]);
}
sub _delta_value {
my ($self,$req,$resp,$delta) = @_;
my $history = $resp->history;
if (!$history) {
# No delta on the first run
return undef;
} else {
my $hist_val;
if ($req->is_mbean_pattern) {
$hist_val = $self->_extract_value_from_pattern_request($history);
} else {
$hist_val = $history;
}
if (!@$hist_val) {
# Can happen in some scenarios when requesting the first history entry,
# we return 0 here
return 0;
}
my $old_value = $hist_val->[0]->{value};
my $old_time = $hist_val->[0]->{timestamp};
my $value = $self->_extract_value($req,$resp);
if ($delta) {
# Time average
my $time_delta = $resp->timestamp - $old_time;
return (($value - $old_value) / ($time_delta ? $time_delta : 1)) * $delta;
} else {
return $value - $old_value;
}
}
}
sub _switch_on_history {
my ($self,$orig_request,$target) = @_;
my ($mbean,$operation) = ("jolokia:type=Config","setHistoryEntriesForAttribute");
# Set history to 1 (we need only the last)
return new JMX::Jmx4Perl::Request
(EXEC,$mbean,$operation,
$orig_request->get("mbean"),$orig_request->get("attribute"),$orig_request->get("path"),
$target ? $target->{url} : undef,1,{target => undef});
}
sub _base_value {
my $self = shift;
my $np = $self->{np};
my $name = shift;
my $responses = shift;
my $requests = shift;
if (looks_like_number($name)) {
# It looks like a number, so we suppose its the base value itself
return $name;
}
my $resp = shift @{$responses};
my $req = shift @{$requests};
$self->_die($resp->{error}) if $resp->{error};
#print Dumper($req,$resp);
return $self->_extract_value($req,$resp);
}
# Normalize value if a unit-of-measurement is given.
# Units and how to convert from one level to the next
my @UNITS = ([ qw(ns us ms s m h d) ],[qw(B KB MB GB TB)]);
my %UNITS =
(
ns => 1,
us => 10**3,
ms => 10**3,
s => 10**3,
m => 60,
h => 60,
d => 24,
B => 1,
KB => 2**10,
MB => 2**10,
GB => 2**10,
TB => 2**10
);
sub _normalize_value {
my $self = shift;
my $value = shift;
my $unit = shift || $self->unit || return ($value,undef);
for my $units (@UNITS) {
for my $i (0 .. $#{$units}) {
next unless $units->[$i] eq $unit;
my $ret = $value;
my $u = $unit;
if (abs($ret) > 1) {
# Go up the scale ...
return ($value,$unit) if $i == $#{$units};
for my $j ($i+1 .. $#{$units}) {
if (abs($ret / $UNITS{$units->[$j]}) >= 1) {
$ret /= $UNITS{$units->[$j]};
$u = $units->[$j];
} else {
return ($ret,$u);
}
}
} else {
# Go down the scale ...
return ($value,$unit) if $i == 0;
for my $j (reverse(0 .. $i-1)) {
if ($ret < 1) {
$ret *= $UNITS{$units->[$j+1]};
$u = $units->[$j];
} else {
return ($ret,$u);
}
}
}
return ($ret,$u);
}
}
die "Unknown unit '$unit' for value $value";
}
sub _sanitize_value {
my ($self,$value) = @_;
if ($value =~ /\de/i) {
$value = sprintf("%f", $value);
}
return $value;
}
sub _verify_response {
my ($self,$req,$resp) = @_;
my $np = $self->{np};
if ($resp->is_error) {
my $extra = "";
if ($np->opts->{verbose}) {
my $stacktrace = $resp->stacktrace;
$extra = ref($stacktrace) eq "ARRAY" ? join "\n",@$stacktrace : $stacktrace if $stacktrace;
}
$self->_die("Error: ".$resp->status." ".$resp->error_text.$extra);
}
if (!$req->is_mbean_pattern && (ref($resp->value) && !$self->string) && !JSON::is_bool($resp->value)) {
$self->_die("Response value is a " . ref($resp->value) .
", not a plain value. Did you forget a --path parameter ?". " Value: " .
Dumper($resp->value));
}
}
sub _get_name {
my $self = shift;
my $args = { @_ };
my $name = $args->{name};
if (!$name) {
if ($self->name) {
$name = $self->name;
} else {
# Default name, tried to be generated from various parts
if ($self->alias) {
$name = "[".$self->alias.($self->path ? "," . $self->path : "") ."]";
} else {
my $val = $self->value;
if ($val) {
$name = "[" . $val . "]";
} else {
my $a_or_o = $self->attribute || $self->operation || "";
my $p = $self->path ? "," . $self->path : "";
$name = "[" . $self->mbean . "," . $a_or_o . $p . "]";
}
}
}
}
if ($args->{cleanup}) {
# Enable this when '=' gets forbidden
$name =~ s/=/#/g;
}
# Prepare label for usage with Monitoring::Plugin, which will blindly
# add quotes if a space is contained in the label.
# We are doing the escape of quotes ourself here
$name =~ s/'/''/g;
return $name;
}
sub _prepare_read_args {
my $self = shift;
my $np = $self->{np};
my $jmx = shift;
if ($self->alias) {
my @req_args = $jmx->resolve_alias($self->alias);
$self->_die("Cannot resolve attribute alias ",$self->alias()) unless @req_args > 0;
if ($self->path) {
@req_args == 2 ? $req_args[2] = $self->path : $req_args[2] .= "/" . $self->path;
}
return @req_args;
} elsif ($self->value) {
return $self->_split_attr_spec($self->value);
} else {
return ($self->mbean,$self->attribute,$self->path);
}
}
sub _prepare_exec_args {
my $self = shift;
my $np = $self->{np};
my $jmx = shift;
#print Dumper($self->{config});
# Merge CLI arguments and arguments from the configuration,
# with CLI arguments taking precedence
my @cli_args = @_;
my $config_args = $self->{config}->{argument};
$config_args = [ $config_args ] if defined($config_args) && ref($config_args) ne "ARRAY";
my @args = ();
if ($config_args) {
my @c_args = (@$config_args);
while (@cli_args || @c_args) {
my $cli_arg = shift @cli_args;
my $config_arg = shift @c_args;
push @args, defined($cli_arg) ? $cli_arg : $config_arg;
}
} else {
@args = @cli_args;
}
if ($self->alias) {
my @req_args = $jmx->resolve_alias($self->alias);
$self->_die("Cannot resolve operation alias ",$self->alias()) unless @req_args >= 2;
return (@req_args,@args);
} else {
return ($self->mbean,$self->operation,@args);
}
}
sub _split_attr_spec {
my $self = shift;
my $name = shift;
my @ret = ();
# Text:ParseWords is used for split on "/" taking into account
# quoting and escaping
for my $p (parse_line("/",1,$name)) {
# We need to 'unescape' things ourselves
# since we want quotes to remain in the names (using '0'
# above would kill those quotes, too).
$p =~ s|\\(.)|$1|sg;
push @ret,$p;
}
return (shift(@ret),shift(@ret),@ret ? join("/",@ret) : undef);
}
sub _check_threshold {
my $self = shift;
my $value = shift;
my $np = $self->{np};
my $numeric_check;
if ($self->numeric || $self->string) {
$numeric_check = $self->numeric ? 1 : 0;
} else {
$numeric_check = looks_like_number($value);
}
if ($numeric_check) {
# Verify numeric thresholds
my @ths =
(
defined($self->critical) ? (critical => $self->critical) : (),
defined($self->warning) ? (warning => $self->warning) : ()
);
#print Dumper({check => $value,@ths});
return (@ths ? $np->check_threshold(check => $value,@ths) : OK,"numeric");
} else {
return
($self->_check_string_threshold($value,CRITICAL,$self->critical) ||
$self->_check_string_threshold($value,WARNING,$self->warning) ||
OK,
$value =~ /^true|false$/i ? "boolean" : "string");
}
}
sub _check_string_threshold {
my $self = shift;
my ($value,$level,$check_value) = @_;
return undef unless $check_value;
if ($check_value =~ m|^\s*qr(.)(.*)\1\s*$|) {
return $value =~ m/$2/ ? $level : undef;
}
if ($check_value =~ s/^\!//) {
return $value ne $check_value ? $level : undef;
} else {
return $value eq $check_value ? $level : undef;
}
}
sub _convert_relative_to_absolute {
my $self = shift;
my ($base_value,@to_convert) = @_;
my @ret = ();
for my $v (@to_convert) {
$v =~ s|([\d\.]+)|($1 / 100) * $base_value|eg if $v;
push @ret,$v;
}
return @ret;
}
# Prepare an exit message depending on the result of
# the check itself. Quite evolved, you can overwrite this always via '--label'.
sub _exit_message {
my $self = shift;
my $args = { @_ };
# Custom label has precedence
return $self->_format_label($self->label,$args) if $self->label;
my $code = $args->{code};
my $mode = $args->{mode};
if ($code == CRITICAL || $code == WARNING) {
if ($self->base || $self->base_mbean) {
return $self->_format_label
('%n : Threshold \'%t\' failed for value %.2r% ('. &_placeholder($args,"v") .' %u / '.
&_placeholder($args,"b") . ' %u)',$args);
} else {
if ($mode ne "numeric") {
return $self->_format_label('%n : \'%v\' matches threshold \'%t\'',$args);
} else {
return $self->_format_label
('%n : Threshold \'%t\' failed for value '.&_placeholder($args,"v").' %u',$args);
}
}
} else {
if ($self->base || $self->base_mbean) {
return $self->_format_label('%n : In range %.2r% ('. &_placeholder($args,"v") .' %u / '.
&_placeholder($args,"b") . ' %w)',$args);
} else {
if ($mode ne "numeric") {
return $self->_format_label('%n : \'%v\' as expected',$args);
} else {
return $self->_format_label('%n : Value '.&_placeholder($args,"v").' %u in range',$args);
}
}
}
}
sub _placeholder {
my ($args,$c) = @_;
my $val;
if ($c eq "v") {
$val = $args->{value};
} else {
$val = $args->{base};
}
return ($val =~ /\./ ? "%.2" : "%") . $c;
}
sub _format_label {
my $self = shift;
my $label = shift;
my $args = shift;
# %r : relative value (as percent)
# %q : relative value (as floating point)
# %v : value
# %f : value as floating point
# %u : unit
# %b : base value
# %w : base unit
# %t : threshold failed ("" for OK or UNKNOWN)
# %c : code ("OK", "WARNING", "CRITICAL", "UNKNOWN")
# %d : delta
#
my @parts = split /(\%[\w\.\-]*\w)/,$label;
my $ret = "";
foreach my $p (@parts) {
if ($p =~ /^(\%[\w\.\-]*)(\w)$/) {
my ($format,$what) = ($1,$2);
if ($what eq "r" || $what eq "q") {
my $val = $args->{rel_value} || 0;
$val = $what eq "r" ? $val : $val / 100;
$ret .= sprintf $format . "f",$val;
} elsif ($what eq "b") {
$ret .= sprintf $format . &_format_char($args->{base}),($args->{base} || 0);
} elsif ($what eq "u" || $what eq "w") {
$ret .= sprintf $format . "s",($what eq "u" ? $args->{unit} : $args->{base_unit}) || "";
$ret =~ s/\s$//;
} elsif ($what eq "f") {
$ret .= sprintf $format . "f",$args->{value};
} elsif ($what eq "v") {
$ret .= &_format_value($format,$args->{mode},$args->{value});
} elsif ($what eq "t") {
my $code = $args->{code};
my $val = $code == CRITICAL ? $self->critical : ($code == WARNING ? $self->warning : "");
$ret .= sprintf $format . "s",defined($val) ? $val : "";
} elsif ($what eq "c") {
$ret .= sprintf $format . "s",$STATUS_TEXT{$args->{code}};
} elsif ($what eq "n") {
$ret .= sprintf $format . "s",$self->_get_name();
} elsif ($what eq "d") {
$ret .= sprintf $format . "d",$self->delta;
} elsif ($what eq "y") {
$ret .= &_format_value($format,$args->{mode},$self->warning);
} elsif ($what eq "z") {
$ret .= &_format_value($format,$args->{mode},$self->critical);
}
} else {
$ret .= $p;
}
}
if ($args->{prefix}) {
my $prefix = $args->{prefix};
$prefix =~ s/\%c/$STATUS_TEXT{$args->{code}}/g;
return $prefix . $ret;
} else {
return $ret;
}
}
sub _format_value {
my $format = shift;
my $mode = shift;
my $value = shift;
if ($mode ne "numeric") {
return sprintf $format . "s",$value;
} else {
return sprintf $format . &_format_char($value),$value;
}
}
sub _format_char {
my $val = shift;
$val =~ /\./ ? "f" : "d";
}
sub _die {
my $self = shift;
my $msg = join("",@_);
die $msg,"\n";
}
my $CHECK_CONFIG_KEYS = {
"critical" => "critical",
"warning" => "warning",
"mbean" => "mbean",
"attribute" => "attribute",
"operation" => "operation",
"alias" => "alias",
"path" => "path",
"delta" => "delta",
"name" => "name",
"base" => "base",
"base-mbean" => "basembean",
"base-attribute" => "baseattribute",
"base-path" => "basepath",
"unit" => "unit",
"numeric" => "numeric",
"string" => "string",
"label" => "label",
"perfdata" => "perfdata",
"value" => "value",
"null" => "null",
"script" => "script"
};
# Get the proper configuration values
sub AUTOLOAD {
my $self = shift;
my $np = $self->{np};
my $name = $AUTOLOAD;
$name =~ s/.*://; # strip fully-qualified portion
$name =~ s/_/-/g;
if ($CHECK_CONFIG_KEYS->{$name}) {
return $np->opts->{$name} if defined($np->opts->{$name});
if ($self->{config}) {
return $self->{config}->{$CHECK_CONFIG_KEYS->{$name}};
} else {
return undef;
}
} else {
$self->_die("No config attribute \"" . $name . "\" known");
}
}
# To keep autoload happy
sub DESTROY {
}
=back
=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
1;